In my playbook I have:
roles:
- role: role_1
- role: role_2
In my roles directory structure I have defined vars with different values under role_1 and role_2
When invoking the playbook, I am trying to ask it to only consider the role I am interested in at the moment, by using limit:
ansible-playbook -i ./hosts.yml myplaybook.yml --limit role_1
What I expect:
It will disregard the variable values under role_2, because I asked it to limit to role_1
What I get:
role_2 variables values override role_1, because role_2 is lexicographically later. ugh!
What else I tried:
I tried to use tags:
roles:
- role: role_1
tags: [ role_1_tag ]
- role: role_2
tags: [ role_2_tag ]
Result is still the undesired
Edit: After following the first answer
The syntax for include_role as given here (and in the documentation) does not work without modifications. In the end I made this:
tasks:
- name: include role_1
include_role:
name: role1
apply:
tags:
- role_1_tag
tags: always
- name: include role_2
include_role:
name: role_2
apply:
tags:
- role_2_tag
tags: always
But even then, When I run it, nothing is executed. I run it like this:
ansible-playbook -i ./hosts.yml % --limit role_1 -t role_1_tag
Trying another way:
tags: always
tasks:
- name: include role_1
include_role:
name: role1
apply:
tags:
- role_1_tag
- name: include role_2
include_role:
name: role_2
apply:
tags:
- role_2_tag
Ansible tries to proceed with the other tasks, but without accessing any of the vars under the role, resulting in variable not found error
Since I put the tags: outside of tasks I guessed the tags need to be mentioned for every task. Even doing that, I got the same result, which is:
fatal: [host_group]: FAILED! => {"msg": "'my_vars' is undefined"}
Edit2: Using public: yes seems to get Ansible to read the vars, however, again, it reads them all, and --limit has no effect
Q: "Only consider the role I am interested in at the moment."
A: Use include_role. For example
- include_role:
name: role_1
- include_role:
name: role_2
By default, the parameter public is no
This option dictates whether the role's vars and defaults are exposed to the playbook. If set to yes the variables will be available to tasks following the include_role task. This functionality differs from standard variable exposure for roles listed under the roles header or import_role as they are exposed at playbook parsing time, and available to earlier roles and tasks as well.
Notes
--limit select hosts to an additional pattern.
apply tags is not properly described in the documentation. See
include_role with apply tags does not work #52063
Related
The calling playbook has:
- hosts: ssh_servers
tasks:
- import_tasks: create_files.yml
Then, in create_files.yml, I'd like to run some tasks on hosts other than ssh_servers, such as:
- Hosts: other_servers
tasks:
- file:
I get: ERROR! conflicting action statements: hosts, tasks .
Is this because I'm trying to run against hosts that were never included in the calling task ?
Is there a way to accomplish this other than in the calling playbook have:
- hosts:
- ssh_servers
- other_servers
tasks:
- import_tasks: create_files.yml
Thank you.
Is this because I'm trying to run against hosts that were never included in the calling task ? Is there a way to accomplish this other than in the calling playbook
I believe the answer is yes, although it'll be weird and could cause subsequent folks who interact with your playbook some confusion
given a hypothetical create_files.yml of:
- name: create /tmp/hello_world on hosts "not_known_at_launch_time"
file:
path: /tmp/hello_world
state: present
delegate_to: '{{ item }}'
with_items: '{{ groups["not_known_at_launch_time"] }}'
then the glue needed to bridge them together is the dynamic creation of a group and that delegate_to: keyword
- hosts: ssh_hosts
tasks:
- add_host:
groups: not_known_at_launch_time
name: secret-host-0
ansible_host: 192.168.1.1 # or whatever
# ... other hostvars here ...
- include_tasks: create_files.yml
it may be possible to combine those inside create_files.yml, via some shared vars: that say which host-and-ip should be added to the magic group name, which also has the benefit of keeping the magic group name localized to the file that consumes it.
BE AWARE, I did actually test this, but not extensively, so there may be some weird things such as the need to run_once: yes on them to keep the tasks from being run groups.ssh_hosts|length times or similar stuff
As Vladimir correctly pointed out, what you actually want to happen is to make that relationship formal:
- hosts: ssh_hosts
tasks:
... whatever tasks you had before
- add_host: ... as before ...
- hosts: anonymous_group_name_goes_here
tasks:
- include_tasks: create_files.yml
- hosts: ssh_hosts
tasks:
- debug:
msg: and now you are back to the ssh_hosts to pick up what they were supposed to be doing when you stopped to post on SO
I am using ansible 2.9.4. My goal is to deny run some playbook on all nodes by accident or without tags. This is my app.yaml:
- hosts: all
remote_user: root
vars:
server_domain: mydomain.com
project_name: project
tasks:
- name: checking limit arg
fail:
msg: "you must use -l or --limit - when you really want to use all hosts, use -l 'all'"
when: ansible_limit is not defined
run_once: true
- name: "suppress message if tag given"
set_fact: suppress_message=yes
tags: dev,test,prod
- name: "message"
fail:
msg: "You didn't choose environment 'dev,test,prod'"
when: suppress_message is not defined
roles:
- testrole
The problem is, when I am not use --limit option, the role testrole run successfully and then the error message occurs - too late if I already run it on all nodes.
Even when I specify tags --tags "mytag" it will not check if limit was specified.
By similar way I would like to force to use tags, so everytime when you run playbook, you should specify environment tag (dev, test, prod) - e.g ssh keys for different environments, configuration files, etc...
What I would expect from this, that If I not specified tag dev, test or prod, the suppress_message would not be specified so next task with name message would fail with message "You didn't choose environement".
The fact is, If I not specified any tag:
- supress message have state OK
- message is skipped
If I specify valid tag --tags "dev":
- supress message have state OK
- message is not even mentioned (I would expect skipping)
If I specify "invalid tag" --tags "dev123":
- supress message is not mentioned
- message is not mentioned
The solution for limit could be replace - hosts: all with - hosts: randomtext so when no limit is specified there will be no match but what about tags/environments? I am quiet lost about how ansible works. The logic about this decisions what will run is quiet chaotic from this example.
Below is an example playbook that should achieve what you need to do.
Hosts are defined in a myhosts variable on the command line, the first task will abort the play if this variable is not set
Through use of the two “special tags”, always and never, we can ensure that:
the above check always runs, and
the inclusion of your testrole never runs — unless dev, test, or prod tags are explicitly specified
There’s a helpful message before the inclusion of the testrole, so the user is not left confused if the play exits silently because of unset tags
- hosts: '{{ myhosts | default("localhost") }}'
tasks:
- name: Fail if hosts are not defined
run_once: true
fail:
msg: >
You must define hosts in the myhosts variable,
e.g. `-e myhosts=foo.example.com` on the command line
when: myhosts is undefined
tags:
- always
- name: Helpful message
run_once: true
debug:
msg: >
This playbook does nothing unless the environment is specified with
the `--tags` option on the command line (dev, test, or prod).
tags:
- always
- name: Include role only when tags are specified
include_role:
name: testrole
tags:
- never
- dev
- test
- prod
This would be then executed like so:
$ ansible-playbook app.yaml --extra-vars myhosts=foo.example.com --tags dev
Change tasks to pre_tasks.
The order is pre_tasks, roles, tasks, post_tasks.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html
Playbook ordering is irrelevant.
Another option is add a task to include the role. So change
roles:
- testrole
to
- include_role:
name: testrole
Try using tags like this -
tags:
- dev,test,prod
I have a playbook with this structure:
---
- hosts: foo-servers
roles:
- foo_setup
become: yes
tags: tweaks
- hosts: bar-servers
roles:
- bar_setup
become: yes
tags: tweaks
[a few more server groups with a similar pattern]
I have a somewhat similar feature to deploy in all servers but each server group has it's own small differences, so I need to keep separate roles for each group.
I also want to run just a select group of tasks from each role, the ones tagged 'tweaks', in all hosts.
And all hosts should be run with raised privileges, but that is not true for all playbooks, so I want that setting to apply just to this playbook (no global vars).
I would like to move all the repeated parameters - become: yes and tags: tweaks outside of host: plays where they will be indicated to apply to all roles bellow. Something to the effect of
--
- all_hosts_this_playbook:
become: yes
tags: tweaks
- hosts: foo-servers
roles:
- foo_setup
- hosts: bar-servers
roles:
- bar_setup
I suppose this is possible in the command line. Like ansible-playbook setup_tweaks.yml --tags "tweak" --become? But is there a playbook equivalent? I'd rather have these in the file than in the command line, where I often forget to add stuff.
And looping doesn't work...
ERROR! 'loop' is not a valid attribute for a Play
- name: Make tweaks in many servers
become: yes
tags: tweaks
hosts: "{{ item.host }}"
roles:
- "{{ item.role }}"
loop:
- { host: 'foo-servers', role: 'foo_setup' }
- { host: 'bar-servers', role: 'bar_setup' }
I also want to add post_tasks: to be run in all servers (maybe they also need to be tagged?):
post_tasks_all_hosts:
- name: Upgrade system
apt:
autoremove: yes
autoclean: yes
update_cache: yes
upgrade: dist
tags: tweaks
- name: Reboot
shell: sleep 2 && reboot
async: 3
poll: 0
tags: tweaks
Is it possible to define playbook-wide pre_tasks or post_tasks?
Here Ansible: How to declare global variable within playbook? it is indicated that one 'cannot define a variable accessible on a playbook level', but in my case it's not variables - it's task parameters and post_tasks:.
Maybe the parameters and the 'pre/post tasks' are different problems with different solutions, but I decided to ask in the same place because they both fall on the same category of parameters that I'd like to set for the whole playbook, outside of host: plays.
Q: "I suppose this is possible in the command line. Like ansible-playbook setup_tweaks.yml --tags "tweak" --become? But is there a playbook equivalent?"
A: No. There is no such playbook equivalent.
Isn't this is a misunderstanding of the command-line option --tags
only run plays and tasks tagged with these values
versus
tag inheritance ?
Adding tags: to a play, or to statically imported tasks and roles, adds those tags to all of the contained tasks...When you apply tags: attributes to structures other than tasks, Ansible processes the tag attribute to apply ONLY to the tasks they contain. Applying tags anywhere other than tasks is just a convenience so you don’t have to tag tasks individually.
Details
In the play below tag "tweaks" is added to all of the contained tasks
- hosts: foo-servers
roles:
- foo_setup
tags: tweaks
The command below selects only tasks tagged "tweaks"
ansible-playbook setup_tweaks.yml --tags "tweak"
Q: "Is it possible to define playbook-wide pre_tasks or post_tasks?"
A: No. It's not possible. The scope of pre/post_tasks is the play.
I have the following set up for Ansible, and I would like to parameterize a filter that will loop, and filter out specific hosts.
- name: run on hosts
hosts: "{{ item }}"
roles:
- directory/role-name
with_items:
- us-east-1a
- us-east-1b
- us-east-1c
The result would be that the role called role-name would be first run on us-east-1a hosts, then us-east-1b... etc.
The above simple errors out with
ERROR! 'with_items' is not a valid attribute for a Play
Is there a way to accomplish what I am trying to do, which is chunking my host list into groups, and running the same role against them, one at a time?
The following achieves the result I am looking for, but is clunky, and not dynamic in length.
- name: run on us-east-1a
hosts: "us-east-1a"
roles:
- my-role
- name: run on us-east-1b
hosts: "us-east-1b"
roles:
- my-role
- name: run on us-east-1c
hosts: "us-east-1c"
roles:
- my-role
I think the only way to (1) have a common code and (2) serialise play execution per group of hosts (with targets inside a group running in parallel) would be to split your playbook into two:
playbook-main.yml
---
- import_playbook: playbook-sub.yml
vars:
host_group_to_run: us-east-1a
- import_playbook: playbook-sub.yml
vars:
host_group_to_run: us-east-1b
- import_playbook: playbook-sub.yml
vars:
host_group_to_run: us-east-1c
playbook-sub.yml
- hosts: "{{ host_group_to_run }}"
roles:
- my-role
# other common code
If you wanted to serialise per host, then there is a serial declaration that might be used in conjunction with this suggestion, but despite your comments and edit, it's unclear because once you refer to us-east-1a as a "host" in singular form, other times as a "group of hosts" or an "availability zone".
Will host patterns do the job?:
- name: run on us-east-1a
hosts: us-east-1a,us-east-1b,us-east-1c
roles:
- my-role
Update: #techraf has opened my eyes with his comment – host pattern alone will not do the job.
It will just concatenate all hosts from all groups.
But in a predictable way, which in some cases can be used to iterate hosts in every group separately.
See this answer for details.
I'd like to pass a variable to an included Ansible playbook as follows:
---
- hosts: localhost
connection: local
vars:
my_group: foo
- include: site.yml hosts={{ my_group }}
Then, in site.yml...
---
- hosts: "{{ hosts }}"
...
Unfortunately, I get an error saying that my_group is undefined in site.yml. Ansible docs do say that:
Note that you cannot do variable substitution when including one playbook inside another.
Is this my case? Is there a way around it?
You can use this syntax, but my_group has to be defined at the global level. Now it's local to the first play - it's even clear from the indentation.
You can confirm this by running your playbook with --extra-vars my_group=foo.
But generally what you seem to want to achieve is done using in-memory inventory files and add_host module. Take this as an example:
- hosts: localhost
gather_facts: no
vars:
target_host: foo
some_other_variable: bar
tasks:
- add_host:
name: "{{ target_host }}"
groups: dynamically_created_hosts
some_other_variable: "{{ some_other_variable }}"
- include: site.yml
with site.yml:
---
- hosts: dynamically_created_hosts
tasks:
- debug:
var: some_other_variable
I added some_other_variable to answer the question from your comment "how do I make a variable globally available from inside a play". It's not global, but it's passed to another play as a "hostvar".
From what I see (and I can't explain why) in Ansible >=2.1.1.0, there must be an inventory file specified for the dynamic in-memory inventory to work. In older versions it worked with Ansible executed ad hoc, without an inventory file, but now you must run ansible-playbook with -i inventory_file or have an inventory file defined through ansible.cfg.