Ansible - Read file and register variable - ansible

I have a file with the following content:
AWS_ACCESS_KEY_ID=xxxxxxx
AWS_SECRET_ACCESS_KEY=yyyyyy
AWS_SESSION_TOKEN=zzzzzzzz
How do I read this file, split the line based on "=" and set the values of the variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) so that I can use these variables in the script for later use?

Q: How do I read this file, split the line based on "=" and set the values of the variables?
A: Use ini lookup plugin. For example the tasks below
- set_fact:
AWS_ACCESS_KEY_ID: "{{ lookup('ini', 'AWS_ACCESS_KEY_ID type=properties file=conf.ini') }}"
- debug:
var: AWS_ACCESS_KEY_ID
give
"AWS_ACCESS_KEY_ID": "xxxxxxx"
It is possible to use a list of variables. For example the play below
- hosts: localhost
vars:
my_vars_keys: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN]
tasks:
- set_fact:
my_vars: "{{ my_vars|default({})|
combine({item:
lookup('ini',
item ~ ' type=properties file=conf.ini')})
}}"
loop: "{{ my_vars_keys }}"
- debug:
msg: "{{ my_vars[item] }}"
loop: "{{ my_vars_keys }}"
gives
ok: [localhost] => (item=AWS_ACCESS_KEY_ID) => {
"msg": "xxxxxxx"
}
ok: [localhost] => (item=AWS_SECRET_ACCESS_KEY) => {
"msg": "yyyyyy"
}
ok: [localhost] => (item=AWS_SESSION_TOKEN) => {
"msg": "zzzzzzzz"
}
Q: How do I make sure that above set_fact runs on the hosts and not on the ansible tower?
A: The set_fact uses Lookup Plugins. Quoting
Like all templating, these plugins are evaluated on the Ansible control machine, not on the target/remote.

Related

Ansible - List of dictionaries

Let me introduce my problem. I have some list of dictionary in my Ansible code:
my_example_list = [
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
I need execute command which will iterate over this list and it should look something like:
- name: 'Example'
shell: 'Here is my {{ item.key }} and here is {{ item.value }}'
What I've do or try to do:
I was trying to do that with with_items but i'm not able to point into value of particular key.
I've also try to filter values using | first and | last but it's not worked in my case.
What I want to achieve:
Creating loop which will iterate via that list and inject separated key and value into command.
I was asked to show how I was trying to resolve my issue:
Here is some code:
# Showing last component failing
- name: "Try to show last component of my list"
debug:
msg: "{{ my_example_list[1] | last }}"
# When i'm trying to show first component of my list i get "key1"
- name: "Try to show first component of my list"
debug:
msg: "{{ my_example_list[1] | first }}"
# This shows me my list of dict
- name: "Trying use with_items"
debug:
msg: "{{ item }}"
with_items: "{{ my_example_list }}"
# But when i'm trying point to key and value for example
- name: "Trying use with_items point to key and value"
debug:
msg: "Here is my {{ item.key }} which keep {{ item.value }}"
with_items: "{{ my_example_list }}"
# It's failing.
Sorry it's not maybe solution with using loop. I'm just stack with that issue over few days... And as first step I want to know how correctly point to pair keys and values.
It also works well:
- name: Correct solution
debug:
msg: "This is my {{ item.key }} and my value {{ item.value }}"
with_dict: "{{ my_example_list }}"
Thanks #U880D for help! I'm not able to add some plus for your solution because I'm new joiner. Appreciate your answer! :)
Your data structure and naming seems to be unfavorable. There is no need to number the key name and therefore it should be avoided. Furthermore counting list elements in Python starts at 0 not 1.
The following minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
vars:
example_list: |
[
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
tasks:
- name: Example loop
debug:
msg: "{{ item }} is of type {{ item | type_debug }}"
loop: "{{ example_list }}"
- name: Example loop
debug:
msg: "{{ item.values() }}"
loop: "{{ example_list }}"
will result into an output of
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg: '{u''key1'': u''value_of_first_key''} is of type dict'
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg: '{u''key2'': u''value_of_second_key''} is of type dict'
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg: '{u''key3'': u''value_of_third_key''} is of type dict'
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg:
- value_of_first_key
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg:
- value_of_second_key
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg:
- value_of_third_key
Further Readings
How to work with lists and dictionaries in Ansible
Extended loop variables

How to loop through a list of variables set-up in an Ansible inventory file?

Having an ansible inventory file like this:
hostname1 users=user1,user2,user3
hostname2 users=user1,user2
I need an Ansible playbook that will loop through each of the user variable list and run the grep username /etc/passwd command for each hostname/user variable
What I have so far is:
---
- name: Grep shadow entries for users
hosts: all
tasks:
- name: Get shadow entry for each user
shell: "grep {{ item }} /etc/shadow"
register: shadow_entry
with_items: "{{ hostvars[inventory_hostname].users }}"
- name: Print shadow entry
debug:
msg: "{{ shadow_entry.results }}"
But this does not loop through each user list but rather goes and runs grep [user1,user2,user3] /etc/passwd.
Also tried different variations on setting the inventory file like:
hostname1 users=["user1","user2","user3"]
hostname1 users='["user1","user2","user3"]'
hostname1 users="user1","user2","user3"
Nothing seems to work.
How to loop through a list of variables set-up in an Ansible inventory file?
For a file cat hosts
[test]
test.example.com USERS="['user1','user2','user3']"
an example playbook
---
- hosts: test
become: false
gather_facts: false
tasks:
- name: Show users
debug:
msg: "{{ item }}"
loop: "{{ hostvars[inventory_hostname].USERS }}"
- name: Show users
debug:
msg: "{{ item }}"
loop: "{{ USERS }}"
will both result into an output of
TASK [Show users] ***********************
ok: [test.example.com] => (item=user1) =>
msg: user1
ok: [test.example.com] => (item=user2) =>
msg: user2
ok: [test.example.com] => (item=user3) =>
msg: user3
Similar Q&A
How specify a list value as variable in Ansible inventory file?
Ansible: How to define a list in host inventory?
Please take note that it is recommended to proceed further with reading the account database fully via getent module – A wrapper to the unix getent utility.
Runs getent against one of it’s various databases and returns information into the host’s facts, in a getent_ prefixed variable
See Q&A
Ansible playbook to check user exist
Both options below are strings
hostname1 users=user1,user2,user3
hostname2 users=[user1,user2,user3]
You can test it
- debug:
var: users|type_debug
gives in both cases
users|type_debug: str
See Defining variables in INI format
In the first case
hostname1 users=user1,user2,user3
split the items if you want to iterate the list
- debug:
msg: "{{ item }}"
loop: "{{ users.split(',') }}"
gives
ok: [hostname1] => (item=user1) =>
msg: user1
ok: [hostname1] => (item=user2) =>
msg: user2
ok: [hostname1] => (item=user3) =>
msg: user3
In the second case
hostname2 users=[user1,user2,user3]
the string is a valid YAML list. Use the filter from_yaml to convert the string to the list
- debug:
msg: "{{ item }}"
loop: "{{ users|from_yaml }}"
gives the same result
ok: [hostname2] => (item=user1) =>
msg: user1
ok: [hostname2] => (item=user2) =>
msg: user2
ok: [hostname2] => (item=user3) =>
msg: user3
You have to quote the string, either single or double if you put spaces into it
hostname2 users='[user1, user2, user3]'
Use module getent to read /etc/shadow. On the remote system, see man getent whether the database is available or not
- getent:
database: shadow
This module will create a dictionary in the variable ansible_facts.getent_shadow. You can use it, instead of grep, to iterate users. For example, given the users
hostname2 users=operator,nobody
The task below
- debug:
msg: "{{ item }}: {{ ansible_facts.getent_shadow[item]|to_yaml }}"
loop: "{{ users.split(',') }}"
gives
ok: [hostname2] => (item=operator) =>
msg: |-
operator: ['*', '18397', '0', '99999', '7', '', '', '']
ok: [hostname2] => (item=nobody) =>
msg: |-
nobody: ['*', '18397', '0', '99999', '7', '', '', '']

How do you iterate/parse through a CSV file to use in Ansible playbook?

I am new to Ansible and don't have much coding experience in general. I am trying to figure out how to provision RHEL servers using the DellEMC OpenManage modules.
The first step to this is figuring out how to parse a CSV, that we are putting necessary information for the later templating. (IP, hostname, MAC, etc. ...) I can get it to print the data in general, but cant figure out how to parse/iterate through it.
CSV Sample
server_name,idrac_ip,idrac_user,idrac_pwd,idrac_nw,idrac_gw,idrac_mac,mgmt_ip,mgmt_nw,mgmt_gw,mgmt_mac,bond0_100_ip,bond0_200_ip,dns_1,bond0_100_nw,bond0_100_gw,bond0_100_vip,bond0_200_nw,bond0_200_gw,bond0_200_vip,os,fs,sio_type,mdm_name
test1,1.1.1.10,root,Password1234,1.1.1.0/24,1.1.1.1,00:00:00:00:00:01,1.1.1.142,1.1.62.0/24,1.1.2.1,98:03:9B:46:25:B3,1.1.1.22,1.1.1.21,1.1.1.26,1.1.61.15/24,1.1.61.1,1.1.61.29,1.1.66.0/24,1.1.66.1,1.1.1.29,RHEL 7.6,1,Master,MDM-1
Here is my playbook to print the info in general.
---
- name: Parse
hosts: localhost
connection: local
tasks:
- name: Load CSV Data into object
read_csv:
path: 'Test_Lab_Servers.csv'
fieldnames: server_name,idrac_ip,idrac_user,idrac_pwd,idrac_nw,idrac_gw,idrac_mac,mgmt_ip,mgmt_nw,mgmt_gw,mgmt_mac,bond0_100_ip,bond0_200_ip,dns_1,bond0_100_nw,bond0_100_gw,bond0_100_vip,bond0_200_nw,bond0_200_gw,bond0_200_vip,os,fs,sio_type,mdm_name
delimiter: ','
register: csv_output
delegate_to: localhost
- name: Print data
debug:
msg: "{{ csv_output }}"
Any advice?
According the read_csv module parameter documentation fieldnames are
needed if the CSV does not have a header
only. Therefore you could remove it to get a list object which might be easier to parse.
- name: Print data
debug:
msg: "{{ csv_output.list }}"
As already mentioned within the comments, to iterate over the list elements you may use loop.
- name: Print data
debug:
msg: "{{ item }}"
loop: "{{ csv_output.list }}"
With extended loop variables you will have a better control about the loop output.
- name: Print data
debug:
msg: "{{ item }}"
loop: "{{ csv_output.list }}"
loop_control:
extended: yes
label: "{{ ansible_loop.index0 }}"
Resulting into an output of
TASK [Print data] ***************
ok: [localhost] => (item=0) =>
msg:
bond0_100_gw: 1.1.61.1
bond0_100_ip: 1.1.1.22
bond0_100_nw: 1.1.61.15/24
bond0_100_vip: 1.1.61.29
bond0_200_gw: 1.1.66.1
bond0_200_ip: 1.1.1.21
bond0_200_nw: 1.1.66.0/24
bond0_200_vip: 1.1.1.29
dns_1: 1.1.1.26
fs: '1'
idrac_gw: 1.1.1.1
idrac_ip: 1.1.1.10
idrac_mac: 00:00:00:00:00:01
idrac_nw: 1.1.1.0/24
idrac_pwd: Password1234
idrac_user: root
mdm_name: MDM-1
mgmt_gw: 1.1.2.1
mgmt_ip: 1.1.1.142
mgmt_mac: 98:03:9B:46:25:B3
mgmt_nw: 1.1.62.0/24
os: RHEL 7.6
server_name: test1
sio_type: Master
...
Specific items (fields) like server_name you could get simply by using
msg: "{{ item.server_name }}"
resulting into an output of
TASK [Print data] ************
ok: [localhost] => (item=0) =>
msg: test1
ok: [localhost] => (item=1) =>
msg: test2
ok: [localhost] => (item=2) =>
msg: test3
...

Issue with using Omit option

I am trying configure a attribute only if my_int_http is defined else I dont want it. So I coded it like below:
profiles:
- "{{ my_int_L4 }}"
- "{{ my_int_http | default(omit) }}"
However my execution fail and when check the arguments passed by the code in actual configuration it shows like below:
"profiles": [
"my_example.internal_tcp",
"__omit_place_holder__ef8c5b99e9707c044ac07fda72fa950565f248a4"
So how to pass absolutely no value where it is passing __omit_place_holder_****?
Q: "How to pass absolutely no value where it is passing omit_place_holder ?"
A1: Some filters also work with omit as expected. For example, the play
- hosts: localhost
vars:
test:
- "{{ var1|default(false) }}"
- "{{ var1|default(omit) }}"
tasks:
- debug:
msg: "{{ {'a': item}|combine({'b': true}) }}"
loop: "{{ test }}"
gives
msg:
a: false
b: true
msg:
b: true
As a sidenote, default(omit) is defined type string
- debug:
msg: "{{ item is defined }}"
loop: "{{ test }}"
- debug:
msg: "{{ item|type_debug }}"
loop: "{{ test }}"
give
TASK [debug] *************************************************************
ok: [localhost] => (item=False) =>
msg: true
ok: [localhost] => (item=__omit_place_holder__6e56f2f992faa6e262507cb77410946ea57dc7ef) =>
msg: true
TASK [debug] *************************************************************
ok: [localhost] => (item=False) =>
msg: bool
ok: [localhost] => (item=__omit_place_holder__6e56f2f992faa6e262507cb77410946ea57dc7ef) =>
msg: str
A2: No value in Ansible is YAML null. Quoting:
This is typically converted into any native null-like value (e.g., undef in Perl, None in Python).
(Given my_int_L4=bob). If the variable my_int_http defaults to null instead of omit
profiles:
- "{{ my_int_L4 }}"
- "{{ my_int_http | default(null) }}"
the list profiles will be undefined
profiles: VARIABLE IS NOT DEFINED!
Use None instead
profiles:
- "{{ my_int_L4 }}"
- "{{ my_int_http | default(None) }}"
The variable my_int_http will default to an empty string
profiles:
- bob
- ''
See also section "YAML tags and Python types" in PyYAML Documentation.
You can try something like this,
profiles:
- "{{ my_int_L4 }}"
- "{{ my_int_http | default(None) }}"
This will give you an empty string. And you can add a check while iterating over the profiles.
Please have a look at this GitHub Issue to get more understanding.

Ansible variables wildcard selection

The only way I've found to select variables by a wildcard is to loop all variables and test match. For example
tasks:
- debug:
var: item
loop: "{{ query('dict', hostvars[inventory_hostname]) }}"
when: item.key is match("^.*_python_.*$")
shell> ansible-playbook test.yml | grep key:
key: ansible_python_interpreter
key: ansible_python_version
key: ansible_selinux_python_present
Is there a more efficient way to do it?
Neither json_query([?key=='name']), nor lookup('vars', 'name') work with wildcards.
Is there any other "wildcard-enabled" test, filter ...?
Note: regex_search is discussed in What is the syntax within the regex_search() to match against a variable?
You can select/reject with Jinja tests:
- debug:
msg: "{{ lookup('vars', item) }}"
loop: "{{ hostvars[inventory_hostname].keys() | select('match', '^.*_python_.*$') | list }}"
gives:
ok: [localhost] => (item=ansible_selinux_python_present) => {
"msg": false
}
ok: [localhost] => (item=ansible_python_version) => {
"msg": "2.7.10"
}
Update for Ansible 2.8 and higher versions.
There is the lookup plugin varnames added in Ansible 2.8. to "List of Python regex patterns to search for in variable names". See details
shell> ansible-doc -t lookup varnames
For example, to list the variables .*_python_.* the task below
- debug:
msg: "{{ item }}: {{ lookup('vars', item) }}"
loop: "{{ query('varnames', '^.*_python_.*$') }}"
gives
TASK [debug] ***************************************************************
ok: [localhost] => (item=ansible_python_interpreter) =>
msg: 'ansible_python_interpreter: /usr/bin/python3'
ok: [localhost] => (item=ansible_python_version) =>
msg: 'ansible_python_version: 3.8.5'
ok: [localhost] => (item=ansible_selinux_python_present) =>
msg: 'ansible_selinux_python_present: True
Moreover, the lookup plugin varnames will find also variables not 'instantiated' yet and therefore not included in the hostvars.

Resources