Ansible Tasks reusability

Hi,

I’ve been playing with Ansible for some time and I’m hitting the wall when it comes to re-usability.
I find that Ansible requires me to duplicate many of my tasks just to cater that specific needs.
Maybe it’s the lacking on my side. So, I hope you guys can kindly shed some light on this.

I notice that the tasks are very ‘strict’. Once we provide an optional param, the rest of the lists must provide it too.

Say. I want to create two users:
vars:

  • users:
  • { name: test1, uid: 1234 }
  • { name: test2 }

Ideal case would be:
tasks:

  • name: creating user
    user: name={{ item.name }} uid={{ item.uid }}
    with_items: users

ie, I hope Ansible will be smart enough to ignore the optional ‘uid’ for ‘test2’ but it wont.

The implication is big. Instead of creating a role name ‘os_create_user’ and passing “users” with the neeeded param as the var,
I need to identify the needed users/param beforehand and create the same tasks for each. To put in short, I cannot create them dynamically.

Worse,I cannot share it between different playbooks because of the ‘strict’ requirement.
So, I ended up creating the same thing for other roles, this time with just an extra parameter.

Actually thats some others that I’m having prob with, but I will keep it at that first.
Many thanks in advance for any guidance on this.

You need to read up on roles.

http://docs.ansible.com/playbooks_roles.html

Actually, if it was up to me I would say that you should explicitly set a UID for both users otherwise you end up with the same user on different machines having different UIDs and when you are trying to manage users across lots of machines it becomes a lot harder.

Adam

To generically answer the question, you could use a when conditional.

The inference that Ansible isn’t smart enough is incorrect, the content isn’t smart enough :slight_smile: … yet.

Thanks for the reply but I think you guys are missing the point.

The ‘user’ module I put is just a simple example showing the use cases. To make it clearer, it can be ‘file’ module where I just want to backup one file and not the rest, but using the same action for all.
Also, I do follow the “Best Practice” and using roles, and hence the whole discussion because of the limited usability.

@Michael DeHaan, I’m well aware of the use of ‘when’ but it makes your tasks tightly coupled with the ‘trigger’. You need to cater for all the possibilites.
It will just clutter the tasks. Say you need to create many users with the following requirement:

  • username, uid, and gid only
  • above + supplementary group only
  • above + custom shell only
  • above + different home only
  • above + etc…

Then your ‘when’ condition will become a real sphegetti because Ansible is ran in sequence. (Try to make the ‘when’ logic for that and you will see).
As far as i know they is no module to skip the subsequent tasks when a task is successfull. Off cause you can use ‘when’ to skip the subsequence but that will just make it even more complicated.

Thanks.

Meh, Im bad at giving example. It’s not ‘file’ module but ‘copy’ module. What i wanted to say is like this 1 just want to specify ‘backup=yes’ for 1 file only and use the default for the rest(dont want to specify ‘backup=no’)

Actually that is pretty simple. You can set a default value for a variable either for a role or using a standard jinja2 filter…

Copy: backup={{backupfile|default (no)}}…

At least I think that that should work.

Adam.

You should probably consider the |default feature in Jinja2 for fields you don’t want to define a value for, and as Adam said, specify the UUID.

This should take care of the above concerns.

{{ item.shell | default(‘/bin/sh’) }}

etc

Yup. I’ve also been using ‘default’. However, As showed in ‘user’ module example, problem is when there’s no default value.
ie in the case when certain users need to have supplementary group and some others do not have supplementary group. (there is no such thing as ‘groups=item.group | default(none)’).

Well, I guess thats the limitation in Ansible and should just accept it until they developers come out with new modules/mechanism. (It’s a great product anyway!)

Thanks.

Actually, I think that you can do this, with a little creativity…

vars:

  • users:
  • { name: test1, uid: 1234}
  • { name: test2}

tasks:
-name: Create user
debug: msg=“user name={{item.name}} {% if item.uid is defined %} ‘uid=’{{item.uid}}{% endif %}”
with_items: users

And here was the output from my testing…

TASK: [debug msg=“user name={{item.name}} {% if item.uid is defined %} ‘uid=’{{item.uid}}{% endif %}”] ***
ok: [10.246.72.170] => (item={‘name’: ‘test1’, ‘uid’: 1234}) => {
“item”: {
“name”: “test1”,
“uid”: 1234
},
“msg”: “user name=test1 'uid='1234”
}
ok: [10.246.72.170] => (item={‘name’: ‘test2’}) => {
“item”: {
“name”: “test2”
},
“msg”: "user name=test2 "
}

Of course you would want to replace the
debug: msg=“user name={{item.name}} {% if item.uid is defined %} ‘uid=’{{item.uid}}{% endif %}”

with
user: name={{item.name}} {% if item.uid is defined %} ‘uid=’{{item.uid}}{% endif %}

but I think that that should work… It is certainly worth a try… :slight_smile:

Adam

I had no idea that was possible, I’ve been using all sorts of kludges to achieve the same.

Me too, but I’m becoming more familar with Jinja2, and it really does make a lot of this simpler when you can get it to work the way that you want. I had a question about setting variables differently for different versions of RedHat, and Brian Green suggested a nice attempt with multiple variables, and setting the value of one variable to a different one selected dynamically… In the end my final (Jinja2) answer was derived from that concept but was much neater…

my group_vars/RedHat file now contains this…

syslog: “{% if ansible_distribution_version|truncate(1,true,‘’) >= ‘6’ %}rsyslog{% else %}syslog{% endif %}”

which sets the syslog variable correctly for RedHat depending on the distribution version. The same role can also handle AIX for me, and without having to create three similar tasks (two for RedHat variants and one for AIX).

My one bit of advice from stuff I have learned in the last week would be that as well as the Ansible documentation it is worth looking through the Jinja2 documentation particularly… http://jinja.pocoo.org/docs/templates/ and the Expressions, Built-in Filters and Built-in Tests sections. There are some additional filters documented in Ansible’s documentation, but the built-in ones are also useful.

I hope that this helps,

Adam

“syslog: “{% if ansible_distribution_version|truncate(1,true,‘’) >= ‘6’ %}rsyslog{% else %}syslog{% endif %}””

That’s super gross IMHO… I would not want to see that in anyone’s playbooks as that goes against much of the simplicity that Ansible seeks to achieve.

You may wish to read about the include_vars module in the module index, or group_by.

Please please please please please :slight_smile:

“syslog: “{% if ansible_distribution_version|truncate(1,true,‘’) >= ‘6’ %}rsyslog{% else %}syslog{% endif %}””

That’s super gross IMHO… I would not want to see that in anyone’s playbooks as that goes against much of the simplicity that Ansible seeks to achieve.

I hope that you are misunderstanding the context of this… If not then I am sorry to have offended you but this makes the entire task much simpler. I guess the question is where do you want the simplicity?

This is NOT in the playbook itself. This is the setting of a variable in one of the group_vars files.

There is a completely separate thread about this which has had one response from someone which didn’t work, but brought me to this point.

Here is a recap of the situation…

I have a task which sets up the base syslog configuration for both AIX and RedHat Linux. Initially this was set up for RedHat Linux 6 where the configuration file is /etc/rsyslog.conf and the service needing restarted is rsyslog. I then changed to a couple of variables because for AIX it’s /etc/syslog.conf and the service is syslogd.

At this point I have a dozen different set up tasks that work on RedHat 6 and AIX.

Then I moved to checking the RedHat tasks on RedHat 5 as we have a bunch of older systems I would like to roll into the same system, and here is where I ran into an issue. On RedHat 5 everything works identically EXCEPT that the syslog configuration file is /etc/syslog.conf and the service is syslog. So I had questions about how to set those two variables appropriately. Do I create a separate group_vars file for RedHat 5 and one for RedHat 6 for the difference of two variables? Do I split the tasks out with and make them conditional on OS and version? The simplest solution (it seems to me) is to have one task that is identical and set the variables appropriately, so that was my question. Someone suggested setting the variables by setting them to the value of other variables that have the OS version as part of their name, I couldn’t get that to work properly, and it still seemed somewhat inelegant. The solution I ended up with was the line above.

I have one group_vars file for AIX (no change needed) and one for RedHat. In the RedHat one I have that line of Jinja2 templating which sets the appropriate variable dependent on the OS version.

If you prefer I can rephrase it (so that it’s clearer it’s not talking about a module to

variable: “{% if ansible_distribution_version|truncate(1,true,‘’) >= ‘6’ %}value1{% else %}value2{% endif %}”

If you still don’t like that then can I ask how you would do this? include_vars will only include additional variable files and while I could do that in the task (or it’s accompanying handler) it would only make the task less simple. group_by has the same issue… This has the task as simple as possible and fully reusable while not creating more and more distinct OS groups.

I can send you the complete task, handler and accompanying group_vars files if you want to see everything… This is just an excerpt to show the pattern, but it is simple.

Adam

Oh I’m not offended, don’t worry about that. I’m just suggesting (especially for new comers on the list), that it’s better to try to simplify first than resort to Jinja in most cases – I like to use it to insert variables and occasionally loop in templates, but limit it to just that whenever possible.

The super easiest way to deal with things is actually to just have a group for each type of system and then use group variables.

I agree that that is an easy solution. I would do that if I could find a way to include variable files within variable files… that way I could have a generic RedHat file that was included in both the RedHat5 and RedHat6 files.

Then RedHat5 and RedHat6 groups could exist with the maximum of reusability.

If this facility exists then I apologise, but I couldn’t find it in the documentation.

Adam.

Hey Adam,

I think you could do this with a chain of “region” variables, but that seems like it could be cumbersome. Here is a piece of code I wrote that I’ve been playing with:
https://github.com/blueboxgroup/ursula/pull/224/files

Essentially it allows you to define some global defaults, and then overlay/augment them with the standard group_vars. I may change this to lexically read a directory for chains of defaults I care about, for instance:

01-global_defaults.yml
02-organization_defaults.yml

… then group_vars …

etc, etc.

Does this help?

Craig

This should be unnecessary.

groups of a lower tier will override the variables in the parent group.

You can also engage blending hashes together versus overriding them if you want, via a setting in ansible.cfg.

I should also point out this thread has now drifted off topic relative to it’s subject :slight_smile: