Search code examples
ansiblejinja2

AttributeError: 'list' object has no attribute 'splitlines'


Considering the following test playbook:

---
- name: Node Tolerations
  hosts: localhost
  gather_facts: false
  vars:
    tolerations:
      - key: node.cilium.io/agent-not-ready
        operator: Exists
        effect: NoExecute
      - key: node-role.kubernetes.io/control-plane
        operator: Exists
        effect: NoSchedule
  tasks:
    - name: Set node tolerations fact
      ansible.builtin.set_fact:
        node_tolerations: "{{ (node_tolerations | default([]) | union([':'.join((item.key, item.effect))])) }}"
      with_items: '{{ tolerations }}'

    - name: Set node taint fact
      ansible.builtin.set_fact:
        node_taint: "{{ node_tolerations | select('search', 'control-plane') }}"

    - name: Variable output
      ansible.builtin.debug:
        var: node_taint

Which produces the expected result:

ok: [localhost] =>
  node_taint:
  - node-role.kubernetes.io/control-plane:NoSchedule

While using the node_taint fact into a Jinja2 template:

node-taint:
  {{ node_taint | indent(2) }}

The following error is generated:

AttributeError: 'list' object has no attribute 'splitlines'

As temporary workaround, I used:

node-taint:
  - {{ node_taint | join }}

But I prefer the initial Jinja2 format, allowing me to define the proper indentation. I was wondering if you can provide some insight, what would be the proper fix.

Edit: A second usage I will have for the above detailed playbook is:

tolerations:
  {{ tolerations | indent(2) }}

Which produces the same error. I would prefer to avoid the use of for loops inside Jinja template and manipulate the data at fact level, allowing me to use a proper indent inside template.


Solution

  • Given the mre list

      node_taint:
        - node-role.0
        - node-role.1
        - node-role.2
    

    the below debug

       - debug:
           var: node_taint
    

    gives in YAML format

      node_taint:
      - node-role.0
      - node-role.1
      - node-role.2
    

    If you want to indent the lines

        - debug:
            msg: |
              node_taint:
                {{ node_taint | indent(2) }}
    

    you get the below error because the value of node_taint isn't a string

    ... The error was: AttributeError: 'list' object has no attribute 'splitlines'

    Quoting from the function indent:

    jinja-filters.indent(s: str, width: int | str = 4, ...
    

    Return a copy of the string with each line indented by 4 spaces. The first line and blank lines are not indented by default.

    The solution is to convert the value of node_taint to a string. Use the filter to_nice_yaml

        - debug:
            msg: |
              node_taint:
                {{ node_taint | to_nice_yaml | indent(2) }}
    

    gives

      msg: |-
        node_taint:
          - node-role.0
          - node-role.1
          - node-role.2
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        node_taint:
          - node-role.0
          - node-role.1
          - node-role.2
    
      tasks:
    
        - debug:
            var: node_taint
    
        - debug:
            msg: |
              node_taint:
                {{ node_taint | to_yaml | indent(2) }}
    
        - debug:
            msg: |
              node_taint:
                {{ node_taint | to_nice_yaml | indent(2) }}