Ansible Inventory Not Ordering Alphabetically - ansible

I'm having the following Ansible hosts file:
# Contains the host mappings
[master]
m1.my-host.com ansible_host=192.168.0.100
[node]
n1.my-host.com ansible_host=192.168.0.102
n2.my-host.com ansible_host=192.168.0.103
[k3s_cluster:children]
master
node
[all:vars]
ansible_python_interpreter=/usr/bin/python3
In my playbook, I'm saying that I need an ordered execution like this:
---
- name: Setup hostname, users and groups
hosts: all
order: sorted
gather_facts: true
remote_user: myuser
become: yes
roles:
- {role: common, tags: ['host', 'system']}
But when I ran it, I get to see the following:
TASK [Gathering Facts] **********************************************************************************************************************************************************************************************
ok: [n1.my-host.com]
ok: [n2.my-host.com]
ok: [m1.my-host.com]
I would have rather expected it to be m1, n1 and then n2. Is there any idea as to why the sort order is not respected?

The sort order is being respected, but because Ansible executes tasks against multiple hosts in parallel, you see the results in the order in which the task completes.
If you were to serialize your task execution like this:
- hosts: all
order: sorted
serial: 1
gather_facts: false
tasks:
- ping:
You would see that the order of execution is consistently:
m1.my-host.com
n1.my-host.com
n2.my-host.com
Generally you don't want to serialize execution like that because it dramatically increases the runtime of your playbook, but if you have a very large number of hosts it sometimes makes sense to use a number > 1 to execute tasks in batches.

Related

Passing hostname to ansible playbook through extravars

I have to pass the host on which the Ansible command will be executed through extra vars.
I don't know in advance to which hosts the tasks will be applied to, and, therefore, my inventory file is currently missing the hosts: variable.
If I understood from the article "How to pass extra variables to an Ansible playbook" correctly, overwriting hosts is only possible by having already composed groups of hosts.
From the post Ansible issuing warning about localhost I gathered that referencing hosts to be managed in an Ansible inventory is a must, however, I still have doubts about it since the usage of extra vars was not mentioned in the given question.
So my question is: What can i do in order to make this playbook work?
- hosts: "{{ host }}"
tasks:
- name: KLIST COMMAND
command: klist
register: klist_result
- name: TEST COMMAND
ansible.builtin.shell: echo hi > /tmp/test_result.txt
... referencing hosts to be managed in an Ansible inventory is a must
Yes, that's the case. Regarding your question
What can I do in order to make this playbook work? (annot. without a "valid" inventory file)
you could try with the following workaround.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- add_host:
hostname: "{{ target_hosts }}"
group: dynamic
- hosts: dynamic
become: true
gather_facts: true
tasks:
- name: Show hostname
shell:
cmd: "hostname && who am i"
register: result
- name: Show result
debug:
var: result
A call with
ansible-playbook hosts.yml --extra-vars="target_hosts=test.example.com"
resulting into execution on
TASK [add_host] ***********
changed: [localhost]
PLAY [dynamic] ************
TASK [Show hostname] ******
changed: [test.example.com]
In any case it is recommended to check how to build your inventory.
Further Documentation
add_host module – Add a host (and alternatively a group) to the ansible-playbook in-memory inventory

Why does the ansible find module work differently in a role when on localhost?

I have a role, called newrole. It sits in my ansible directory:
ansible
- roles
- newrole
- tasks
main.yml
- templates
template.j2
playbook1.yml
My playbook is defined quite simply:
---
- hosts: host1
roles:
- newrole
And the main.yml in tasks for the newrole:
- name: Find templates in the role
find:
path: "templates"
pattern: "*.j2"
register: result
- debug:
msg: "{{ result }}"
I then invoke the playbook from the ansible directory: ansible-playbook playbook1.yml
This works as expected: it runs on host1 and matches the template.j2 file. However, if I change the playbook ever so slightly to run on localhost instead of host1 (and specify connection: local), things go off the rail. It says it can't find the path templates. If I instead put in the path from the ansible directory (that I am running from), e.g. roles/newrole/templates, then it matches the template.j2 file. However, I can't figure out why that difference is occurring.
Aren't roles supposed to be providing a insulation from the directory structure I am running them from?
Even more confusing is that even on localhost, the template module (ansible.builtin.template) will search for templates only in the templates directory of the newrole (as I would originally expect). Can someone explain this behavior difference?
Is there a way to get the find module in my example to start searching at the root of the role regardless of localhost or not invocation?
When Ansible runs on nodes, it does not ships the whole playbook, nor the whole role on the said node, it packages a Python script that get send to the node and executed there.
So, if you do not send your files over to a node, a task executed on a node is not going to find the said file.
Now, for what you do need, you don't need a find task, you need the fileglob lookup, as lookups do tend to execute on the controller, rather than on the nodes.
Here is an example of usage:
- debug:
var: lookup('fileglob', 'templates/*.j2')
If we go beyond your localised issue, then, you can also use a delegation to the controller, in order to achieve the same.
In your case, that would translate in:
- find:
path: "templates"
pattern: "*.j2"
delegate_to: localhost
run_once: true
register: result
Given the playbook:
- hosts: node1
gather_facts: no
tasks:
- debug:
var: lookup('fileglob', 'templates/*.j2')
- find:
path: "templates"
pattern: "*.j2"
delegate_to: localhost
run_once: true
register: result
- debug:
var: result.files.0.path
This yields:
PLAY [node1] ******************************************************************
TASK [debug] ******************************************************************
ok: [node1] =>
lookup('fileglob', 'templates/*.j2'): /absolute/path/to/templates/template.j2
TASK [find] *******************************************************************
ok: [node1 -> localhost]
TASK [debug] ******************************************************************
ok: [node1] =>
result.files.0.path: templates/template.j2

How to make ansible loop over hosts sequentially

I have set of tasks that i want to execute at set of hosts sequentially.
Example is below.
hosts: all
tasks:
- name: do some work
include_tasks: tasks_here.yml
loop: "{{ vars[play_hosts] }}"
ansible-playbook main.yml --limit myhosts
I expect that set of tasks would be executed at first host, then at second host etc... But in fact these tasks are being executed simulatineously at all hosts in "limit".
I suspect that it's happening because I use limit but i need it in my case.
So what I should I do?
By default, as specified here:
plays run with a linear strategy, in which all hosts will run each task before any host starts the next task.
You can use the strategy serial: 1 to execute the tasks on each host sequentially.
For example:
- hosts: all
serial: 1
tasks:
...

Ansible - Play with hosts in order I desire

When running a playbook Ansible randomly sets a node as first, second and third.
ok: [node-p02]
ok: [node-p03]
ok: [node-p01]
Q: How can I configure Ansible to let it execute with the hosts in sorted order? Example:
ok: [node-p01]
ok: [node-p02]
ok: [node-p03]
Serial: 1 is not an option, since it slows down the play, and my playbook is meant for 3 nodes in a single play.
Applicable for Ansible 2.4 and higher:
This is now the default behaviour, ansible will play the hosts in the order they were mentioned in the inventory file. Ansible also provides a few built in ways you can control it with order:
- hosts: all
order: sorted
gather_facts: False
tasks:
- debug:
var: inventory_hostname
Possible values of order are:
inventory: The default. The order is ‘as provided’ by the inventory
reverse_inventory: As the name implies, this reverses the order ‘as provided’ by the inventory
sorted: Hosts are alphabetically sorted by name
reverse_sorted: Hosts are sorted by name in reverse alphabetical order
shuffle: Hosts are randomly ordered each run
Source: https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html#hosts-and-users
Edit: The best solution is in dubes' answer but this one gives you more freedom in case specific operations have to be applied to the host list, or you can't use Ansible 2.4.
Since Ansible 2.2 you can use ansible_play_hosts or ansible_play_batch and sort it:
---
- hosts: "{{ ansible_play_hosts | sort() }}"
From ansible doc:
ansible_play_hosts is the full list of all hosts still active in the current play.
ansible_play_batch is available as a list of hostnames that are in scope for the current ‘batch’ of the play. The batch size is defined by serial, when not set it is equivalent to the whole play (making it the same as ansible_play_hosts).
I figured that another possibility is to use the exact hostnames in the hosts as a list, instead of a group. However, the other answers are more compliant to Ansible methods.
---
- hosts:
- node-p01
- node-p02
- node-p03

How to run Ansible playbook tasks in order of tags

I want to run tasks in ansible playbook in order of tags given in --tags
My ansible playbook
---
- hosts: all
remote_user: root
vars:
file_path: '{{filename}}'
tasks:
- name: Delete user
user:
name: "{{username}}"
state: absent
remove: yes
tags:
- delete_user
- name: Create user
user:
name: "{{username}}"
shell: /bin/bash
groups: "{{groupname}}"
password: "{{ password |password_hash('sha512') }}"
tags:
- create_user
- name: Add ssh key
authorized_key:
user: "{{username}}"
key: "{{lookup('file', 'file_path')}}"
exclusive: yes
tags:
- add_ssh_key
Run Ansible
ansible-playbook createuser.yml --extra-vars "username=hello password=helloworld groupname=something filename=/path/to/filename" --tags=create_user,add_ssh_key,delete_user
Expected Output
TASK: [Create user] ***********************************************************
changed: [ip address]
TASK: [Add ssh key] ***********************************************************
changed: [ip address]
TASK: [Delete user] ***********************************************************
ok: [ip address]
Output Comes
TASK: [Delete user] ***********************************************************
ok: [ip address]
TASK: [Create user] ***********************************************************
changed: [ip address]
TASK: [Add ssh key] ***********************************************************
changed: [ip address]
Order of tags given
create_user,add_ssh_key,delete_user
But Executed in order
delete_user,create_user,add_ssh_key,
That's not what tags are for and there is no way to do that within Ansible. Tasks always are execute in the order they have been defined in the tasks file(s) and/or in order roles have been added to a playbook/play.
If you want to target specific tasks in order you could call the playbook multiple times with a single tag applied.
ansible-playbook ... --tags=create_user
ansible-playbook ... --tags=add_ssh_key
ansible-playbook ... --tags=delete_user
You could write a simple bash script to automate that. (That's what one usually ends up with anyway when you have a more complex setup and have to deal with multiple tags)
Generally, all tasks are executed in the order they are in the playbook if no tags are given. I think because your tags cover all tasks in the playbook, they are just executed in the order as they would without any tags. So, a solution could be to rearrange the tasks in the playbook.
Example playbook:
- hosts: localhost
tasks:
- name: Third task
shell:
tags: "third"
- name: Second task
shell:
tags: "second"
- name: First task
shell:
tags: "first"
First run:
ansible-playbook -c 'local' test.yml --tags=first,second,third --list-tasks
Output:
playbook: test.yml
play #1 (localhost): localhost TAGS: []
tasks:
Third task TAGS: [third]
Second task TAGS: [second]
First task TAGS: [first]
Second run:
ansible-playbook -c 'local' test.yml --tags=third,first,second --list-tasks
Output(didn't change):
playbook: test.yml
play #1 (localhost): localhost TAGS: []
tasks:
Third task TAGS: [third]
Second task TAGS: [second]
First task TAGS: [first]
Now let's rearrange tasks in the playbook:
- hosts: localhost
tasks:
- name: First task
shell:
tags: "first"
- name: Second task
shell:
tags: "second"
- name: Third task
shell:
tags: "third"
Final run:
ansible-playbook -c 'local' test.yml --tags=third,first,second --list-tasks
Output (proper order):
playbook: test.yml
play #1 (localhost): localhost TAGS: []
tasks:
First task TAGS: [first]
Second task TAGS: [second]
Third task TAGS: [third]
Generally, all tasks are executed in the order they are in the playbook if no tags are given.
Now tasks are executed in ALPHABETICAL order in my environment. (ansible 2.6.0, not latest)
I have two answers:
Call the playbook multiple times with a single tag
Set up the tasks with wait_for (doc here) so that the second task waits until a file (or something) exists that the first task delivers.

Resources