Log variables to a logfile on ansible host - ansible

I have a playbook that registers three variables. I want to produce a CSV report of those three variables on all hosts in my inventory.
This SO answer suggests to use:
- local_action: copy content={{ foo_result }} dest=/path/to/destination/file
But that does not append to the csv file.
Also, I have to manually compose by comma separators in this case.
Any ideas on how to log (append) variables to a local file?

If you are wanting to append a line to a file rather than replace it's contents then this is probably best suited to the lineinfile module and utilising the module's ability to insert a line at the end of the file.
The equivalent task to the copy one that you used would be something like:
- name: log foo_result to file
lineinfile:
line: "{{ foo_result }}"
insertafter: EOF
dest: /path/to/destination/file
delegate_to: 127.0.0.1
Note that I've used the long hand for delegating tasks locally rather than local_action. I personally feel that the syntax reads a lot clearer but you could easily use the following instead if you prefer the more compact syntax of local_action:
- local_action: lineinfile line={{ foo_result }} insertafter=EOF dest=/path/to/destination/file

Related

How to substitute values to a conf file using Ansible

I have an ansible playbook that has created a file (/tmp/values.txt) in each server with the following content
1.1.1.1
2.2.2.2
3.3.3.3
4.4.4.4
5.5.5.5
And I have a conf file named etcd.conf in the /tmp/etcd.conf with the following content
and I need to substitute the value of each line from /tmp/values.txt, so /tmp/etcd.conf will look like this:
SELF_IP=1.1.1.1
NODE_1_IP=2.2.2.2
NODE_2_IP=3.3.3.3
NODE_3_IP=4.4.4.4
NODE_4_IP=5.5.5.5
Is there a way I can do this? I used the following lookup method but it only works in the controll server
- name: Create etcd.conf
template:
src: etcd.conf.j2
dest: /tmp/etcd.conf
vars:
my_values: "{{ lookup('file', '/tmp/values.txt').split('\n') }}"
my_vars: [SELF_IP, NODE_1_IP, NODE_2_IP, NODE_3_IP, NODE_4_IP]
my_dict: "{{ dict(my_vars|zip(my_values)) }}"
You can use the slurp: task to grab the content for each machine, or use command: cat /tmp/values.txt and then examine the stdout_lines of its register:-ed variable to achieve that same .split("\n") behavior (I believe you'll still need to use that .split call after | b64decode if you use the slurp approach)
What will likely require some massaging is that you'll need to identify the group (which may very well include all) of inventory hostnames for which that command will produce content, and then grab the hostvar out of them to get all values
Conceptually, similar to [ hostvars[hv].tmp_values.stdout_lines for hv in groups[the_group] ] | join("\n"), but it'll be tedious to write out, so I'd only want to do that if you aren't able to get it to work on your own

Ansible - Log stdout lines from each remote host to a single file on the local server

Is there an easy way to log output from multiple remote hosts to a single file on the server running ansible-playbook?
I have a variable called validate which stores the output of a command executed on each server. I want to take validate.stdout_lines and drop the lines from each host into one file locally.
Here is one of the snippets I wrote but did not work:
- name: Write results to logfile
blockinfile:
create: yes
path: "/var/log/ansible/log"
insertafter: BOF
block: "{{ validate.stdout }}"
delegate_to: localhost
When I executed my playbook w/ the above, it was only able to capture the output from one of the remote hosts. I want to capture the lines from all hosts in that single /var/log/ansible/log file.
One thing you should do is to add a marker to the blockinfile to wrap the result from each single host in a unique block.
The second problem is that the tasks would run in parallel (even with delegate_to: localhost, because the loop here is realised by the Ansible engine) with effectively one task overwriting the other's /var/log/ansible/log file.
As a quick workaround you can serialise the whole play:
- hosts: ...
serial: 1
tasks:
- name: Write results to logfile
blockinfile:
create: yes
path: "/var/log/ansible/log"
insertafter: BOF
block: "{{ validate.stdout }}"
marker: "# {{ inventory_hostname }} {mark}"
delegate_to: localhost
The above produces the intended result, but if serial execution is a problem, you might consider writing your own loop for this single task (for ideas refer to support for "serial" on an individual task #12170).
Speaking of other methods, in two tasks: you can concatenate the results into a single list (no issue with parallel execution then, but pay attention to delegated facts) and then write to a file using copy module (see Write variable to a file in Ansible).

Ansible Lineinfile insert after syntax issues

Having some issues with my code and can't figure out the syntax error.
I'm trying to take output from a firewall and put it into an html file but i keep getting stuck. The command in its own separate file outputs a couple of lines of text. Note destination changed to protect it.
- hosts: firewall
gather_facts: no
tasks:
- name: Hardware Info
raw: show asset all
register: output
- local_action: lineinfile dest=dest destination regexp="{{item}}" insertafter="test" line={{item}}
with_items: output.stdout_lines
Error:
local_action: lineinfile dest=dest insertafter="test"
line='{{item}}'
^ here We could be wrong, but this one looks like it might be an issue with missing quotes. Always quote template expression brackets
when they start a value. For instance:
with_items:
{{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
Change this line:
- local_action: lineinfile dest=dest destination regexp="{{item}}" insertafter="test" line={{item}}
To this:
- local_action: lineinfile dest=dest destination regexp="{{item}}" insertafter="test" line="{{item}}"
The extra quotes should help.
Note lineinfile is terrible from a configuration management point of view, I predict you'll pull your hair out in the future.

Is it possible to use inline templates?

I need to create a single file with the contents of a single fact in Ansible. I'm currently doing something like this:
- template: src=templates/git_commit.j2 dest=/path/to/REVISION
My template file looks like this:
{{ git_commit }}
Obviously, it'd make a lot more sense to just do something like this:
- inline_template: content={{ git_revision }} dest=/path/to/REVISION
Puppet offers something similar. Is there a way to do this in Ansible?
Another option to the lineinfile module (as given by udondan's answer) would be to use the copy module and specify the content rather than a source local to the Ansible host.
An example task would look something like:
- name: Copy commit ref to file
copy:
content: "{{ git_commit }}"
dest: /path/to/REVISION
I personally prefer this to lineinfile as for me lineinfile should be for making slight changes to files that are already there where as copy is for making sure a file is in a place and looking exactly like you want it to. It also has the benefit of coping with multiple lines.
In reality though I'd be tempted to make this a template task and just have a the template file be:
"{{ git_commit }}"
Which gets created by this task:
- name: Copy commit ref to file
template:
src: path/to/template
dest: /path/to/REVISION
It's cleaner and it's using modules for exactly what they are meant for.
Yes, in that simple case it is possible with the lineinfile module.
- lineinfile:
dest=/path/to/REVISION
line="{{ git_commit }}"
regexp=".*"
create=yes
The lineinfile module usually is used to ensure that a specific line is contained inside a file. The create=yes option will crete the file if it does not exist. The regexp=.* option makes sure you do not add content to the file if git_commit changes, because it would by default simply make sure the new content is added to the file and not replace the previous content.
This only works since you only have one line in your file. If you'd had more lines this obviously would not work with this module.
This issue seems to be resolved. However, if the template file was more than one variable, i.e. a json file, it is possible to use copy module with content parameter, with a lookup, i.e.:
# playbook.yml
---
- name: deploy inline template
copy:
content: '{{ lookup("template", "inlinetemplate.yml.j2") }}'
dest: /var/tmp/inlinetempl.yml
# inlinetemplate.yml.j2
---
- name: {{ somevar }}
abc: def
If you need insert the template to the exist file, you can insert through the lineinfile module.
- name: Insert jinja2 template to the file
lineinfile:
path: /path/file.conf
insertafter: "after this line"
line: "{{ lookup('template', 'template.conf.j2') }}"

How do I loop over each line inside a file with ansible?

I am looking for something that would be similar to with_items: but that would get the list of items from a file instead of having to include it in the playbook file.
How can I do this in ansible?
I managed to find an easy alternative:
- debug: msg="{{item}}"
with_lines: cat files/branches.txt
Latest Ansible recommends loop instead of with_something. It can be used in combination with lookup and splitlines(), as Ikar Pohorský pointed out:
- debug: msg="{{item}}"
loop: "{{ lookup('file', 'files/branches.txt').splitlines() }}"
files/branches.txt should be relative to the playbook
Lets say you have a file like
item 1
item 2
item 3
And you want to install these items. Simply get the file contents to a variable using register. And use this variable for with_items. Make sure your file has one item per line.
---
- hosts: your-host
remote_user: your-remote_user
tasks:
- name: get the file contents
command: cat /path/to/your/file
register: my_items
- name: install these items
pip: name:{{item}}
with_items: my_items.stdout_lines
I am surprised that nobody mentioned the ansible Lookups, I think that is exactly what you want.
It reads contents that you want to use in your playbook but do not want to include inside the playbook from files, pipe, csv, redis etc from your local control machine(not from remote machine, that is important, since in most cases, these contents are alongside your playbook on your local machine), and it works with ansible loops.
---
- hosts: localhost
gather_facts: no
tasks:
- name: Loop over lines in a file
debug:
var: item
with_lines: cat "./files/lines"
with_lines here is actually loop with lines lookup, to see how the lines lookup works, see the code here, it just runs any commands you give it(so you can give it any thing like echo, cat etc), then split the output into lines and return them.
There are many powerful lookups, to get the comprehensive list, check out the lookup plugins folder.

Resources