Score:0

Ansible jinja2 template from JSON format provided as extra-vars

us flag

I have this jinja2 template:

# {{ ansible_managed }}

{% for vhost in nginx_vhosts %}
{%- if vhost.name == item.name -%}

# redirect www to non-www
server {
    listen {{ nginx_port }};
    listen [::]:{{ nginx_port }};
    port_in_redirect off;

    server_name www.{{ vhost.name }};
    return 301 http://{{ vhost.name }}$request_uri;
}
{%- endif -%}
{%- endfor -%}

An ansible role with an yaml file vhosts.yml containing definitions like this:

nginx_vhosts:
      - name: "test1.com"
        repo: "git1"
        branch: master
        state: present
      - name: "test2.com"
        repo: "git2"
        branch: master
        state: present
...
      - name: "test101.com"
        repo: "git101"
        branch: master
        state: present

A task inside playbook.yml:

- name: "Generate nginx vhost configuration file"
  template:
    src: templates/nginx-vhost-template.j2
    dest: "{{ nginx_vhosts_dir }}/{{ item.name }}.conf"
    owner: "{{ nginx_user }}"
    group: "{{ nginx_group }}"
    mode: 0640
  with_items:
    - "{{ nginx_vhosts }}"
  when:
    - item.state == 'present'
  notify:
    - nginx-restart

I ran a taks like:

ansible-playbook -l web1 playbook.yml --tags=nginx-vhost-config

which is working fine, it will create from template a nginx vhost configuration file on the remote server as domain1.com.conf and so on for all the found definitions.

Assuming that in the vhosts.yml file I have test1.com up to test100.com, I'll add let's say test101.com and I want to run the tasks strictly for that test101.com and not for all previous hosts. So I tried something like this:

ansible-playbook -l web1 playbook.yml --tags=nginx-vhost-config -e "{ 'nginx_vhosts': { 'name': 'test101.com', 'state': 'present', 'repo': 'git101', 'branch': 'master' }}"

The issue with this is that it results in an error when trying to replace values from the jinja2 template.

An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.errors.AnsibleUndefinedVariable: 'ansible.parsing.yaml.objects.AnsibleUnicode object' has no attribute 'name'

I've also tried using loop instead of with_items but no luck.

I understand that when using the extra-vars, the provided content is in JSON format but I was not able to find a different way to pass the content from vhosts.yml as extra vars for one single entry. Is there any way to make this functional?

Is there a better approach maybe?

flowerysong avatar
th flag
One minor note: what you are passing in is YAML, not JSON. It looks a lot like JSON because it's flow-style YAML, but is easily distinguished because JSON doesn't allow single quotes to be used the way they are here.
Score:1
th flag

You are passing in an object/dictionary but your code is expecting a list. You need to either wrap it in a list when you pass it in, or account for the different possible structures when you consume it.

You should first reduce the number of places that reference nginx_vhosts by using the current loop item directly in your template:

# {{ ansible_managed }}

# redirect www to non-www
server {
    listen {{ nginx_port }};
    listen [::]:{{ nginx_port }};
    port_in_redirect off;

    server_name www.{{ item.name }};
    return 301 http://{{ item.name }}$request_uri;
}

You can then modify the structure you pass in slightly:

"{ 'nginx_vhosts': [{ 'name': 'test101.com', 'state': 'present', 'repo': 'git101', 'branch': 'master' }]}"

Or modify your loop slightly:

- name: "Generate nginx vhost configuration file"
  template:
    src: templates/nginx-vhost-template.j2
    dest: "{{ nginx_vhosts_dir }}/{{ item.name }}.conf"
    owner: "{{ nginx_user }}"
    group: "{{ nginx_group }}"
    mode: "0640"
  loop: "{{ [ nginx_vhosts ] | flatten }}"
  when:
    - item.state == 'present'
  notify:
    - nginx-restart
Bogdan Stoica avatar
us flag
It makes sense now. It works like a charm! Thank you!
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.