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') }}"
Related
I wanted to log content of a variable in a file. I did it like this,
- name: write infraID to somefile.log
copy:
content: "{{ infraID.stdout }}"
dest: somefile.log
And it worked. But in the documentation for copy here, I found that they have recommended to use template instead of copy in such cases.
For advanced formatting or if content contains a variable, use the
ansible.builtin.template module.
I went through the examples given at the documentation for template module here. But I was unable to figure out something that works in my scenario. Could you please show me how to do this properly in recommended way ?
Thanks in advance !! Cheers!
The template module does not have a content property.
You have to create a file that contains your template, for example:
templates/infra-id-template
{{ infraID.stdoud }}
playbook
---
- hosts: localhost
tasks:
- name: Get infra ID
shell: echo "my-infra-id"
register: infraID
- name: Template the file
template:
src: infra-id-template
dest: infra-id-file
While working with Ansible modules, More notably assemble and blockinfile, I've noticed a few drawbacks to both modules. It could be lack of education on either module, or an intentional design.
Assemble
For example, when working with assemble, I can read in a directory of files, and 'assemble' them into one configuration file, like so:
assemble:
src: <path to directory of files>
dest: <destination>
The result is expected, one configuration file comprised of all files included in src. However, when using variables, they are not expanded. Which is also expected. I could use a lookup, which would expand these variables, however I am unaware of such a lookup that would include one directory for use with assemble.
Blockinfile
When working with blockinfile, I can use lookup to expand variables in my source file.
blockinfile:
create: yes
block: "{{ lookup('template', '<path to file>') }}"
dest: <destination>
marker: "# {mark} Test "
backup: yes
I could also include multiple files like this:
blockinfile:
create: yes
block: "{{ lookup('template', \"<path>/{{ item }}\") }}"
dest: <destination>
marker: "# {mark} Test {{ item }}"
backup: yes
with_items:
- file1.j2
- file2.j2
However the drawback with this solution would be that if a source template file is removed, it is not removed from the file specified in dest.
The End Goal
The goal that I would like to achieve would appear as
Read in a directory of files, and assemble them into one file.
Support for variables. When {{ variable }} is in the source file, have it expand to its value
When one or more source files is absent after previously adding it into the destination file, have it removed from the destination file. Also have the state report as changed.
You could use a main template that include others templates.
Jinja documentation for include
An exemple of main.j2:
My first block
{% include block1.j2 %}
My second block
{% include block2.j2 %}
I'm writing an Ansible role where I have some templates that must be present multiple times with different names in a single destination directory. In order not to have to handle each of these files separately I would need to be able to apply templating or some other form of placeholder substitution also to their names. To give a concrete example, I might have a file named
{{ Client }}DataSourceContext.xml
which I need to change into, say,
AcmeDataSourceContext.xml
I have many files of this kind that have to be installed in different directories, but all copies of a single file go to the same directory. If I didn't need to change their names or duplicate them I could handle a whole bunch of such files with something like
- name: Process a whole subtree of templates
template:
src: "{{ item.src }}"
dest: "/path/to/{{ item.path }}"
with_filetree: ../templates/my-templates/
when: item.state == 'file'
I guess what I'd like is a magic consider_filenames_as_templates toggle that turned on filename preprocessing. Is there any way to approximate this behaviour?
Pretty much anywhere you can put a literal value in Ansible you can instead substitute the value of a a variable. So for example, you could do something like this:
- template:
src: sometemplate.xml
dest: "/path/to/{{ item }}DataSourceContext.xml"
loop:
- client1
- client2
This would end up creating templates
/path/to/client1DataSourceContext.xml and
/path/to/client2DataSourceContext.xml.
Update 1
For the question you've posed in your update:
I guess what I'd like is a magic consider_filenames_as_templates toggle that turned on filename preprocessing. Is there any way to approximate this behaviour?
It seems like you could just do something like:
- name: Process a whole subtree of templates
template:
src: "{{ item.src }}"
dest: "/path/to/{{ item.path.replace('__client__', client_name) }}"
with_filetree: ../templates/my-templates/
when: item.state == 'file'
That is, replace the string __client__ in your filenames with the
value of the client_name variable.
In order to perform some operations locally (not on the remote machine), I need to put the content of an ansible variable inside a temporary file.
Please note that I am looking for a solution that takes care of generating the temporary file to a location where it can be written (no hardcoded names) and also that takes care of the removal of the file as we do not want to leave things behind.
You should be able to use the tempfile module, followed by either the copy or template modules. Like so:
- hosts: localhost
tasks:
# Create a file named ansible.{random}.config
- tempfile:
state: file
suffix: config
register: temp_config
# Render template content to it
- template:
src: templates/configfile.j2
dest: "{{ temp_config.path }}"
vars:
username: admin
Or if you're running it in a role:
- tempfile:
state: file
suffix: config
register: temp_config
- copy:
content: "{{ lookup('template', 'configfile.j2') }}"
dest: "{{ temp_config.path }}"
vars:
username: admin
Then just pass temp_config.path to whatever module you need to pass the file to.
It's not a great solution, but the alternative is writing a custom module to do it in one step.
Rather than do it with a file, why not just use the environment? This wan you can easily work with the variable and it will be alive through the ansible session and you can easily retrieve it in any steps or outside of them.
Although using the shell/application environment is probably, if you specifically want to use a file to store variable data you could do something like this
- hosts: server1
tasks:
- shell: cat /etc/file.txt
register: some_data
- local_action: copy dest=/tmp/foo.txt content="{{some_data.stdout}}"
- hosts: localhost
tasks:
- set_fact: some_data="{{ lookup('file', '/tmp/foo.txt') }}"
- debug: var=some_data
As for your requirement to give the file a unique name and clean it up at the end of the play. I'll leave that implementation to you
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