loopsnginxansiblevhosts

How to Loop right between Playbook and Role in Ansible


I try to loop over the geerlingguy.nginx Role to create nginx VHosts. But I don't get it done:

Playbook.yml

- hosts: some.server
  become: true
  roles:
    - geerlingguy.nginx
  tasks:
    - name: looping vhosts
      include_tasks: vhosts.yml
      loop:
        - { name: 'vhost1.bla.com', state: 'present' }
        - { name: 'vhost1.bla.com', state: 'present' }

For this Server I create a Host_vars File:

host_vars.yml

nginx_worker_processes: "auto"
nginx_worker_connections: 768
nginx_extra_http_options: |
    gzip on;
    types_hash_max_size 2048;
    include /etc/nginx/mime.types;
    ssl_prefer_server_ciphers on;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
nginx_vhosts:
  - listen: "443 ssl http2"
    server_name: '{{ item.name }}'
    server_name_redirect: " {{ item.name }} "
    root: "/var/www/{{ item.name }}"
    index: "index.php index.html index.htm"
    access_log: "/var/www/{{ item.name }}/logs/access_{{ item.name }}.log"
    error_log: "/var/www/{{ item.name }}/logs/erro_{{ item.name }}.log"
    state: "{{ item.state }}"
    template: "{{ nginx_vhost_template }}"
    filename: "{{ item.name }}"
    extra_parameters: |
      ssl_certificate     /etc/ssl/certs/ssl-cert-snakeoil.pem;
      ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
      ssl_protocols       TLSv1.1 TLSv1.2;
      ssl_ciphers         HIGH:!aNULL:!MD5;

This is the vhost.yml from the geerlingguy.nginx Role:

- name: Remove default nginx vhost config file (if configured).
  file:
    path: "{{ nginx_default_vhost_path }}"
    state: absent
  when: nginx_remove_default_vhost | bool
  notify: restart nginx

- name: Ensure nginx_vhost_path exists.
  file:
    path: "{{ nginx_vhost_path }}"
    state: directory
    mode: 0755
  notify: reload nginx

- name: Add managed vhost config files.
  template:
    src: "{{ item.template|default(nginx_vhost_template) }}"
    dest: "{{ nginx_vhost_path }}/{{ item.filename|default(item.server_name.split(' ')[0] ~ '.conf') }}"
    force: true
    owner: root
    group: "{{ root_group }}"
    mode: 0644
  when: item.state|default('present') != 'absent'
  with_items: "{{ nginx_vhosts }}"
  notify: reload nginx
  tags:
    - skip_ansible_lint

- name: Remove managed vhost config files.
  file:
    path: "{{ nginx_vhost_path }}/{{ item.filename|default(item.server_name.split(' ')[0] ~ '.conf') }}"
    state: absent
  when: item.state|default('present') == 'absent'
  with_items: "{{ nginx_vhosts }}"
  notify: reload nginx
  tags:
    - skip_ansible_lint

- name: Remove legacy vhosts.conf file.
  file:
    path: "{{ nginx_vhost_path }}/vhosts.conf"
    state: absent
  notify: reload nginx

So, when I run the playbook I got:

fatal: [some.server]: FAILED! => {
    "msg": "[{'listen': '443 ssl http2', 'server_name': '{{ item.name }}'... HIGH:!aNULL:!MD5;\\n'}]: 'item' is undefined

I try it in different ways but always get the same error, would be greate if someone could help me.


Solution

  • Your approach doesn't work, you won't get anything out of a loop at this point. Furthermore, it is not possible to define a variable or data structure and have the Jinja logic evaluate it later.

    The implementation of geerlingguy provides that the variable nginx_vhosts is defined. This variable must be a list of dicts, and this list is then automatically processed.

    You have two main options:

    Option 1

    You create nginx_vhosts as a list of dicts for all your virtual hosts.

    nginx_vhosts:
      - listen: "443 ssl http2"
        server_name: "vhost1.bla.com"
        server_name_redirect: "www.vhost1.bla.com"
        root: "/var/www/vhost1.bla.com"
        index: "index.php index.html index.htm"
        error_page: ""
        access_log: "/var/www/vhost1.bla.com/logs/access_vhost1.bla.com.log"
        error_log: "/var/www/vhost1.bla.com/logs/error_vhost1.bla.com.log"
        state: "present"
        template: "{{ nginx_vhost_template }}"
        filename: "vhost1.bla.com.conf"
        extra_parameters: |
          ssl_certificate     /etc/ssl/certs/ssl-cert-snakeoil.pem;
          ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
          ssl_protocols       TLSv1.1 TLSv1.2;
          ssl_ciphers         HIGH:!aNULL:!MD5;
      - listen: "443 ssl http2"
        server_name: "vhost2.bla.com"
        server_name_redirect: "www.vhost2.bla.com"
        root: "/var/www/vhost2.bla.com"
        index: "index.php index.html index.htm"
        error_page: ""
        access_log: "/var/www/vhost2.bla.com/logs/access_vhost2.bla.com.log"
        error_log: "/var/www/vhost2.bla.com/logs/error_vhost2.bla.com.log"
        state: "present"
        template: "{{ nginx_vhost_template }}"
        filename: "vhost2.bla.com.conf"
        extra_parameters: |
          ssl_certificate     /etc/ssl/certs/ssl-cert-snakeoil.pem;
          ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
          ssl_protocols       TLSv1.1 TLSv1.2;
          ssl_ciphers         HIGH:!aNULL:!MD5;
    

    Option 2

    A bit more complicated, but I think that was your wish, with the loop.

    Create a separate file for your tasks myvhost.yml with the following content:

    ---
    - name: create directories
      file:
        path: "{{ item }}"
        state: directory
      with_items:
        - "/var/www/{{ vhost.name }}"
        - "/var/www/{{ vhost.name }}/logs"
    
    - name: define nginx_vhosts variable
      set_fact:
        nginx_vhosts:
          - listen: "443 ssl http2"
            server_name: '{{ vhost.name }}'
            # server_name_redirect: " {{ vhost.name }} "
            root: "/var/www/{{ vhost.name }}"
            index: "index.php index.html index.htm"
            access_log: "/var/www/{{ vhost.name }}/logs/access_{{ vhost.name }}.log"
            error_log: "/var/www/{{ vhost.name }}/logs/erro_{{ vhost.name }}.log"
            state: "{{ vhost.state }}"
            # template: "{{ nginx_vhost_template }}"
            filename: "{{ vhost.name }}"
            extra_parameters: |
              ssl_certificate     /etc/ssl/certs/ssl-cert-snakeoil.pem;
              ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
              ssl_protocols       TLSv1.1 TLSv1.2;
              ssl_ciphers         HIGH:!aNULL:!MD5;
    
    - name: include vhosts.yml from geerlingguy
      include_role:
        name: geerlingguy.nginx
        tasks_from: vhosts
    

    Here you set the variable nginx_vhosts with new values, a list with a single dict. Then you perform the import the tasks vhosts of the role from geerlingguy.

    In your playbook, on the other hand, you import your new myvhost.yml with the loop.

    - name: looping vhosts
      include_tasks: myvhost.yml
      loop:
        - { name: 'vhost1.bla.com', state: 'present' }
        - { name: 'vhost2.bla.com', state: 'present' }
      loop_control:
        loop_var: vhost
    

    Explanation of the changes

    For your loop you have to rename the loop variable, otherwise there will be conflicts with loops in the vhosts.yml of geerlingguy (I had overlooked this at the beginning), see loop_var: vhost. After renaming the loop variable, you must of course also change the name in myvhost.yml from item to vhost.

    Before running your task looping vhosts, the geerlingguy.nginx role should have been run once, e.g. if it is listed in your playbook under roles:.

    Another change I made in myvhost.yml. Instead of include_tasks use better include_role with tasks_from: vhosts.

    I commented out the server_name_redirect: setting for now, because it creates nginx config files that crash nginx. If you really need this setting you have to analyze this in more detail.

    Furthermore the certificate files (ssl-cert-snakeoil) must exist before creating the VHosts.

    A complete playbook might look like this:

    ---
    - hosts: nginx
      become: true
    
      roles:
        - geerlingguy.nginx
    
      tasks:
        - name: looping vhosts
          include_tasks: myvhost.yml
          loop:
            - { name: 'vhost1.bla.com', state: 'present' }
            - { name: 'vhost2.bla.com', state: 'present' }
          loop_control:
            loop_var: vhost