How to wait for a variable to be defined? - ansible

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

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

Single Ansible task for become yes and no

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

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

Conditionally import a playbook based on vars_prompt in Ansible

I am using the following ansible script to import a playbook based on the user input,
---
- hosts: localhost
vars_prompt:
- name: "cleanup"
prompt: "Do you want to run cleanup? Enter [yes/no]"
private: no
- name: run the cleanup yaml file
import_playbook: cleanup.yml
when: cleanup == "yes"
Execution log:
bash-$ ansible-playbook -i hosts cleanup.yml
Do you want to run cleanup? Enter [yes/no]: no
PLAY [localhost] *********************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************
ok: [127.0.0.1]
PLAY [master] ********************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************
fatal: [192.168.56.128]: FAILED! => {"msg": "The conditional check 'cleanup == \"yes\"' failed. The error was: error while evaluating conditional (cleanup == \"yes\"): 'cleanup' is undefined"}
to retry, use: --limit #/home/admin/playbook/cleanup.retry
PLAY RECAP ***************************************************************************************************************************
127.0.0.1 : ok=1 changed=0 unreachable=0 failed=0
192.168.56.128 : ok=0 changed=0 unreachable=0 failed=1
It throws error in the imported playbook not in the mail playbook.
Please help me to import a playbook based on user input.
vars_prompt variables are only defined in the play in which they were called. In order to use them in other plays, a workaround is to use set_fact to bind the variable to a host, then use hostvars to access that value from the second play.
For instance:
---
- hosts: localhost
vars_prompt:
- name: "cleanup"
prompt: "Do you want to run cleanup? Enter [yes/no]"
private: no
tasks:
- set_fact:
cleanup: "{{cleanup}}"
- debug:
msg: 'cleanup is available in the play using: {{cleanup}}'
- debug:
msg: 'cleanup is also available globally using: {{hostvars["localhost"]["cleanup"]}}'
- name: run the cleanup yaml file
import_playbook: cleanup.yml
when: hostvars["localhost"]["cleanup"] == True

Ansible - /etc/profile.d scripts

I defined a envs.sh script inside the /etc/profile.d/ folder.
When executing a ansible-playbook, I'm trying to get the value of this env var but it instead throws me an error:
Ansible test:
debug: msg="{{ ansible_env.NGN_VAL }} is an environment variable"
Error:
fatal: [xxx.yyy.zzz.kkk] => One or more undefined variables: 'dict object' has no attribute 'NGN_VAL'
FATAL: all hosts have already failed -- aborting
Why it doesn't execute the scripts inside that folder? When I connect through ssh, I echo it and it displays its value. How do I set remote environment variables and obtain them during ansible execution?
Thanks
Test with this if you want catch the environment variable.
[jenkins#scsblnx-828575 jenkins]$ cat mypass
export MYPASSWD=3455637
[jenkins#scsblnx-828575 jenkins]$ cat test.yml
- hosts: all
user: jenkins
tasks:
- name: Test variables.
shell: source /apps/opt/jenkins/mypass && echo $MYPASSWD
register: myenvpass
- debug: var=myenvpass.stdout
[jenkins#scsblnx-828575 jenkins]$ ansible-playbook -i hosts test.yml
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [127.0.0.1]
TASK [Test variables.] *********************************************************
changed: [127.0.0.1]
TASK [debug] *******************************************************************
ok: [127.0.0.1] => {
"myenvpass.stdout": "3455637"
}
PLAY RECAP *********************************************************************
127.0.0.1 : ok=3 changed=1 unreachable=0 failed=0
In my point of view you can accomplish using a mix of ansible vault and system environment variables.
Create and encrypt a file with ansible vault (this is where you put the content of your remote env variable):
$ ansible-vault create vars_environment.yml
Put your password to encrypt this file and write content of your variable, for example:
ngn_val: supersecret
I use to load my permanent environment variables in /etc/profile.d directory but they are more paths:
/etc/profile.d
/etc/profile
/etc/environment
~/.profile
It is a good way to write a template in your playbook, although your can use the copy module:
---
# environment.yml
- name: Get enviroment variable server
hosts: debian.siccamdb.sm
vars:
dest_profile_environment: /etc/profile.d/environment.sh
vars_files:
- vars_environment.yml
tasks:
- name: "Template {{ dest_profile_enviroment }}"
template:
src: environment.sh.j2
dest: "{{ dest_profile_environment }}"
mode: '0644'
- name: Load env variable
shell: ". {{ dest_profile_environment }} && echo $NGN_VAL"
register: env_variable
- name: Debug env variable
debug:
var: env_variable.stdout_lines[0]
The important thing here is in the shell task because you have to source your environment variable and the use it by the register keyword which can be used to capture the output of a command in a variable.
This is the template's content in this example the file environment.sh.j2:
export NGN_VAL={{ ngn_val }}
Type your password o the vars_environment.yml file after run the playbook:
ansible-playbook --vault-id #prompt environment.yml
The output is the following:
PLAY [Get enviroment variable server] *************************************************************************************************************************************************************************************************
TASK [Gathering Facts] ****************************************************************************************************************************************************************************************************************
ok: [debian.siccamdb.sm]
TASK [Template {{ dest_profile_enviroment }}] *****************************************************************************************************************************************************************************************
changed: [debian.siccamdb.sm]
TASK [Load env variable] **************************************************************************************************************************************************************************************************************
changed: [debian.siccamdb.sm]
TASK [Debug env variable] *************************************************************************************************************************************************************************************************************
ok: [debian.siccamdb.sm] => {
"env_variable.stdout_lines[0]": "supersecret"
}
PLAY RECAP ****************************************************************************************************************************************************************************************************************************
debian.siccamdb.sm : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Another way to accomplish is using custom facts in your remote server. This is a good practice if you want to leave the content of your variable in the remote server and using in playbooks without the needs of ansible vault.

Resources