How to create a file with a single line? - ansible

The documentation for file operations does not seem to mention a module which would allow me to create a file with one line. The closest is lineinfile but this module also inserts markers (so a minimum of three lines).
The line will be generated from variables so I do not want to use a local file transferred to the target.
Is there such a module? Or should I run a shell command such as
command: echo {{ myvariable }} > the_file_to_create
to generate it?

You can do it easily with copy module using force=no to create a new empty file only when the file does not yet exist (if the file exists, it's content is preserved)
- name: Copy the content
copy:
content: "{{ myvariable }}"
dest: /location/of/the/file
force: no
Hope that might help you.

Related

Generate Ansible template variable only once

Context
I have an Ansible template that creates a configuration file. Most variables used by this template come from some stable source, such as variables defined in the playbook. However one variable contains a secret key which somehow needs to be generated if it does not already exist.
I want Ansible to generate the key on an as-needed basis. I do not want the key to be stored on the OS running Ansible, like is done with the password module. And naturally I want idempotence.
I currently have a working solution but I'm not happy with it, hence my question here. My current solution uses an extra file which is include by the configuration file created by my template. This is possible as it is a PHP file. I have a task using the shell module that generates the secret when this extra file does not exist, and then another that then creates it using the registered variable.
- name: Generate secret key
shell: openssl rand -hex 32
register: secret_key_command
when: not SecretKey.stat.exists
- name: Create SecretKey.php
template:
src: "SecretKey.php.j2"
dest: "{{ some_dir }}/SecretKey.php"
when: not SecretKey.stat.exists
Surely there is a less contrived way to do this?
Question
Is there a nice way to have a variable that gets generated only once in an Ansible template?
I am not sure, but I understood correctly, we want to generate your template if it doesn't already exists. So you can just do as follow:
- name: Create SecretKey.php
template:
src: "SecretKey.php.j2"
dest: "{{ some_dir }}/SecretKey.php"
force: no
force: no tells to don't overwrite a file if it already exists. No need to do extra check.

Ansible lookup file plugin with variable inside the file

Hei guys,
After a few days of struggling, I've decided to write my issue here.
I have an ansible(2.7) task that that has a single variable, which points to a host var that uses the file lookup plugin.
Thing is that this works, for one host, but I have 6 hosts, where a value inside the lookup file should be different for each of the hosts.
Can you pass a variable inside the file that is looked up?
I'm new to ansible and don't master it fully.
Has someone encountered this before?
Task:
- name: Copy the file to its directory
template:
src: file.conf
dest: /path/to/file
vars:
file_contents: "{{file_configuration}}"
-----
hostvar file:
file_configuration:
- "{{lookup('file', './path/to/file') | from_yaml}}"
----
file that is looked up:
name: {{ value that should be different per host }}
driver:
long_name: unchanged value.
You should have 6 host_vars files, one for each host. In that host_var file, set your desired value.
Ansible documentation is here
E.g.
https://imgur.com/a/JCbnNBT
Content of host1.yml
---
my_value: something
Content of host2.yml
---
my_value: else
Ansible automagicly sees the host_var folder. It looks in that folder and searches for files which exactly match a host in the play.
So ensure your host_var/filename.yml matches the hostname in your play!
If there is a match, then it'll use that .yml file for that specific host.

Replace string with hostname

I've been searching up and down for a way to replace a string in a file with the contents of ansible_hostname. Say I have something similar to this in a configuration file:
kern.* /syslog/SYSLOG_SERVER/kern.log
auth.* /syslog/SYSLOG_SERVER/auth.log
Is there an easy way in Ansible to replace all occurrences of SYSLOG_SERVER with the hostname of the server the configuration file is copied to? If anyone has an example or a link describing how to do this I would be super appreciative if you shared it.
You can use the replace module:
- replace:
dest: /path/to/configuration/file
regexp: SYSLOG_SERVER
replace: '{{ ansible_hostname }}'
backup: yes
backup parameter is optional, but since you will be experimenting at first, it is safer to leave it. You can leave it out once you have established the correct procedure.
If the file is already out on the destination server you can use the lineinfile module to make sure the contents are what you want.
If the file is distributed/updated as part of a playbook run you can use the template module to dynamically inject the destionation node's hostname as it gets copied to the destionation.

Log variables to a logfile on ansible host

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

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') }}"

Resources