Execute ansible task when variable has changed - ansible

I have a variables file that defines a number of users:
my_users:
admin:
- admin1
- admin2
user:
- bob
I have a task that adds these users to mongodb. I don't want to execute the task every time but only if the users changed. I tried the following:
- name: add users
command: "<add users>"
when: my_users|changed
However, this does not work as intended. If I add a user, the task is still skipped.
How can I execute a task only if a variable has changed?

Normally you don't do it the way you are trying. Instead, ansible must create the users if they don't already exist. The way to do it with one user is like this:
- name: Check if user george exists
command: "<return a status code of zero if user george exists>"
register: check_george_exists
ignore_errors: yes
always_run: yes
changed_when: False
- name: Create user george
command: "<create user george>"
when: check_george_exists.rc == 0
To do it for many users, see Register variables in with_items loop in Ansible playbook.

Related

Give Input from Text file to Ansible Playbook

In my ansible playbook I want to get the input from text file and perform some set of operations.
I have 10 user names in the text file, then the play has to pick first name from the text file and do few tasks. Once done for the first user and the play has to pick it for second user and so on.
I wrote play for a single user. Kindly help me or give some sample play for this kind of scenario.
You should be using roles and manage everything from inventory.
But however, simplest way; Create a file, and store your user names in a variable that way
users:
- user1
- user2
- user3
- user4
- user5
At the beginning of your playbook, include that file
- hosts: whatever
become: yes
vars_file:
- <<path_to_your_var_file>>
Then in the task you can use that users variable you included from the variable file
tasks:
- name: create 10 users
user:
name: "{{ item }}"
state: present
with_items:
- "{{ users }}"
Ansible will import the users variable from your var file, and loop n times with number of users you have.

How to end a whole play while using serial (Ansible 2.9)

Using Ansible 2.9, how do you end a playbook while also using serial?
When I run the following code that is a rolling upgrade, it executes the prompt for each target system. When using serial, run_once only seems to work for the current target. I only want it to execute once.
- hosts: all
become: yes
serial: 1
handlers:
- include: handlers/main.yml
pre_tasks:
- name: Populate service facts
service_facts:
- name: Prompt
pause:
prompt: "NOTE: You are running a dangerous playbook. Do you want to continue? (yes/no)"
register: confirm_input
run_once: True
- name: end play if user didn't enter yes
meta: end_play
when: confirm_input.user_input | default ("yes") =="no"
run_once: True
tasks:
(other stuff)
run_once will be executed at each serial execution in the play. That means, if you choose serial = 1, it will be asked to confirm as many times as the quantity of targets on the play.
Check Ansible docs: https://docs.ansible.com/ansible/latest/user_guide/playbooks_strategies.html#running-on-a-single-machine-with-run-once
When used together with serial, tasks marked as run_once will be run
on one host in each serial batch. If the task must run only once
regardless of serial mode, use when: inventory_hostname ==
ansible_play_hosts_all[0] construct.
to avoid default ansible behaviour and do what you want, follow the doc and use when: inventory_hostname == ansible_play_hosts_all[0] on the prompt task.

Is there a way to assign a user in the active directory to multiple groups with ansible?

I created an ansible-playbook which aims to add a user to a group in Active Directory via Ansible with code as shown below:
# addmembertogroup.yaml
# Skip task when 'group' or 'username' is undefined
# Show message when 'group' or 'username' doesn't exist
---
- hosts: brc.testlab.com
gather_facts: no
tasks:
- name: "Add Member to Group"
block:
- name: "Add Member to Group"
community.windows.win_domain_group_membership:
name: "{{group}}"
members: "{{username}}"
state: present
when: (group is defined) and (username is defined)
rescue:
- name: Print when error
debug:
msg: Username / Group not exist
Where the playbook is run with the following command:
Ansible-playbook –i hosts addmembertogroup.yaml –e group=DNSAdmins –e username=Cahbayu
Based on the existing ansible playbook and command, I managed to add a user with the name Cahbayu into the DNSAdmins group. However, I want the user to be in multiple groups in one command. For example, I want to add user Cahbayu into the group DNSAdmins, Backup Operators, Remote Desktop Users. I've made the group parameters into a list like the following but the result still fail:
---
- hosts: brc.testlab.com
gather_facts: no
tasks:
- name: "Add Member to Group"
block:
- name: "Add Member to Group"
community.windows.win_domain_group_membership:
name:
- "{{group}}"
members: "{{username}}"
state: present
when: (group is defined) and (username is defined)
rescue:
- name: Print when error
debug:
msg: Username / Group not exist
And here's the result:
[Failed to Add User to Multiple Groups][1]
Thus, my question is, is there a way to enter a user into multiple groups in Active Directory with Ansible? Thank you.
(Edited)
I managed to get user into multiple groups using ‘loop’ according to the answer provided.
I think you can manage to do so if you define the groups in a variable or a list and loop through the list that you want your user to be added in, check this this link it could help you manage doing it since your playbook is working and needs only a well defined loop
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html[loops in ansible]1

Execute an Ansible Playbook periodically

I have an Ansible Playbook myPlayBook.yml. It has two plays and each plays has one or more tasks.
I use the command as follows to run my playbook:
ansible-playbook myPlayBook.yml
So every thing is OK and my tasks are executed successfully. Now, after the first running I want to run my playbook again from the first play (similar to the first running but automatically). Is there a way to do that?
(I saw that it can be done for a specific task or play with include or include_tasks, but what about for a playbook?)
Now, after the first running I want to run my playbook again from the
first play (similar to the first running but automatically).
You can form your tasks as role and execute it via include_role from playbook multiple times:
- name: 'Include role'
include_role:
name: '{{ app_role}}'
- debug:
msg: "{{ inventory_hostname }}"
Create role by the following path roles/inventory_role that consist of one task. From playbook you can execute role just call it multiple times:
- name: 'Include role inventory_role'
include_role:
name: 'inventory_role'
- name: 'Include role inventory_role'
include_role:
name: 'inventory_role'
Or you can use something like this:
- { role: 'inventory_role' }
- { role: 'inventory_role' }
NOTE:
Ansible will only allow a role to execute once, even if defined
multiple times, if the parameters defined on the role are not
different for each definition.
See for more details: Role Duplication and Execution.
Execute an Ansible Playbook periodically
Ansible Tower have job with scheduled launch type.

Ansible include task only if file exists

I'm trying to include a file only if it exists. This allows for custom "tasks/roles" between existing "tasks/roles" if needed by the user of my role. I found this:
- include: ...
when: condition
But the Ansible docs state that:
"All the tasks get evaluated, but the conditional is applied to each and every task" - http://docs.ansible.com/playbooks_conditionals.html#applying-when-to-roles-and-includes
So
- stat: path=/home/user/optional/file.yml
register: optional_file
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
Will fail if the file being included doesn't exist. I guess there might be another mechanism for allowing a user to add tasks to an existing recipe. I can't let the user to add a role after mine, because they wouldn't have control of the order: their role will be executed after mine.
The with_first_found conditional can accomplish this without a stat or local_action. This conditional will go through a list of local files and execute the task with item set to the path of the first file that exists.
Including skip: true on the with_first_found options will prevent it from failing if the file does not exist.
Example:
- hosts: localhost
connection: local
gather_facts: false
tasks:
- include: "{{ item }}"
with_first_found:
- files:
- /home/user/optional/file.yml
skip: true
Thanks all for your help! I'm aswering my own question after finally trying all responses and my own question's code back in today's Ansible: ansible 2.0.1.0
My original code seems to work now, except the optional file I was looking was in my local machine, so I had to run stat through local_action and set become: no for that particular tasks, so ansible wouldn't attempt to do sudo in my local machine and error with: "sudo: a password is required\n"
- local_action: stat path=/home/user/optional/file.yml
register: optional_file
become: no
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
In Ansible 2.5 and above, it can be done using tests like this:
- include: /home/user/optional/file.yml
when: "'/home/user/optional/file.yml' is file"
More details: https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html#testing-paths
I using something similar but for the file module and what did the trick for me is to check for the variable definition, try something like:
when: optional_file.stat.exists is defined and optional_file.stat.exists
the task will run only when the variable exists.
If I am not wrong, you want to continue the playbook even the when statement false?
If so, please add this line after when:
ignore_errors: True
So your tasks will be look like this:
- stat: path=/home/user/optional/file.yml
register: optional_file
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
ignore_errors: True
Please let me know, if I understand your question correctly, or can help further. Thanks
I could spend time here to bash ansible's error handling provisions, but in short, you are right and you can't use stat module for this purpose due to stated reasons.
Simplest solution for most ansible problems is to do it outside ansible. E.g.
- shell: test -f /home/user/optional/file.yml # or use -r if you're too particular.
register: optional_file
failed_when: False
- include: /home/user/optional/file.yml
when: optional_file.rc == 0
- debug: msg="/home/user/optional/file.yml did not exist and was not included"
when: optional_file.rc != 0
* failed_when added to avoid host getting excluded from further tasks when the file doesn't exist.
Using ansible-2.1.0, I'm able to use snippets like this in my playbook:
- hosts: all
gather_facts: false
tasks:
- name: Determine if local playbook exists
local_action: stat path=local.yml
register: st
- include: local.yml
when: st.stat.exists
I get no errors/failures when local.yml does not exist, and the playbook is executed (as a playbook, meaning it starts with the hosts: line, etc.)
You can do the same at the task level instead with similar code.
Using stat appears to work correctly.
There's also the option to use a Jinja2 filter for that:
- set_fact: optional_file="/home/user/optional/file.yml"
- include: ....
when: optional_file|exists
The best option I have come up with so far is this:
- include: "{{ hook_variable | default(lookup('pipe', 'pwd') ~ '/hooks/empty.yml') }}"
It's not exactly an "if-exists", but it gives users of your role the same result. Create a few variables within your role and a default empty file. The Jinja filters "default" and "lookup" take care of falling back on the empty file in case the variable was not set.
For convenience, a user could use the {{ playbook_dir }} variable for setting the paths:
hook_variable: "{{ playbook_dir }}/hooks/tasks-file.yml"

Resources