how to create conditional list in ansible task

If possible I would like to combine the two tasks below into a single task. Rather than use the when conditional, is there some way to add the last two list items under subject_alt_name (second task) only when item.0.sans is defined? These two tasks are otherwise identical.

I did try some additional filters for those lines to default/omit them, but then I ended up with errors that the list could not be parsed. I also explored the possibility of an inline template, but I cannot tell if that is supported in this context. A templating solution seems ideal to me... I think that technique could be useful in many other scenarios.

The full playbook is pretty short, so I can post that if it would be helpful for additional context.

- name: generate endpoint CSR - no custom sans
  when: item.0.sans is undefined
  community.crypto.openssl_csr:
    common_name: "{{item.0.name}}.{{item.1}}"
    path: "{{endpoint_artifact_dir}}/{{item.1}}//CSRs/{{item.0.name}}.{{item.1}}.pem"
    privatekey_path: "{{endpoint_artifact_dir}}/{{item.1}}/keys/{{item.0.name}}.{{item.1}}.pem"
    subject_alt_name:
      - "DNS:{{item.0.name}}.{{item.1}}"
      - "DNS:{{item.0.name}}"
      - "IP:127.0.0.1"
  with_nested:
    - "{{endpoints}}"
    - "{{domains}}"

- name: generate endpoint CSR - with custom sans
  when: item.0.sans is defined
  community.crypto.openssl_csr:
    common_name: "{{item.0.name}}.{{item.1}}"
    path: "{{endpoint_artifact_dir}}/{{item.1}}/CSRs/{{item.0.name}}.{{item.1}}.pem"
    privatekey_path: "{{endpoint_artifact_dir}}/{{item.1}}/keys/{{item.0.name}}.{{item.1}}.pem"
    subject_alt_name:
      - "DNS:{{item.0.name}}.{{item.1}}"
      - "DNS:{{item.0.name}}"
      - "IP:127.0.0.1"
      - "{{ item.0.sans | join(',') | replace(',', '.' + item.1 + ', ') + '.' + item.1 }}"
      - "{{ item.0.sans | join(',') }}"
  with_nested:
    - "{{endpoints}}"
    - "{{domains}}"

Solution 1:

Use the filter ternary. For example, given the data

    endpoints1:
      - {name: srv1, sans: [alt1, alt2]}
      - {name: srv2, sans: [alt1, alt2]}
    endpoints2:
      - {name: srv1}
      - {name: srv2}
    domains:
      - exampleA.com
      - exampleB.com

The task below

    - debug:
        msg: |-
          subject_alt_name:
            {{ _san|to_nice_yaml|indent(2) }}
      with_nested:
        - "{{ endpoints1 }}"
        - "{{ domains }}"
      vars:
        _san1:
          - "DNS:{{ item.0.name }}.{{ item.1 }}"
          - "DNS:{{ item.0.name }}"
          - "IP:127.0.0.1"
        _sans: "{{ item.0.sans|default([]) }}"
        _san2:
          - "{{ _sans|join(',')|replace(',', '.' + item.1 + ', ') + '.' + item.1 }}"
          - "{{ _sans|join(', ') }}"
        _san: "{{ (_sans|length > 0)|ternary( _san1 + _san2, _san1) }}"

gives

  msg: |-
    subject_alt_name:
      - DNS:srv1.exampleA.com
      - DNS:srv1
      - IP:127.0.0.1
      - alt1.exampleA.com, alt2.exampleA.com
      - alt1, alt2

  msg: |-
    subject_alt_name:
      - DNS:srv1.exampleB.com
      - DNS:srv1
      - IP:127.0.0.1
      - alt1.exampleB.com, alt2.exampleB.com
      - alt1, alt2

  msg: |-
    subject_alt_name:
      - DNS:srv2.exampleA.com
      - DNS:srv2
      - IP:127.0.0.1
      - alt1.exampleA.com, alt2.exampleA.com
      - alt1, alt2

  msg: |-
    subject_alt_name:
      - DNS:srv2.exampleB.com
      - DNS:srv2
      - IP:127.0.0.1
      - alt1.exampleB.com, alt2.exampleB.com
      - alt1, alt2

The same task with the list endpoints2

      with_nested:
        - "{{ endpoints2 }}"
        - "{{ domains }}"

will omit the attribute sans

  msg: |-
    subject_alt_name:
      - DNS:srv1.exampleA.com
      - DNS:srv1
      - IP:127.0.0.1

  msg: |-
    subject_alt_name:
      - DNS:srv1.exampleB.com
      - DNS:srv1
      - IP:127.0.0.1

  msg: |-
    subject_alt_name:
      - DNS:srv2.exampleA.com
      - DNS:srv2
      - IP:127.0.0.1

  msg: |-
    subject_alt_name:
      - DNS:srv2.exampleB.com
      - DNS:srv2
      - IP:127.0.0.1

Notes

  • Optionally, use product and map to create the hosts, e.g.
        _san2:
          - "{{ _sans|product([item.1])|map('join', '.')|join(', ') }}"
          - "{{ _sans|join(', ') }}"
  • mre would be
    endpoints:
      - {name: srv1, sans: alt1}
      - {name: srv2}
    - debug:
        msg: |-
          subject_alt_name:
            {{ _san|to_nice_yaml|indent(2) }}
      loop: "{{ endpoints }}"
      vars:
        _san1:
          - "A: {{ item.name }}"
          - "B: {{ item.name }}"
        _san2:
          - "C: {{ item.sans|default('') }}"
        _san: "{{ (item.sans|default('')|length > 0)|
                  ternary( _san1 + _san2, _san1) }}"

gives

  msg: |-
    subject_alt_name:
      - 'A: srv1'
      - 'B: srv1'
      - 'C: alt1'

  msg: |-
    subject_alt_name:
      - 'A: srv2'
      - 'B: srv2'