Set variable if empty or not defined with ansible - ansible

In my ansible vars file, I have a variable that will sometimes be set and other times I want to dynamically set it. For example, I have an RPM that I want to install. I can manually store the location in a variable, or if I don't have a particular one in mind, I want to pull the latest from Jenkins. My question is, how can I check if the variable is not defined or empty, and if so, just use the default from Jenkins (already stored in a var)?
Here is what I have in mind:
...code which gets host_vars[jenkins_rpm]
- hosts: "{{ host }}"
tasks:
- name: Set Facts
set_fact:
jenkins_rpm: "{{ hostvars['localhost']['jenkins_rpm'] }}"
- name: If my_rpm is empty or not defined, just use the jenkins_rpm
set_fact: my_rpm=jenkins_rpm
when: !my_rpm | my_rpm == ""

There is default filter for that:
- set_fact:
my_rpm: "{{ my_rpm | default(jenkins_rpm) }}"

Related

Ansible - Set Playbook level Environment variable, but after defining some tasks

I want to set a playbook level environment, but after I execute a couple of tasks. I have found that I could define a playbook level environment variable before definition of any tasks or task level environment variables. But, I haven't found how can I set-up an environment variable that can be used by all tasks following a task.
- name: server properties
hosts: kafka_broker
vars:
ansible_ssh_extra_args: "-o StrictHostKeyChecking=no"
ansible_host_key_checking: false
date: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
copy_to_dest: "/export/home/kafusr/kafka/secrets"
server_props_loc: "/etc/kafka"
secrets_props_loc: "{{ server_props_loc }}/secrets"
environment:
CONFLUENT_SECURITY_MASTER_KEY: "{{ extract_key2 }}"
tasks:
- name: Create a directory if it does not exist
file:
path: "{{ copy_to_dest }}"
state: directory
mode: '0755'
- name: Find files from "{{ server_props_loc }}"
find:
paths: /etc/kafka/
patterns: "server.properties*"
# ... the rest of the task
register: etc_kafka_server_props
- name: Find files from "{{ secrets_props_loc }}"
find:
paths: /etc/kafka/secrets
patterns: "*"
# ... the rest of the task
register: etc_kafka_secrets_props
- name: Copy the files
copy:
src: "{{ item.path }}"
dest: "{{ copy_to_dest }}"
remote_src: yes
loop: "{{ etc_kafka_server_props.files + etc_kafka_secrets_props.files }}"
- name: set masterkey content value
set_fact:
contents: "{{ lookup('file', '/export/home/kafusr/kafka/secrets/masterkey.txt') }}"
extract_key2: "{{ contents.split('\n').2.split('|').2|trim }}"
I want to set CONFLUENT_SECURITY_MASTER_KEY after the set_facts task
Is it possible to set playbook level environment variable, but after defining some tasks
Thank you
UPDATE
Initially, when I was executing the playbook as originally defined, I was getting the error
fatal: [kafkaserver1]: FAILED! => {"msg": "The field 'environment' has an invalid value,
which includes an undefined variable. The error was: 'extract_key2' is undefined"}
which was expected as the variable extract_key2 was not set - before copying the files to desired directory.
After #Zeitounator's suggestion, when I added default to the environment variable's definition,
CONFLUENT_SECURITY_MASTER_KEY: "{{ extract_key2 | default('') }}"
I now get a different error
The new error is
TASK [set masterkey content value] ******************** fatal: [kafkaserver1]: FAILED! =>
{"msg": "The task includes an option with an undefined variable. The error was: 'contents' is undefined\n\n
The error appears to be in '/export/home/kafuser/tmp/so-71538207-question.yml': line 43, column 7, but may\n
be elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n
- name: set masterkey content value\n ^ here\n"}
Getting this on all 3 brokers in the console and I checked the file it exists
I did do a cat on that file, copying the path from error to make sure there is no typo, and the contents of that file are displayed on console.
Update 2
I am trying to figure out how to use slurp to get the info, with the same approach as #Zeitounator's example about using lookup.
This is what I am trying. The current definition, is of course, erroneous. Just wanted to show what I am trying to do. But, can it be done for slurp and am I on the right path?
environment:
CONFLUENT_SECURITY_MASTER_KEY: >-
{{
(
((slurp: src: /export/home/z8tpush/kafka/secrets/masterkey.txt)['content'] | b64decode ).split('\n').2.split('|').2|trim
)
}}
#Zeitounator - Will you be able to direct me to an example where a slurp or fetch module is defined to set-up an environment variable and where the value will get updated after the tasks that create the file are executed, similar to what you have shown with lookup filter? I would really appreciate it.
Note:
Ultimately, I want to use ansible to create a new kafka user using confluents CLI commands ( using shell or command module ), verify it in my directory and once satisfied, I will encrypt the security.properties file using the masterkey and copy it to the appropriate location where confluent is installed.
As already mentioned, you can
With Ansible Configuration Settings set environment variables globally
Setting the remote environment in a task
Regarding your question
I haven't found how can I set-up an environment variable that can be used by all tasks following a task.
You can set the environment on Block level, a logical groups of tasks too
Setting the remote environment: "When you set a value with environment: at the play or block level, it is available only to tasks within the play or block that are executed by the same user."
This means you would need to define a block for the next tasks
- name: Block of next task(s)
block:
- name: Next task
...
environment:
CONFLUENT_SECURITY_MASTER_KEY: "{{ extract_key2 }}"
Regarding your question
Is it possible to set playbook level environment variable, but after defining some tasks?
No, not on that level in that run as the playbook is already running.
Another option might be to distribute the tasks in question into an other role, playbook or task file and include_* it.
You cannot set_fact a var depending on an other var in the same block. Moreover, there is absolutely no need to set_fact here as long as your relevant tasks can live with an empty environment var until it is fully defined. The following environment declaration (untested) should work and return the key for every task running after your file exists.
environment:
CONFLUENT_SECURITY_MASTER_KEY: >-
{{
(
(
lookup('file', '/export/home/kafusr/kafka/secrets/masterkey.txt', errors='ignore')
| default('')
).split('\n').2
| default('')
).2
| default('')
| trim
}}

Ansible - multiple items in path, but cannot use loop

I'm not sure how to describe the title or my question properly, feel free to edit.
I'll jump right in. I have this working piece of Ansible code:
- file:
path: "{{ item.item.value.my_folder }}/{{ item.item.value.filename }}"
state: absent
loop: "{{ my_stat.results }}"
when: item.stat is defined and item.stat.exists and item.stat.islnk
If Ansible is run, the task is executed properly, and the file is removed from the system.
Now, the issue. What I want Ansible to do is loop over multiple items described in "path". This way I won't have to create a seperate task for each filename I want to be deleted.
Example:
- file:
path:
- "{{ item.item.value.my_folder }}/{{ item.item.value.filename }}"
- "{{ item.item.value.my_folder }}/{{ item.item.value.other_filename }}"
state: absent
loop: "{{ my_stat.results }}"
when: item.stat is defined and item.stat.exists and item.stat.islnk
But Ansible doesn't proces the items in the list described in 'path', so the filesnames will not be deleted.
I see I cannot use 'loop', since it is already in use for another value.
Question: How would I configure Ansible so that I can have multiple items in the path and let Ansible delete the filenames, and keeping the current loop intact.
-- EDIT --
Output of the tasks:
I've removed the pastebin url since I believe it has no added value for the question, and the answer has been given.
As described in the documentation, path is of type path, so Ansible will only accept a valid path in there, not a list.
What you can do, though, is to slightly modify your loop and make a product between your existing list and a list of the filenames properties you want to remove, then use those as the key to access item.item.value (or item.0.item.value now, since we have the product filter applied).
For example:
- file:
path: "{{ item.0.item.value.my_folder }}/{{ item.0.item.value[item.1] }}"
state: absent
loop: "{{ my_stat.results | product(['filename', 'other_filename']) }}"
when:
- item.0.stat is defined
- item.0.stat.exists
- item.0.stat.islnk
PS: a list in a when is the same as adding and statements in the said when

Ansible: print only user defined variables

How can I print variables declared only at group_vars, host_vars without ansible facts?
such code is good:
- name: "Ansible | List all known variables and facts"
debug:
var: hostvars[inventory_hostname]
But I don't need host IPs,disks, etc.
I mean to check all my variables one more time before continue to execute Play.
There are 3 categories of variables: ansible facts, special variables, and user's variables. Remove both ansible facts and special variables from hostvars and what is left are user's variables. The list of the ansible facts is available in the variables ansible_facts. The list of the special variables must be created (I think).
Create a list of special variables
If you run the playbook below you'll see the list of the special variables and user's variables
- hosts: localhost
tasks:
- debug:
msg: "{{ hostvars[inventory_hostname]|
difference(ansible_facts) }}"
Eliminate the user's vars and put the list of the special variables into a file. For example
shell> cat special_vars.yml
special_vars:
- ansible_python_interpreter
- ansible_connection
- inventory_hostname
...
This list of special variables might be not complete and will serve the purpose of this host only.
Remove ansible facts and special variables from hostvars
- hosts: localhost
vars_files:
- special_vars.yml
tasks:
- set_fact:
user_var1: AAA
- debug:
msg: "{{ hostvars[inventory_hostname]|
difference(ansible_facts)
difference(special_vars) }}"
gives the list of user's variables only
msg:
- user_var1
The user's variables will include also the configuration variables set by the user (e.g. connection variables: ansible_user or priviledge escalation: ansible_become).
Name-space
A better practice is to "name-space" variables. For example
- hosts: localhost
vars:
prj51_var1: AAA
prj51_var2: BBB
tasks:
- debug:
msg: "{{ item }}: {{ query('vars', item)|first }}"
loop: "{{ query('varnames', 'prj51_.+$') }}"
gives
msg: 'prj51_var1: AAA'
msg: 'prj51_var2: BBB'

How to get the current file name in ansible?

I am having an ansible script. It calls other scripts using include module.
I need to get the current file name in a variable.
For ex:
- include: Run-Config889.yml
Inside Run-Config889.yml, i need to get the file name Run-Config889.yml in a variable.
Whether any built-in variable is there to find the current file name? if so what it is?
You can get the current playbook file name with :
---
- hosts: 127.0.0.1
connection: local
gather_facts: no
vars:
playbook_absoluteName: "{{ (lookup('file', '/proc/self/cmdline') | regex_replace('\u0000',' ')).split() | select('match','^.*[.]ya?ml$') | list | first }}"
playbook_baseName: "{{ playbook_absoluteName | basename }}"
tasks:
- debug:
var: item
loop:
- "{{ playbook_absoluteName }}"
- "{{ playbook_baseName }}"
...
This was inspired by this blog article, with minor/cosmetics improvements.
Don't know about any built-in variable but there are 2 options I can think of.
Option 1
You can pass variables to included files like so:
main.yml
---
- include: Run-Config889.yml file_name_variable="Run-Config889.yml"
Option 2
Set the variable in any of the vars files used by the play. All defined variables in a play are inherited by subsequent include statements.
sample-vars.yml
---
file_name_variable: "Run-Config889.yml"
Using the variable.
Run-Config889.yml
---
- name: Display variable
debug:
msg: File name {{ file_name_variable }}

Constant Date and Time

I leverage this to get the date/time without running facts in my playbooks, in order to save run time:
all.yaml
date: "{{ lookup('pipe','date \"+%Y-%m-%d-%H%M\"') }}"
I've noticed that if I reference this at the beginning of the playbook it references one time e.g. 2019-04-10-1300. If I reference it at the end of the playbook, which is 5 minutes later, the time is different e.g. 2019-04-10-1305.
I want to use this variable to reference a directory name, and therefore I want it to be constant from at any point in the script's lifetime.
./outputs/"{{ date }}"/errors.txt
AKA
./outputs/2019-04-10-1300/errors.txt
How do I get this value to be constant?
EDIT
This task gives me an error
- name: TESTS
environment:
execution_date: "{{ lookup('pipe','date \"+%Y%m%d-%H%M\"') }}"
tags:
- test
The group_var below is not callable via "environment.execution_date" or "execution_date"
all.yaml
environment:
execution_date: "{{ lookup('pipe','date \"+%Y%m%d-%H%M\"') }}"
- name: TESTS
debug:
var: environment.execution_date
Sounds like you're wanting to save/recall a particular date, using it like a variable.
Probably a few ways to do this, my first thought is that you could export this as an environment variable and then recall that value:
environment:
execution_date: "{{ lookup('pipe','date \"+%Y-%m-%d-%H%M\"') }}"
You would then use it like:
./outputs/"{{ execution_date }}"/errors.txt
Check out the documentation about this here: https://docs.ansible.com/ansible/latest/user_guide/playbooks_environment.html
Ansible variables don't store a value, they get re-evaluated each time they are referenced. Hence your date variable always does a fresh lookup of the current time.
To store a value, and recall it later you can set a fact, e.g.
- hosts: localhost
connection: local
tasks:
- set_fact:
execution_time: "{{ lookup('pipe','date \"+%Y-%m-%d-%H%M\"') }}"
- debug:
msg: "{{ execution_time }}"
- pause:
minutes: 2
- hosts: localhost
connection: local
tasks:
- debug:
msg: "{{ execution_time }}"

Resources