2

I need to assert the policy-maps from Cisco devices. And Cisco for some reason adds trailing whitespaces on some lines, but not all. I want to remove them, but, only the trailing whitespaces.

- name: Get running class-map & policy-map config
  vars:
    ansible_connection: network_cli
  ios_command:
    commands:
      - 'show run | sec class-map|policy-map'
  register: show_policy

- name: Print trim
  debug:
    var: show_policy.stdout_lines | trim

- name: Ansible block with assert module
  block:
    - name: Validate running line
      ansible.builtin.assert:
        that:
          - "lookup('template', 'policy_desired.j2').splitlines() in show_policy.stdout_lines"
        success_msg: "TEST: {{ UNIT_HOSTNAME }}: VALIDATE RUNNING POLICY: PASSED"
        fail_msg: "TEST: {{ UNIT_HOSTNAME }}: VALIDATE RUNNING POLICY: FAILED"

This gives the following output:

{
    "show_policy.stdout_lines | trim": [
        [
            "class-map match-any CM-QOS-GENERIC-BESTEFFORT-MARK",
            "  description Generic - Best Effort",
            " match access-group name ACL-QOS-GENERIC-BESTEFFORT",
            "class-map match-any CM-QOS-1P3Q-Q1",
            " match dscp cs4  cs5  ef ",        <--- Notice the whitespace
            "class-map match-any CM-QOS-1P3Q-Q2",
            " match dscp cs6  cs7 ",
            "class-map match-any CM-QOS-1P3Q-Q3",
            " match dscp cs1 "
        ]
    ]
}   

The filter should then be added in the assert module so that it is gone when being asserted.

I have tried multiple things, but nothing seems to do the trick:

- name: Print trim
  debug:
    var: show_policy.stdout_lines | trim

- name: Print trim
  debug:
    var: show_policy.stdout_lines | strip

- name: Print trim
  debug:
    var: show_policy.stdout_lines.strip()

- name: Print trim
  debug:
    var: "{{ show_policy.stdout_lines | map('trim') }}"

- name: Print trim
  debug:
    var: "{{ show_policy.stdout_lines | trim }}"
β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
C.AN5
  • 31
  • 4

2 Answers2

0

You can use a regex_replace filter in order to achieve that.

Note: It seems, from you debug of show_policy.stdout_lines that it is actually a list of list, so you need the trim to happen on show_policy.stdout_lines.0.

Mind that, in the example below, I am purposely using a YAML multiline syntax, to save me from escaping the backslashes.
If you don't want to use this syntax, you will have to escaping those backslashes by doubling them (i.e. \\1 and \\s instead of \1 and \s).

- debug:
    msg: >-
      {{
        show_policy.stdout_lines.0 | map('regex_replace', '(.*)\s+$', '\1')
      }}

Given the task:

- debug:
    msg: >-
      {{
        show_policy.stdout_lines | map('regex_replace', '(.*)\s+$', '\1')
      }}
  vars:
    show_policy:
      stdout_lines:
        -
          - "class-map match-any CM-QOS-GENERIC-BESTEFFORT-MARK"
          - "  description Generic - Best Effort"
          - " match access-group name ACL-QOS-GENERIC-BESTEFFORT"
          - "class-map match-any CM-QOS-1P3Q-Q1"
          - " match dscp cs4  cs5  ef "
          - "class-map match-any CM-QOS-1P3Q-Q2"
          - " match dscp cs6  cs7 "
          - "class-map match-any CM-QOS-1P3Q-Q3"
          - " match dscp cs1 "

This yields:

msg:
  - class-map match-any CM-QOS-GENERIC-BESTEFFORT-MARK
  - '  description Generic - Best Effort'
  - ' match access-group name ACL-QOS-GENERIC-BESTEFFORT'
  - class-map match-any CM-QOS-1P3Q-Q1
  - ' match dscp cs4  cs5  ef'
  - class-map match-any CM-QOS-1P3Q-Q2
  - ' match dscp cs6  cs7'
  - class-map match-any CM-QOS-1P3Q-Q3
  - ' match dscp cs1'
β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
  • Thank you for the solution! Although it works, I for some reason cannot use it in the assert module. So I tried to do set_fact and write the trimmed output to a new variable and use that in the assert. But then the whitespace returns. Tried debug the trimmed variable out, and the whitespace is gone. But when used in the assert module, it reappears. – C.AN5 Jan 30 '23 at 15:41
  • Nope, sorry.. It was my own mistake. I forgot to change the variable in my diff module.. IT WORKS. Thanks allot! – C.AN5 Jan 30 '23 at 15:53
  • But if you can show how to use it directly in the assert module it would be fantastic. That I cannot figure out. Would love to be able to not have to write it to a new variable with set_fact. – C.AN5 Jan 30 '23 at 15:54
0

Since you've tried already to use the Python method .strip(), a minimal example playbook

---
- hosts: localhost
  become: false
  gather_facts: false

  vars:

    pol:
      stdout_lines:
        - "First line"
        - " Second line with leading whitespace"
        - "Third line with trailing whitespace "
        - "  Forth line with leading and trailing whitespaces  "
        - "Last line"

  tasks:

  - name: Print stripped
    debug:
      msg: "\"{{ item.rstrip() }}\""
    loop: "{{ pol.stdout_lines }}"

will result into the output of

TASK [Print stripped] ***********************************************************
ok: [localhost] => (item=First line) =>
  msg: '"First line"'
ok: [localhost] => (item= Second line with leading whitespace) =>
  msg: '" Second line with leading whitespace"'
ok: [localhost] => (item=Third line with trailing whitespace ) =>
  msg: '"Third line with trailing whitespace"'
ok: [localhost] => (item=  Forth line with leading and trailing whitespaces  ) =>
  msg: '"  Forth line with leading and trailing whitespaces"'
ok: [localhost] => (item=Last line) =>
  msg: '"Last line"'

You may also have a look into .lstrip().

Unfortunately there is no direct Jinja2 Builtin Filter for, so a simple approach like

  - name: Print trimmed
    debug:
      msg: "{{ pol.stdout_lines | map('trim') }}"

won't work since it

Strip leading and trailing characters, by default whitespace.

Further Q&A


How to proceed further?

If there are several use cases where one could take advantages from a filter like strip(), lstrip() and rstrip() it might be feasible to write an own filter plugin.

cat plugins/filter/rstrip.py

#!/usr/bin/python

class FilterModule(object):
    def filters(self):
        return {
            'rstrip': self.rstrip,
        }

    def rstrip(self, String):
        return String.rstrip()

which can then be used as follow

  - name: Print stripped
    debug:
      msg: "{{ pol.stdout_lines | map('rstrip') }}"

and producing the required output.

Further Q&A reagarding Filter Plugins

U880D
  • 8,601
  • 6
  • 24
  • 40
  • 1
    This is great input. Allthough it will require some changes in our ansible setup, slightly. But I will take this up with my team and present it as a possiibiliy. Didn't know we could write our own filters. Just need to figure out where to put the py file. Thanks allot for a great solution. – C.AN5 Jan 30 '23 at 15:43