Overriding a template provided by a common role when invoked by a another role through dependency

Hello,

I have not done any tests yet, but what is currently the expected
behavior when a role that depends on a common role provides a template
with the same name as the common role? Is the task in the common role
(being invoked by the dependent role) executed with the template
provided by the common role or does the template provided by the
dependent role override the template in the common role?

"I have not done any tests yet, but what is currently the expected
behavior when a role that depends on a common role provides a template
with the same name as the common role? "

I’m having a hard time parsing this. It might be helpful to see a playbook example so we could see the ansible source.

In many cases, trying things is usually easiest :slight_smile:

Well, I did some tests and figured out how things work myself. :slight_smile: Nevertheless, I will try to describe it better and also go further to suggest a new feature relevant to this discussion. So, consider the following directory structure: roles/extended/meta/main.yml contains: And myplaybook.yml contains: There are two roles: the ‘base’ role and the ‘extended’ role. Assume that the ‘base’ role has a task that runs a template action with src=mytemplate.j2 (relative path). The ‘extended’ role provides a template with the same filename ‘mytemplate.j2’ but different content. To make things look simpler let’s assume that the ‘extended’ role has no tasks at all, so it only calls the ‘base’ role. Now that I have tested it myself, I can tell that the current behavior when the ‘base’ role is being invoked by the ‘extended’ role is to run the ‘base’ role’s template task using the ‘roles/base/templates/mytemplate.j2’ file, not the ‘roles/extended/templates/mytemplate.j2’ file provided by the ‘extended’ role. The ‘extended’ role is only calling the ‘base’ role as it is. It does not override the template and file paths. In other words, file and template relative paths are resolved to paths relative to the ‘base’ role, not to paths relative to ‘extended’ role. There is no ‘overriding’ behavior. So, I am thinking that making this “overriding” behavior possible would be really a useful feature, which would allow to write specialized roles that provide more specific templates/files to be used with tasks defined in more generic (base) roles. This behavior would come very handy, for example, if you see a nice role on Galaxy, but would need some changes in a couple of templates to adapt it to your needs. You would prefer not to copy the whole role because you would like to avoid to do the copy each time the “upstream” role is upgraded. Writing a dependent role that provides just the templates that need to be overridden would be much cleaner. The above behavior could be enabled with an ‘override_relative_paths: yes’ meta attribute in the dependency statement. For example, to enable this in the example above, the roles/extended/meta/main.yml file above would be written like this: What do you think?

Hey Ernest0x,

I don’t think the dependencies currently stand to mean “extend” in the OO sense. All it means is “I depend on some othe role, so make sure that role is run before me”. Each role is still an independent one. I believe this is a very clear and useful way of thinking of the dependecies of roles (i.e. create-some-nginx-configs-role depends on nginx-role - so nginx must be installed first).

That being said, I have recognized the need for “overriding” files/templates in roles. I tested like this:

global_roles_somewhere
/galaxy_role_x
/tasks
/main.yml
/templates
/some_template.j2

project_root
/ansible
/galaxy_role_x
/templates
/some_template.j2
/playbook.yml

In the playbook:

roles:

  • galaxy_role_x

Why not use variables for what you want. If you would like to use different templates for different servers using the same role, why not define a default template file in defaults directory of the role something like this:

`

foo_role/defaults/main.yml

Hi Ramon, What you suggest could be useful only if you just want to override some templates and/or files. But you may also want your ‘extend’ role to have some new tasks of its own, so that they are run after the invocation of the ‘base’ role. So that’ s where the OO-like approach I suggest applies. You write a new role that can override relative paths when invoking the base role and which may also add new functionality with tasks of its own. It’s not fully object oriented because you still cannot override tasks in a single pass (though this could be done with added syntax complexity), but you can have tasks in the ‘extend’ role that work to override changes made by tasks in the ‘base’ role. Above all, the approach I suggest is very simple and, by having an explicit option to enable this mode in the dependency statement, it’s totally harmless and backwards-compatible. The above applies also to the suggestion of Strahinja to use the same role with a parameter that changes the template. It is bound to the functionality of a single role and does not allow for new (extended) functionality. Think that you may want to have multiple roles that each one extends the same base role, but has it’s own set of overridden templates/files and new tasks.

Hi,

I understand the suggestion about adding more variables to your templates to be able to steer with variables; and I understand defaults - this is exactly how we try to build roles for galaxy. The problem lies with roles beyond your own control - i.e. roles by other people where you want to make a change in the template that was not made possible with variables (a pull-request on that role might also do the trick).

I think I would draw the line at overriding files/templates though. Or perhaps a whole task lists. If you want to add tasks to a role - i’d suggest adding a separate role to the playlist following the previous role you wanted to adjust - or adding pre_tasks/post_tasks to your playbook. Extending tasks sounds like a bridge too far…

for me, Ansible works because of it’s simplicity, and I “extending" roles would hurt that. BC-Break or not, it adds complexity to maintain.
My 2cts.

Hi,

I understand the suggestion about adding more variables to your templates to be able to steer with variables; and I understand defaults - this is exactly how we try to build roles for galaxy. The problem lies with roles beyond your own control - i.e. roles by other people where you want to make a change in the template that was not made possible with variables (a pull-request on that role might also do the trick).

The problem does not lie with roles beyond your own control only. You
may also have created and fully control a 'base' role with templates
that work for most cases, but you also have some special cases that
cannot be handled with these "base" templates. So you must provide
special templates that override these templates. One would say that
Strahinja's suggestion of passing the templates that need to be changed
as parameters to the role would be enough. The problem with that
approach has to do with the place where these "special" templates should
be kept and maintained. Since they are used to provide special,
non-basic content, they do not belong to the 'base' role. Instead they
should be part of another "special" role that depends on the "base" role
and provides special templates to make tasks in the "base" role render
the templates the way the "special" role wants.

I will give an example:

Let's say you have a "common" role that creates "/etc/hosts" from a
template which adds a line for the primary interface. Then, you also
have a 'cluster_node' role that needs some extra lines in "/etc/hosts"
for inter-cluster communication with hostnames. You cannot use a task
that modifies (e.g. with lineinfile) "/etc/hosts" after it having been
created by the "common" role, because the extra lines you want to add
can only be created with template logic. So you have two options here:

1) Create the special template and also create a copy of the same task
along with any accompanying tasks and handlers in the "cluster_node"
role tasks to re-render the file. This is unneeded and possibly
interruptive duplication of a task that should be run only once. Ugly.

2) Just create the special template and tell "common" role in the
dependency statement to first try to resolve its relative paths to the
paths that "cluster_node" provides. This is clean, without double
execution of a task which should be run only once.

I think I would draw the line at overriding files/templates though. Or perhaps a whole task lists. If you want to add tasks to a role - i'd suggest adding a separate role to the playlist following the previous role you wanted to adjust - or adding pre_tasks/post_tasks to your playbook. Extending tasks sounds like a bridge too far...

I agree. That' why I suggest an option only for overriding templates/files.

You are complicating things with all this, think of it a lot simpler. Regarding your example you can solve #1 in two ways:

  1. Don’t set hosts file with a template, but using lineinfile
  2. Don’t change the hosts file in common role, since from the looks of it, it is not “common” enough

You are assuming things here that they are not correct. 1. As I said before lineinfile does not help because it has not the power of a template engine and cannot handle (easily) more complex scenarios. 2. The common role works for ~90% of the hosts. So, it’s quite common. Also, one should think of the general need for a mode like the one I suggest. Perhaps, my example is not the best. Actually, I could “attack” my example myself and say “don’t use /etc/hosts, use DNS”. However, the point of my example was not to find the best solution for me, but to understand what I suggest and see how useful such a feature could be in general.

I can see your suggestion being useful, but for now, you just have to think of roles as group of similar tasks and that is all, don’t think of them as classes in an OO language. I don’t see that this will change any time soon.

Which is where you use the “dependency” part for real. Override the templates files using ramon’s technique and then use Dependency to call that task BEFORE you call your own tasks.

I’m not sure that adding Inheritance features to Ansible playbooks would be worthwhile, there are ways around it already using different templates (or more complicated templates). You can make it non-idempotent by using two tasks that modify the same file (say a template to put a base version in and lineinfile to modify it) but if you do that you probably want to look at what you are doing and see if there is a better way to get to where you want to be. If you are making big changes to a role then you might want to consider creating your own role to do what you want instead.

Adam

Actually, from users’ point, I think that it is not a big deal to make this possible. My suggestion does not involve rethinking about roles, as it does not bring any change in the current behavior that users need to learn about (if they don’t want to) and adapt their ansible stuff to. What I suggest is an explicit, simple syntax addition that would enable an OO-like behavior only for those who need it and only for the cases it is actually needed. Users don’t need to change their minds and try thinking of roles like classes in OOP, if they don’t want that functionality. Besides, even if they want that functionality, they don’t have to understand anything about classes and OOP, as the only understanding needed in this case is about templates/files that can be overridden. The “OO-like” wording is somewhat misleading, because it would be familiar only to those who already know about the OO paradigm.

Ramon's technique would work great in some cases but would not work if
you want reusability for the overriding behavior. Don't forget that with
roles coming into the game, it is the _role_ that gives you the means
for reusability. So, you have to make the overriding possible at the
_role_ level, not at the playbook level.