Как разбить строку на список с помощью Ansible / Jinja2?

#ansible #jinja2

#ansible #jinja2

Вопрос:

У меня есть следующие переменные:

 domain_names:
  - app1.example.com
  - app2.example.com

customers:
  - name: customer1
  - name: customer2
  

И я пытаюсь сгенерировать следующий список доменных имен:

 - customer1.app1.example.com
- customer2.app1.example.com
- customer1.app2.example.com
- customer2.app2.example.com
  

Используя следующий код Ansible / Jinja2:

 - name: check which certificates exist
  stat:
    path: '/etc/nginx/{{item}}.crt'
  register: cert_file
  loop: '{% for d in domain_names %}{{ d }} {% for customer in customers %}{{ customer.name }}.{{ d }} {% endfor %}{% endfor %}'
  

Однако я получаю следующую ошибку:

 failed | msg: Invalid data passed to 'loop', it requires a list, got this instead: customer1.app1.example.com customer2.app1.example.com customer1.app2.example.com customer2.app2.example.com. Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup.
  

Как я могу это исправить?

Ответ №1:

Просто используйте правильные инструменты :). В этом случае вашими друзьями являются:

  • map фильтр для извлечения name атрибута в виде списка из вашей customers переменной
  • product фильтр для объединения двух ваших списков

например, test.yml playbook:

 ---
- name: product and map filters demo
  hosts: localhost
  gather_facts: false
  
  vars:
    domain_names:
      - app1.example.com
      - app2.example.com

    customers:
      - name: customer1
      - name: customer2

  tasks:
    - name: Demonstrate product and map filters use
      debug:
        msg: "{{ item.0 }}.{{ item.1 }}"
      loop: "{{ customers | map(attribute='name') | product(domain_names) | list }}"
  

Что дает:

 $ ansible-playbook test.yml 

PLAY [product and map filters demo] *******************************************************************************************************************************************************************************

TASK [Demonstrate product and map filters use] ********************************************************************************************************************************************************************
ok: [localhost] => (item=['customer1', 'app1.example.com']) => {
    "msg": "customer1.app1.example.com"
}
ok: [localhost] => (item=['customer1', 'app2.example.com']) => {
    "msg": "customer1.app2.example.com"
}
ok: [localhost] => (item=['customer2', 'app1.example.com']) => {
    "msg": "customer2.app1.example.com"
}
ok: [localhost] => (item=['customer2', 'app2.example.com']) => {
    "msg": "customer2.app2.example.com"
}

PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

  

Применительно к вашей задаче это дает:

 - name: Check which certificates exist
  stat:
    path: '/etc/nginx/{{ item.0 }}.{{ item.1 }}.crt'
  register: cert_file
  loop: "{{ customers | map(attribute='name') | product(domain_names) | list }}"
  

Если вы действительно хотите повторно использовать этот список, вы можете встроить его в другую переменную. Один из простых способов понять это — встроить ее в set_fact задачу, например

 - name: Create my application names list
  vars:
    current_name: "{{ item.0 }}.{{ item.1 }}"
  set_fact:
    application_names_list: "{{ application_names_list | default([])   [current_name] }}"
  loop: "{{ customers | map(attribute='name') | product(domain_names) | list }}"

- name: Check which certificates exist
  stat:
    path: '/etc/nginx/{{ item }}.crt'
  register: cert_file
  loop: "{{ application_names_list }}"
  

Но вы также можете объявить ее «статически» в своих переменных с помощью немного более сложного выражения (см. map Возможности фильтрации и join фильтр)

 ---
- name: product and map filters demo
  hosts: localhost
  gather_facts: false

  vars:
    domain_names:
      - app1.example.com
      - app2.example.com

    customers:
      - name: customer1
      - name: customer2

    application_names_list: "{{ customers | map(attribute='name') | product(domain_names) | map('join', '.') | list }}"

  tasks:
    - name: Demonstrate product and map filters use
      debug:
        var: application_names_list
  

=>

 PLAY [product and map filters demo] ****************************************************************************************************************************************************************************************************

TASK [Demonstrate product and map filters use] *****************************************************************************************************************************************************************************************
ok: [localhost] => {
    "all_domains": [
        "customer1.app1.example.com",
        "customer1.app2.example.com",
        "customer2.app1.example.com",
        "customer2.app2.example.com"
    ]
}

PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  

Комментарии:

1. Вы, сэр, гений. Вы не только ответили на мой вопрос, но и научили меня множеству полезных приемов, которые я обязательно буду использовать в будущем. Хотел бы я, чтобы у меня было больше одного голоса за!