support passing new variables directly to the template module, but not only

Hello everyone!

I was asked to bring this up in the email list as well. Please see
issues #8733 and #4546 for the origins of this email.

Just encountered the problem reported in #4546
Due to openSSL not being able to accept extendedusagetypes from the
command line, only from config files, I need to generate a temporary
configuration file, so I would need to do something similar to this:

- template: src=openssl_config.j2 dest=/tmp/openssl.cnf
  vars:
    usage: serverAuth
- name: sign client csrs
  shell: openssl req -in {{item}} -config /tmp/openssl.cnf..... [sign items]
  with_items: server_certs

- template: src=openssl_config.j2 dest=/tmp/openssl.cnf
  vars:
    usage: clientAuth
- name: sign server csrs
  shell: openssl req -in {{item}} -config /tmp/openssl.cnf .....
  with_items: client_certs

Without being able to manipulate the usage variable passed to the
template, one can not reuse the config template.

Of course this could be worked around by putting the two tasks into a
different file, and then including it with parameters (which is
supported btw), but it seems silly that one needs to do that if one
wants to reuse the template.

I'd like to see the ability to pass in extra variables not implemented
in the template module, but globally, so that one could include extra
variables to *any* module, just like the when, or changed_when, etc.
work.

Regards,
  Akos Vandra

There was another comment to about 3 weeks ago from @iraksdale

""
Have to agree with @claco here. I'm using a template to output
multiple files based on a with_dict loop, and semantically it kind of
sucks to use item.key or item.value all over the template - doesn't
make them very reusable.

It would be nice to define "service_name" or "hostname" variables or
whatever in the playbook where it's easy to see their relationship to
the variable being looped, and have the templates use {{service_name}}
instead of having to put "whatever_{{item.key}}" in the templates.

It really hinders the reuse of templates, because it marries them to a
particular playbook structure, and the passthrough to the file module
could be preserved if you just added a template_vars argument that
would make its contents available to the template.

As a new user to ansible, I'd say the lack of this is really
counterintuitive. Is it possible to reconsider this decision? I think
it adds a lot to the reusability & understandability of templates.
""

Regards,
  Akos Vandra

Small request - when referencing tickets, hyperlinks keep me from copying and pasting every link and then clicking on them, and several thousand other people from doing the same :slight_smile: … I looked them up and summarized here.

The first ticket, which we declined as a feature early in our development. Not an issue per say but more of a feature request: https://github.com/ansible/ansible/issues/4546. The ticket on https://github.com/ansible/ansible/issues/8733 is a duplicate of 4546.

Basically I’d like to explore what your particular use case is, so that we can find an idiomatic solution in Ansible, if one exists.

Can we explore what you are modeling? I’m wanting to make sure this shouldn’t be a role, and so on, or that it can’t be done more natively in other ways.

I’m not opposed to the feature, but I’m also wanting to limit having 7000 different ways to set variables and pass them around, and in many cases this would be abused by folks doing “x={{x}}” and not knowing that all the variables are automatically passed down.

It may be that your particular use case does warrant this, but I would like to understand it if possible.

Thanks!

Hi!

Sorry for not copying hyperlinks, I hate that too - I guess I just got
too confortable with github doing that for me :slight_smile:

I have written down a few use cases in my previous emails and the
ticket, but I will summarize them here again for simplicity's sake.
I have also just encountered another usecase, i'm detailing it as the first one.

The problem with the current setup is that by not being able to pass
in, rename, or specify how to look up input variables, it hinders
template reusability.

*** USE CASE 1 ***

I have a list of hosts, and for reasons outside the scope of this
discussion, I need to create a /etc/hosts.d directory containing one
entry per file, which will be concatenated into one /etc/hosts file by
an incron job.

So I'd like to do this:

- template: src=foo.j2, dest=/etc/hosts.d/{{item}}
dns={{hostvars[item].ansible_hostname}}.vpn
ip={{hostvars[item].ansible_tun0.ipv4.address}}
  with_items: {{ groups["vpn-clients"] }}

And yes, I could use that complicated lookup within the template, but
would be mixing up layers. The tasks should know how to find out what
to render, and templates should be dead simple, just render what is
given to them.
And I couldn't reuse this template if I'd hardcode the way to look up
the tunnel0 interface address into them in case I would need (and I
do) to
add other interface addresses to the list, like so:

- template: src=foo.j2, dest=/etc/hosts.d/{{item}}
dns={{hostvars[item].ansible_hostname}}.public
ip={{hostvars[item].ansible_eth0.ipv4.address}}
  with_items: {{ groups["all"] }}

**** USE CASE 2 ****

Due to openSSL not being able to accept extendedusagetypes from the
command line, only from config files, I need to generate a temporary
configuration file, so I would need to do something similar to this:

- template: src=openssl_config.j2 dest=/tmp/openssl.cnf usage=serverAuth
- name: sign client csrs
  shell: openssl req -in {{item}} -config /tmp/openssl.cnf..... [sign items]
  with_items: server_certs

- template: src=openssl_config.j2 dest=/tmp/openssl.cnf usage=clientAuth
- name: sign server csrs
  shell: openssl req -in {{item}} -config /tmp/openssl.cnf .....
  with_items: client_certs

**** USE CASE 3 ****

Very similar to Use case 1, it was mentioned by @ iraksdale in the
first Issue on Gtihub:

Have to agree with @claco here. I'm using a template to output
multiple files based on a with_dict loop, and semantically it kind of
sucks to use item.key or item.value all over the template - doesn't
make them very reusable.

It would be nice to define "service_name" or "hostname" variables or
whatever in the playbook where it's easy to see their relationship to
the variable being looped, and have the templates use {{service_name}}
instead of having to put "whatever_{{item.key}}" in the templates.

Regards,
  Akos Vandra

A plus for Use Case 1:

Adding fixed ip addresses like this:

- template: template: src=foo.j2, dest=/etc/hosts.d/localhost
dns=localhost ip=127.0.0.1

Thanks, I understand #1.

Use case #2 could be handled by:

  • set_fact: mode=‘serverAuth’

  • template: …

  • set_fact: mode=‘clientAuth’

  • template: …

Though I’d be tempted to use seperate templates and keep it simple.

Use case 2 could be handled like that, but I feel that is just a
work-around for a missing feature in ansible. Those two tasks actually
is one single task that needed to be split into two parts because
templates cannot receive a set of facts to use.

In fact I was tempted to work around the whole thing by creating a
task file that included ONLY that one template creation task, and
included passed the necessary variables to that, which is obviously a
hack, and also does not work with with_items, or with_dict

- include: create_template.yml foo=bar, baz=raz

Regards,
  Akos Vandra

Thanks, I understand the request. We’ll think about it.

Hi,

Were there any decisions taken since then? I also vote for being able to send variables when generating a template. Other solutions (parametrized roles or extra “set_fact” calls) look like heavy workarounds to a simple problem.

  • template:
    src: …
    dest: …
    vars:
    service: …
    title: …

Not sure why you don't just set the vars at play level, they would be
directly accessible by the template.

Hi,

This is similar to declaring global variables each time before making a function call. I may be wrong but I look at templates similarly. Today you can’t pass any arguments when making a call, only rely on global variables available in a play/role.

If a template is rendered within with_dict loop - it ends up using {{ item.key }} and {{ item.value }} (yuk!) rather than simpler names, passed into a template on each iteration. So templates are attached to variables naming in a play/role and aren’t self-contained, like a good old function. Any time variable names are changed in a play/role - template needs to be updated as well. It would be so much cleaner to say “This template needs variables ‘user’ and ‘account’ to render, pass it in”.

Have a look at Chef:

http://docs.chef.io/templates.html

template “/etc/sudoers” do

variables({

:sudoers_groups => node[:authorization][:sudo][:groups],
:sudoers_users => node[:authorization][:sudo][:users]
})
end

What is cleaner to use in a template - “sudoers_groups” or “node[:authorization][:sudo][:groups]”? Not to mention what happens if the same template if rendered in different contexts where, say, node[:authorization][:sudo][:groups] is something else.
Ansible answer to that will be - global “set_fact” for every variable before “template” call.