Search code examples
ansiblejinja2

ansible jinja2 loop dictionary


vars:
  openssh_match_config: 
      test:
        matchkey: 'User'
        matchvalue: 'XXX'
        chrootdirectory: '/home/%u'
        maxsessions: '2'
      test2:
        matchkey: 'Address'
        matchvalue: '1.3.0.3.0/24,192.43.120.0/27'
        maxsessions: '3'
        x11forwarding: 'yes'
        otheropt: VAL1
        otheropt2: VAL2

I need to have it translated as:

Match User XXX
   ChrootDirectory /home/%u
   MaxSessions 2
 
Match Address 1.3.0.3.0/24,192.43.120.0/27
   MaxSessions 3
   X11Forwarding yes
   otheropt  VAL1
   otheropt2 VAL2

I've updated the loop , seems to be on the right track but still can't figure out how to fix it...

{% for k,v in openssh_match_config.items() %}
Match {{ openssh_match_config[k]['matchkey'] }} {{ openssh_match_config[k]['matchvalue'] }}
  {%for x in v.items() %}
   {{x}}
  {%endfor%}
{% endfor %}

Solution

  • Your loop is for x in v.items(). The items() method returns a list of (key, value) tuples. That is, given the following dictionary in variable myvar:

    {"name": "bob"}
    

    Writing myvar.items() would return [("name", "bob")].

    All this means that in your list, the value of x is a tuple, so writing {{x}} will get you the string representation of a tuple: something like ('matchkey', 'Address').

    You could write something like this:

    {% for k,v in openssh_match_config.items() %}
    Match {{ v.matchkey }} {{ v.matchvalue }}
    {% for optname, optval in v.items() %}
      {{ optname }} {{ optval }}
    {% endfor %}
    {% endfor %}
    

    Which gets you:

    Match User XXX
      matchkey User
      matchvalue XXX
      chrootdirectory /home/%u
      maxsessions 2
    Match Address 1.3.0.3.0/24,192.43.120.0/27
      matchkey Address
      matchvalue 1.3.0.3.0/24,192.43.120.0/27
      maxsessions 3
      x11forwarding yes
      otheropt VAL1
      otheropt2 VAL2
    

    And that almost works, except for the erroneous conclusion of matchkey and matchvalue. You can avoid those by using appropriate conditionals:

    {% for k,v in openssh_match_config.items() %}
    Match {{ v.matchkey }} {{ v.matchvalue }}
    {% for optname, optval in v.items() if optname not in ['matchkey', 'matchvalue'] %}
      {{ optname }} {{ optval }}
    {% endfor %}
    {% endfor %}
    

    Which produces:

    Match User XXX
      chrootdirectory /home/%u
      maxsessions 2
    Match Address 1.3.0.3.0/24,192.43.120.0/27
      maxsessions 3
      x11forwarding yes
      otheropt VAL1
      otheropt2 VAL2
    

    Or you could redesign your data structure so that the ssh config values are kept separate from the metadata you use in your playbook:

    - hosts: localhost
      gather_facts: false
      vars:
        openssh_match_config:
          - matchkey: 'User'
            matchvalue: 'XXX'
            config:
              chrootdirectory: '/home/%u'
              maxsessions: '2'
          - matchkey: 'Address'
            matchvalue: '1.3.0.3.0/24,192.43.120.0/27'
            maxsessions: '3'
            config:
              x11forwarding: 'yes'
              otheropt: VAL1
              otheropt2: VAL2
      tasks:
        - copy:
            dest: out.txt
            content: |
              {% for v in openssh_match_config %}
              Match {{ v.matchkey }} {{ v.matchvalue }}
              {% for optname, optval in v.config.items() %}
                {{ optname }} {{ optval }}
              {% endfor %}
              {% endfor %}