ansible - read file, then randomly select entry - random

I'm using Ansible 2.9.10. I am trying to read a file in which consists of a list of items. I then want to choose 3 random items from this list. I've tried several things (noted below) but cannot seem to figure this out.
The file itemList is a list of items, one per line:
hammer
saw
wood
.....
In the playbook I have:
vars:
file_contents: "{{ lookup('file', 'itemList') }}"
# Printing file contents returns "widget\hammer\nsaw\nwood"..... etc.
- name: Print file contents
debug:
msg: "File contents are {{ file_contents }}"
# Now try to select one random item from file_contents
- name: Select one random item
debug:
msg: "item is {{ item }}"
with_random_choice: "{{ file_contents }}"
What am I missing? It seems as though I am making it harder than it seems. TIA

you are getting the file_contents as 1 line, as below:
"msg": "File contents are hammer\nsaw\nwood\npencil\nfork\nspoon\nmobile"
this is 1 item and not a list of items (since the end of line character is not identified and is presented as a string sequence of '\n'.
one easy tweak is to split the 1 line string using the '\n' delimiter. Modifying your last task directly, you would use:
# Now try to select one random item from file_contents
- name: Select one random item
debug:
msg: "item is {{ item }}"
with_random_choice: "{{ file_contents.split('\n') }}"
cheers

Related

How to store the contents of the file to a variable in ansible when the file contains new lines as well

I'm trying to store content of the certificate.pem file to a variable using the following task:
- name: Get the contents of the root certificate
shell: cat {{ ca_certificate_file }}
- name: Decode data and store as fact
set_fact:
root_certificate_content: "{{ ca_certificate_data.stdout }}"
The variable root_certificate_content has the entire content of the file but instead of a new line it is replacing it with a space. I there a way I can get the certificate content as it is in the variable.
Try lookup plugins
- set_fact:
root_certificate_content: "{{ lookup('file', ca_certificate_file) }}"
For example "the variable "root_certificate_content" should have the contents of the file as it is. If the file has a new line then it should come as the new line". The play below
- hosts: localhost
tasks:
- set_fact:
root_certificate_content: "{{ lookup('file', 'cert1') }}"
- debug:
msg: "{{ root_certificate_content.split('\n') }}"
with the file (3 lines with newline each)
$ cat cert1
line 1
line 2
line 3
gives the content of the variable root_certificate_content (3 lines with newline each)
"msg": [
"line 1",
"line 2",
"line 3"
]
"if you just show the value of root_certificate_content without using .split('\n') in the debug msg"
- debug:
var: root_certificate_content
then the newlines can be seen in the string
"root_certificate_content": "line 1\nline 2\nline 3"

Jinja templates in ansible loop

I need to run an ansible loop based on input from a CSV file. I am using the following question / answer as reference. However, I cannot seem to figure out where to actually include the jinja part for the loop.
So far this is what I have, but it throws an error:
---
- hosts: localhost
connection: local
gather_facts: no
vars:
csv_var: "{{ lookup ('file', 'file.csv') }}"
tasks:
- debug:
msg: "{{ item }}"
with_items:
- {% set list = csv_var.split(",") %}
file.csv has the following content: 345,1234,1234
Ideally the message should print out the numbers above.
The syntax error I was getting is:
The offending line appears to be:
with_items:
- {% set list = csv_var.split(",") %}
^ here
exception type: <class 'yaml.scanner.ScannerError'>
exception: while scanning for the next token
found character that cannot start any token
in "<unicode string>", line 19, column 10
You should use Jinja2 expression not a statement.
You should also quote any string that starts with { in Ansible:
- debug:
msg: "{{ item }}"
with_items: "{{ csv_var.split(',') }}"
And there is no need to wrap the resulting list in another list (dash before element), although Ansible handles this automatically.

Ansible - iterate over a list of dictionaries

I built the following list but I don't succeed to iterate over it.
Should I use with_items? with_elements? or something else?
My goal is to iterate over all the hosts in the inventory, get their name and their IP, and finally print it.
- set_fact:
list_of_hosts: |
{% set myList = [] %}
{% for host in groups['all'] %}
{% set ignored = myList.extend([{'server_name': host, 'server_ip': hostvars[host].ansible_eth0.ipv4.address }]) %}
{% endfor %}
{{ myList }}
- debug: msg="{{ item.server_name }}"
with_items: "{{ list_of_hosts }}"
Here is my list when I debug it:
TASK [common : debug] ************************************************************************************************
ok: [my1stServer] => {
"msg": " [{'server_ip': u'192.168.0.1', 'server_name': u'my1stServer'}, {'server_ip': u'192.168.0.2', 'server_name': u'my2ndServer'}]\n"
}
And here is the error but it is not really relevant :
fatal: [my1stServer]: FAILED! => {"failed": true, "msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'ansible.vars.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'server_name'\n\nThe error appears to have been in 'hosts.yml': line 19, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- debug: msg=\"{{ item.server_name }}\"\n ^ here\nWe could be wrong, but this one looks like it might be an issue with\nmissing quotes. Always quote template expression brackets when they\nstart a value. For instance:\n\n with_items:\n - {{ foo }}\n\nShould be written as:\n\n with_items:\n - \"{{ foo }}\"\n"}
Please forgive me for bluntness, but the proposed implementation makes it an effort to understand what the idea actually is. which is simple: to print some variables anyway present in hostvars[host] for a list of hosts picked by various criteria.
If we keep implementation close to that idea, implementation is simpler.
So what I'd do to create a list of hosts picked by group membership, or possibly 'hand picked' is to actually do what I just wrote :).
Consider this task list:
# this task creates an empty list
- name: create my_cool_list
set_fact:
my_cool_list: []
# this task adds to the list all hosts in groups we're iterating over
- name: update my cool list with whole groups
set_fact: '{{my_cool_list + groups[curr_grp]}}'
with_items:
- grp1
- grp2
loop_control:
loop_var: curr_grp
# this task adds to the list all hosts we're iterating over
- name: update my cool list with specific hosts
set_fact: '{{my_cool_list + [curr_node]}}'
with_items:
- node001
- node101
loop_control:
loop_var: curr_node
# now we can iterate over the list, accessing specific fields on each host
- name: go over my cool list and print ansible_init_mgr
debug:
msg: 'hostvars["{{curr_host}}"].ansible_init_mgr: {{hostvars[curr_host].ansible_init_mgr}}'
with_items: '{{my_cool_list|default([], true)|list}}'
Furthermore, you can add safety when: by validating the key you're accessing is defined..
And, to print a selection of variables about each host, you should use jinja filter map('extract',...):
- name: print some vars from each host
debug:
msg: {'server_name': '{{hostvars[curr_node]|selectattr("ansible_hostname")}}', 'ip_address': '{{hostvars[curr_node]|selectattr("ansible_eth0.ipv4.address")}}'}
with_items: '{{my_cool_list|default([], true)|list}}'
loop_control:
loop_var: curr_node
IF you want to increase readability though, you better write a filter plugin, which would do the above stuff, and hide iteration ugliness in readable way, so you can have:
Either (For generic approach, i.e. without renaming attributes)
- name: print some vars from each host
debug:
msg: '{{my_cool_list|multi_select_in_dict(["ansible_hostname", "ansible_eth0.ipv4.address")}}'
Or specific approach (so that you are using specific hard coded remapping of attributes...)
- name: print some vars from each host
debug:
msg: '{{my_cool_list|my_cool_filter}}'

Ansible list of lists - flattening

I am using a set_fact in a playbook to gather data using a regex_findall(). I'm pulling out two groups with the regex and the ending result becomes a list of lists.
set_fact: nestedList="{{ myOutput.stdout[0] | regex_findall('(.*?)\n markerText(.*)')}}"
A example dump of the list looks like:
[[a,b],[c,d],[e,f],[g,h]]
I need to iterate through the parent list, and take the two parts of each sub-list and use them together. I tried with_items, and with_nested but do not get the results I'm looking for.
Using the example above, in one loop pass I need to work with 'a' and 'b'. An example could be item.0 = 'a' and item.1 = 'b'. On the next loop pass, item.0 = 'c' and item.1 = 'd'.
I can't seem to get it correct when it is a list of lists like that.
If I take the list above and just output it, the 'item' iterates through every item in all of the sub-lists.
- debug:
msg: "{{ item }}"
with_items: "{{ nestedList }}"
The result from this looks like:
a
b
c
d
e
f
g
h
How can I iterate through the parent list, and use the items in the sub-lists?
You want to use with_list instead of with_items.
with_items forcefully flattens nested lists, while with_list feeds argument as is.
---
- hosts: localhost
gather_facts: no
vars:
nested_list: [[a,b],[c,d],[e,f],[g,h]]
tasks:
- debug: msg="{{ item[0] }} {{ item[1] }}"
with_list: "{{ nested_list }}"

convert dictionary keys in playbook

I have an existing playbook variable dictionary defined like:
vars:
resource_tags: {
Name: "some name"
Service: "some service"
}
This is used in various calls to tasks in this form. But in another task, I need it in a different format, and rather than have it hard-coded, I was wondering if it could be built in a task.
I need it to look like:
{
"tag:Name": "some name"
"tag:Service": "some service"
}
I tried iterating using with_dict and setting a fact with combine:
- set_fact:
ec2_remote_facts_filter: "{{ ec2_remote_facts_filter | default({}) | combine( { 'tag:'item.name: item.val } ) }}"
with_dict: "{{ ec2_count_resource_tags }}"
And obviously that doesn't work.
Is this even possible?
If you don't mind a bit of hackery:
- debug: msg="{{ resource_tags | to_json(indent=0) | regex_replace('\n\"','\n\"tag:') }}"
This will convert your dict into JSON-formatted string with indent=0, meaning each key will start from new line; then insert tag: after first double quote on every line.
Because the result is valid JSON, Ansible template engine will convert it back into dict as the last step of variable substitution, giving you:
ok: [localhost] => {
"msg": {
"tag:Name": "some name",
"tag:Service": "some service"
}
}
I suppose there may be some corner cases if there are newlines inside your values, but in general it should be fine.
Maybe you need a custom lookup plugin in your case.
1) Edit file ansible.cfg and uncomment key 'lookup_plugins' with value './plugins/lookup'
2) Create a plugin file named 'ec2remote.py' in './plugins/lookup'
3) Use it in your playbook:
- debug:
msg: "{{ item }}"
with_ec2remote: "{{ ec2_count_resource_tags }}"
4) Implements your ec2remote.py (many examples here)
class LookupModule(LookupBase):
def run(self, terms, **kwargs):
result = {}
for k,v in terms.items():
result["tag:"+k] = v
return result
Usually, I prefer to develop plugins that are easily usable and testable and thus preserve an understandable playbook.

Resources