Single Ansible task for become yes and no - ansible

I have a lot of tasks running for the user and superuser. Most of them load configs via the template module. Question:
Is it possible to complete somehow the same task for both a regular user and a superuser? Those. do not do two tasks, one with become: no, and the other with yes.
Are there any conditions for template to distinguish become, so that at least one template is used.
I have quite a lot of experience with ansible and have read a lot of documentation and googled. Probably what I need is simply impossible, but suddenly someone came up with a crutch.
Thanks.

Ansible evaluates the become statement only once for the task, so if
you were to write a task like this:
- become: "{{ item|bool }}"
command: id
register: result
loop:
- false
- true
It would always run with become: false; if you were to switch the
order of the items in the loop, it would always run with become: true. However, the become_user setting can be set from a loop
variable, so you can do something like this:
- become: true
become_user: "{{ item }}"
command: id
register: result
loop:
- root
- "{{ ansible_user_id }}"
- debug:
var: result.results|map(attribute='stdout')|list
Running these tasks on my local system results in the following output:
TASK [command] *****************************************************************
changed: [localhost] => (item=root)
changed: [localhost] => (item=lars)
TASK [debug] *******************************************************************
ok: [localhost] => {
"result.results|map(attribute='stdout')|list": [
"uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023",
"uid=1000(lars) gid=1000(lars) groups=1000(lars),10(wheel),18(dialout),983(libvirt) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023"
]
}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Related

Add host during runtime execution w/o starting new play

Is it possible during Ansible execution to add another host in the play, without starting a new play?
I am aware of the add_host module, but that requires the start of a new play to add the host, which is undesired.
No. By design, it's not possible to add hosts to 'in-flight play'. Quoting from the Summary of Ansible bug #59401:
By design, the in-flight play will not start running tasks on newly-added hosts, but it will stop running tasks on hosts that have disappeared. Newly-created hosts from an inventory refresh are immediately visible in ansible_play_hosts, even though they're not executing.
Notes
The bug claims refresh_inventory and add_host should have the same effects.
One might expect that the option refresh_inventory of the module meta does the job. The scenario would be:
Start a play
Modify the source of the inventory
Run - meta: refresh_inventory
Unfortunately, the example of the INI file below shows that this doesn't work. The host host03 is added to the inventory and to the list ansible_play_hosts_all as well. But, then, the following task debug doesn't run at this host. Play recap doesn't include this host either.
shell> cat hosts
[test]
host01
host02
The playbook below
shell> cat playbook.yml
- hosts: test
gather_facts: false
tasks:
- debug:
var: ansible_play_hosts_all
run_once: true
- community.general.ini_file:
path: hosts
section: test
option: "{{ item.host }}"
state: "{{ item.state }}"
allow_no_value: true
loop:
- {host: host03, state: present}
run_once: true
delegate_to: localhost
- meta: refresh_inventory
- debug:
var: ansible_play_hosts_all
run_once: true
- debug:
var: inventory_hostname
gives
shell> ansible-playbook -i hosts playbook.yml
PLAY [test] **********************************************************************************
TASK [debug] *********************************************************************************
ok: [host01] =>
ansible_play_hosts_all:
- host01
- host02
TASK [community.general.ini_file] ************************************************************
changed: [host01 -> localhost] => (item={'host': 'host03', 'state': 'present'})
TASK [meta] **********************************************************************************
TASK [debug] *********************************************************************************
ok: [host01] =>
ansible_play_hosts_all:
- host01
- host02
- host03
TASK [debug] *********************************************************************************
ok: [host01] =>
inventory_hostname: host01
ok: [host02] =>
inventory_hostname: host02
PLAY RECAP ***********************************************************************************
host01 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host02 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Run tasks on hosts defined in yaml file instead of inventory using ansible

I'm new to Ansible and trying to fetch hosts from below config.yaml file instead of inventory, to run tasks on those hosts. How can I do this in main playbook?
service:
web-app:
common:
tomcat:
port: 80
hosts:
all:
- abc.com
- pqr.com
Is there a way to access abc.com and pqr.com in my playbook, if I have to run certain tasks on those servers?
The base: loading the data
The base ansible functions needed for the following examples are:
The file lookup plugin to load the content of a file present on the controller.
The from_yaml filter to read the file content as yaml formatted data
For both examples below, I added your above yaml example (after fixing the indentation issues) to files/service_config.yml. Simply change the name of the file if it is in a files subdir, or use the full path to the file if it is outside of your project.
Combining the above, you can get your list of hosts with the following jinja2 expression.
{{ (lookup('file', 'service_config.yml') | from_yaml).service.hosts.all }}
Note: if your custom yaml file is not present on your controller, you will firts need to get the data locally by using the slurp or fetch modules
Use in memory inventory
In this example, I create a dynamic group custom_group running a add_hosttask on a play targeted to localhost and later target that custom group in the next play. This is probably the best option if you have a large set of tasks to run on those hosts.
---
- name: Prepare environment
hosts: localhost
gather_facts: false
vars:
# Replace with full path to actual file
# if this one is not in your 'files' subdir
my_config_file: service_config.yml
my_custom_hosts: "{{ (lookup('file', my_config_file) | from_yaml).service.hosts.all }}"
tasks:
- name: Create dynamic group from custom yaml file
add_host:
name: "{{ item }}"
group: custom_group
loop: "{{ my_custom_hosts }}"
- name: Play on new custom group
hosts: custom_group
gather_facts: false
tasks:
- name: Show we can actually contact the group
debug:
var: inventory_hostname
Which gives:
PLAY [Prepare environment] **********************************************************************************************************************************************************************************************************************************************
TASK [Create dynamic group from custom yaml file] ***********************************************************************************************************************************************************************************************************************
changed: [localhost] => (item=abc.com)
changed: [localhost] => (item=pqr.com)
PLAY [Play on new custom group] *****************************************************************************************************************************************************************************************************************************************
TASK [Show we can actually contact the group] ***************************************************************************************************************************************************************************************************************************
ok: [abc.com] => {
"inventory_hostname": "abc.com"
}
ok: [pqr.com] => {
"inventory_hostname": "pqr.com"
}
PLAY RECAP **************************************************************************************************************************************************************************************************************************************************************
abc.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
pqr.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Use delegation
In the following example, I use task delegation to change the target host inside a play targeted to other hosts.
This is more suited if you have few tasks to run on the custom hosts and/or you need facts from the current play hosts to run those tasks. See the load balancer example in the above doc for a more in depth explanation.
---
- name: Delegation example
hosts: localhost
gather_facts: false
vars:
# Replace with full path to actual file
# if this one is not in your 'files' subdir
my_config_file: service_config.yml
my_custom_hosts: "{{ (lookup('file', my_config_file) | from_yaml).service.hosts.all }}"
tasks:
- name: Task played on our current target host list
debug:
var: inventory_hostname
- name: Fake task delegated to our list of custom host
# Note: we play it only once so it does not repeat
# if the play `hosts` param is a group of several targets
# This is for example only and is not really delegating
# anything in this case. Replace with your real life task
debug:
msg: "I would run on {{ item }} with facts from {{ inventory_hostname }}"
delegate_to: "{{ item }}"
run_once: true
loop: "{{ my_custom_hosts }}"
Which gives:
PLAY [Delegation example] ***********************************************************************************************************************************************************************************************************************************************
TASK [Task played on our current target host list] **********************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"inventory_hostname": "localhost"
}
TASK [Fake task delegated to our list of custom host] *******************************************************************************************************************************************************************************************************************
ok: [localhost -> abc.com] => (item=abc.com) => {
"msg": "I would run on abc.com with facts from localhost"
}
ok: [localhost -> pqr.com] => (item=pqr.com) => {
"msg": "I would run on pqr.com with facts from localhost"
}
PLAY RECAP **************************************************************************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Run tasks with a certain tag within role in Ansible

I have the following folder-structure, inspired by the best-practices section in Ansibles documentation:
my-playbook.yml
my-role
|
|── tasks
|
|── my-task.yml
I have tagged the tasks within the my-task.yml file which is part of a role. I execute the playbook using ansible-playbook.yml --tags "mytag". Unfortunately, all tasks are skipped. Can I only filter tasks directly part of the playbook?
Within my playbook, I do something like
- hosts: ansible_server
connection: local
gather_facts: no
roles:
- validate_properties
Thanks in advance!
What you should do is call the role from a task by using the include_role module. On that task you can apply tags. Take this playbook, for example:
---
- name: Tag role test
hosts: local
connection: local
gather_facts: no
tasks:
- include_role:
name: debug
tags:
- dont_run
- debug:
msg: Solo shot first
tags:
- run
Where my role/debug consists of just a task that prints Hello, world!.
If you call this playbook directly you get this output:
PLAY [Tag role test]
TASK [debug : debug]
ok: [localhost] =>
msg: Hello, world!
TASK [debug]
ok: [localhost] =>
msg: Solo shot first
PLAY RECAP
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
But, if you exclude the dont_run task like this:
ansible-playbook tag_roles.yml --skip-tags dont_run
This is the output:
PLAY [Diff test]
TASK [debug]
ok: [localhost] =>
msg: Solo shot first
PLAY RECAP
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I hope it helps.
You have to tag the subtasks also with your tag u want to run to:
Main Task:
- name: "test tags on sub task"
include_tasks: subtask.yml
with_items: "{{ myList }}"
loop_control:
label: item
tags: test
Sub task:
debug: msg="Sub Task"
tags: test

How to wait for a variable to be defined?

Is there a way to make a playbook waiting till a variable is defined?
To reduce some time in the execution of a playbook, I would like to to split it into multiple and start them at the same time. Some of them need a variables, which are defined in the other playbooks.
Is it possible?
IMHO it's not possible. Global scope is set only by config, environment variables and the command line.
Other variables are shared in the scope of a play. It is possible to import more playbooks into one playbook with import_playbook and share variables among the playbooks. But, it's not possible to let the imported playbooks run asynchronously and let them wait for each other.
An option would be to use an external shared memory (e.g. database) and to start such playbooks separately. For example, to share variables among the playbooks at the controller, a simple ini file would do the job.
$ cat shared-vars.ini
[global]
The playbook below
- hosts: localhost
tasks:
- wait_for:
path: "{{ playbook_dir }}/shared-vars.ini"
search_regex: "^shared_var1\\s*=(.*)"
- debug:
msg: "{{ lookup('ini', 'shared_var1 file=shared-vars.ini') }}"
waits for a variable shared_var1 in the file shared-vars.ini
$ ansible-playbook wait_for_var.yml
PLAY [localhost] *******************************************************
TASK [wait_for] ********************************************************
Next playbook
- hosts: localhost
tasks:
- ini_file:
path: "{{ playbook_dir }}/shared-vars.ini"
section: global
option: shared_var1
value: Test value set by declare_var.yml
writes the variable shared_var1 into the file shared-vars.ini
$ ansible-playbook declare_var.yml
PLAY [localhost] *******************************************************
TASK [ini_file] ********************************************************
changed: [localhost]
PLAY RECAP *************************************************************
localhost : ok=1 changed=1 unreachable=0 failed=0
First playbook which was waiting for the variable continues
TASK [debug] ***********************************************************
ok: [localhost] => {
"msg": "Test value set by declare_var.yml"
}
PLAY RECAP *************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0

Force limit parameter to be set in ansible

Is there a way to force commands ansible-playbook, ansible-variable, etc... to be executed with a --limit option (otherwise to deny it) ?
I discovered that, on a cluster it can easily run a playbook to all nodes if you mistakenly run it without limit, I'd like to prevent it from ansible users.
Use the ansible_limit variable (added in ansible 2.5). You can test like this:
tasks:
- fail:
msg: "you must use -l or --limit"
when: ansible_limit is not defined
run_once: true
It's the opposite of the task I've solved recently. My goal was to detect there is a --limit and to skip some plays.
https://medium.com/opsops/how-to-detect-if-ansible-is-run-with-limit-5ddb8d3bd145
In your case you can check this in the play and fail if it "full run":
- hosts: all
gather_facts: no
tasks:
- set_fact:
full_run: '{{play_hosts == groups.all}}'
- fail:
msg: 'Use --limit, Luke!'
when: full_run
You can use a different group instead of all, of course (change it in both hosts and set_fact lines).
I did it that way in a task:
$ cat exit-if-no-limit.yml
---
- name: Verifying that a limit is set
fail:
msg: 'This playbook cannot be run with no limit'
run_once: true
when: ansible_limit is not defined
- debug:
msg: Limit is {{ ansible_limit }}, let's continue
run_once: true
when: ansible_limit is defined
Which I include in my playbooks when I need to disallow them to run on all the hosts:
- include_role:
name: myrole
tasks_from: "{{ item }}.yml"
loop:
- exit-if-no-limit
- something
- something_else
Easy to reuse when needed. It works like that:
TASK [myrole: Verifying that a limit is set]
fatal: [ahost]: FAILED! => {"changed": false, "msg": "This playbook cannot be run with no limit"}
or
TASK [myrole: debug]
ok: [anotherhost] => {
"msg": "Limit is anotherhost, let's continue"
}
This can be done with the assert module.
I like to do this in a separate play, at the start of the playbook, with fact gathering disabled. That way, the playbook fails instantly if the limit is not specified.
- hosts: all
gather_facts: no
tasks:
- name: assert limit
run_once: yes
assert:
that:
- 'ansible_limit is defined'
fail_msg: Playbook must be run with a limit (normally staging or production)
quiet: yes
When a limit is not set, you get:
$ ansible-playbook site.yaml
PLAY [all] *********************************************************************
TASK [assert limit] ************************************************************
fatal: [host1.example.net]: FAILED! => {"assertion": "ansible_limit is defined", "changed": false, "evaluated_to": false, "msg": "Playbook must be run with a limit (normally staging or production)"}
NO MORE HOSTS LEFT *************************************************************
PLAY RECAP *********************************************************************
host1.example.com : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
And when a limit is set, you get:
$ ansible-playbook -l staging site.yaml
PLAY [all] *********************************************************************
TASK [assert limit] ************************************************************
ok: [host1.example.com]
PLAY [all] *********************************************************************
[... etc ...]
Functionally this is very similar to using the fail module, guarded with when. The difference is that the assert task itself is responsible for checking the assertions, therefore if the assertions pass, the task succeeds. When using the fail module, if the when condition fails, the task is skipped.

Resources