63

Is there an easy way to deploy a folder full of template .j2 folder to a linux box, using the same name as the template, but without the .j2 extension, rather than using the template module for each file?

Right now i have a long list of:

- name: create x template
  template:
    src=files/x.conf.j2
    dest=/tmp/x.conf
    owner=root
    group=root
    mode=0755
  notify:
    - restart myService
dawud
  • 15,096
  • 3
  • 42
  • 61
Trololololol
  • 733
  • 1
  • 5
  • 6

8 Answers8

101

You could use with_fileglob to get the list of files from your template directory and use filters to strip the j2 extension like this:

- name: create x template
  template:
    src: "{{ item }}"
    dest: /tmp/{{ item | basename | regex_replace('\.j2$', '') }}
  with_fileglob:
    - ../templates/*.j2
Margaret
  • 7
  • 2
Russell
  • 1,158
  • 3
  • 9
  • 8
  • 14
    note `with_fileglob` always operates from `files/`, you can get to templates with `../templates/mytemplate/*`. http://stackoverflow.com/a/27407566/1695680 – ThorSummoner May 13 '15 at 20:10
  • 6
    Thanks, this is super helpful. I found that I had to use two backslashes to escape the literal period in the regex_replace function. Maybe because I had the entire dest template portion within double quotes so I could use YAML format for the task definition (which I prefer over the one-liner format). – Tony Cesaro Mar 31 '16 at 23:03
  • 1
    this assumes only files live within the templates folder, if you need to support both directories and files within the templates folder then you need with_filetree - see http://stackoverflow.com/questions/41667864/can-the-templates-module-handle-multiple-templates-directories – danday74 Jan 16 '17 at 02:02
  • 7
    Remember to escape \ if you are wrapping the whole value in double quote. `dest: "/tmp/{{ item | basename | regex_replace('\\.j2$', '') }}"` – tom10271 Nov 12 '20 at 07:43
22

Michael DeHaan(creator of Ansible) made a post on CoderWall that talks about very similar issue. You can adjust and expand it according to your needs(such as permissions and ownership). Relevant part of the post is here:


This can be simplified by using "with_items" and a single notify statement. If any of the tasks change, the service will be notified in exactly the same way that it needs to restart at the end of the playbook run.

 - name:  template everything for fooserv
   template: src={{item.src}} dest={{item.dest}}
   with_items:
      - { src: 'templates/foo.j2', dest: '/etc/splat/foo.conf' }
      - { src: 'templates/bar.j2', dest: '/etc/splat/bar.conf' }
   notify: 
      - restart fooserv

Note that since we have tasks that take more than one unique argument, we don't just say "item" in the 'template:' line, but use with_items with a hash (dictionary) variable. You can also keep it a little shorter by using lists, if you like. This is a stylistic preference:

 - name:  template everything for fooserv
   template: src={{item.0}} dest={{item.1}}
   with_items:
      - [ 'templates/foo.j2', '/etc/splat/foo.conf' ]
      - [ 'templates/bar.j2', '/etc/splat/bar.conf' ]
   notify: 
      - restart fooserv

Of course we could also define the list you were walking over in another file, like a "groupvars/webservers" file to define all the variables needed for the webservers group, or a YAML file loaded from the "varsfiles" directive inside the playbook. Look how this can clean up if we do.

- name: template everything for fooserv
  template: src={{item.src}} dest={{item.dest}}
  with_items: {{fooserv_template_files}}
  notify: 
      - restart fooserv
Mxx
  • 2,362
  • 2
  • 28
  • 40
  • 6
    A simpler method might be to write `template: src=templates/{{item}}.j2 dest=/etc/splat/{{item}}.conf`, and then use a plain list of items: `with_items: - foo - bar` – Ethan Jan 06 '15 at 03:47
  • This actually looks wrong now. The correct template line would be `template: src={{item.src}} dest={{item.dest}}` (i.e. not `${var}` but rather `{{var}}`) – Fabiano Francesconi Nov 13 '15 at 13:45
  • @FabianoFrancesconi updated. – Mxx Nov 13 '15 at 15:01
  • item.0 actually just gets f from - [ 'foo.bar' , 'foo.baz' ] instead of foo.bar. Which ansible version are you using? https://github.com/ansible/ansible/issues/5913 – Bato-Bair Tsyrenov Apr 23 '20 at 18:23
15

I wrote a filetree lookup plugin that can help with actions on file trees.

You can recurse over files in a file tree and based on file properties perform actions (e.g. template or copy). Since the relative path is returned, you can recreate the filetree on the target system(s) with ease.

- name: Template complete tree
  template:
    src: '{{ item.src }}'
    dest: /web/{{ item.path }}
    force: yes
  with_filetree: some/path/
  when: item.state == 'file'

It makes for more readable playbooks.

If you need to create directories first

In most cases we have files inside other directories, in this scenario we need to create the directory first and then copy files. Exactly like what is described in the official documentation

Kasir Barati
  • 103
  • 4
Dag Wieers
  • 346
  • 2
  • 7
  • It's not been merged yet :-( – Morgan Christiansson May 24 '16 at 11:32
  • 2
    It has been merged. – Dag Wieers Sep 26 '16 at 18:32
  • Is there a way to filter say only *.conf files? – Andrei May 12 '17 at 07:20
  • Sure, in the "when:" part you can write an expression that fits your need. – Dag Wieers May 13 '17 at 12:36
  • Warning: This plugin is too slow to be useful for a large number of small files (e.g. a few thousand). That said, it works really well otherwise (although its output ends up being very verbose). – Reid Jun 23 '17 at 17:20
  • 1
    The plugin is not slow, it's the process of templating and copying each file individually that makes it slow. But that has hardly anything to do with the plugin, the plugin could is useful for other things than templating or copying. – Dag Wieers Jun 24 '17 at 18:59
  • The plugin does not control the verbosity of Ansible, the user does. The plugin returns information for each file that allows to do a number of things, there is no other way the plugin could work (or offer the functionality). If you don't need any of the functionality, don't use the plugin but use with_fileglob instead. It's as simple as that. – Dag Wieers Jun 24 '17 at 19:02
  • The means by which the plugin interacts with Ansible makes it unusably slow for a filetree with thousands of files. With a large filetree, there is no conceivable situation in which it won't be several orders of magnitude slower than hacking together some ugly commands. I like your plugin a lot — it's flexible and clean and I really wanted to use it — but for _large_ file trees, it's not really an option. I don't mean this as disparagement, but simply fact. Ansible has tied your hands here. – Reid Jul 20 '17 at 15:00
  • ... and if you think the user controls the verbosity of the output, I challenge you to find a way to make Ansible _not_ output a line of text for every file processed. – Reid Jul 20 '17 at 15:00
  • 1
    @DagWieers is there a way to create folders in the filetree at the destination? – Quintin Par Aug 13 '18 at 04:54
  • @Reid I wrote stdout_callback plugin named "dense", which basically does that. There are others. – Dag Wieers Aug 15 '18 at 19:23
  • @QuintinPar The [filetree documentation](https://docs.ansible.com/ansible/devel/plugins/lookup/filetree.html) has an example on how to do this. – Dag Wieers Aug 15 '18 at 19:25
  • @Dag Wieers, can you please have a look at https://stackoverflow.com/questions/66901582/ansible-debug-and-with-filetree Sorry for asking directly here. – Alexandr Apr 01 '21 at 15:06
9

The answer by Russel does work but it needs improvement

- name: create x template
- template: src={{ item }} dest=/tmp/{{ item | basename | regex_replace('.j2','') }}
- with_fileglob:
   - files/*.j2

Firs of all the $ needs to go as it was wrong regex in the regex_replace . Secondly all the files should be in the files directory rather than templates directory

HJ_VORTEX
  • 91
  • 1
  • 2
  • 5
    Welcome to Server Fault! Your answer suggests a workable solution to the question is available through a previous answer, thus would be more appropriate as an edit of that answer. Please consider deleting your current answer and suggesting an edit to Russell's answer. – Paul Jan 16 '15 at 17:31
4

The below command worked for me to do a recursive lookup for j2 files in templates and move it to the destination. Hope it helps someone looking for recursive copy of templates to destination.

     - name: Copying the templated jinja2 files
       template: src={{item}} dest={{RUN_TIME}}/{{ item | regex_replace(role_path+'/templates','') | regex_replace('\.j2', '') }}
       with_items: "{{ lookup('pipe','find {{role_path}}/templates -type f').split('\n') }}"
Robin
  • 41
  • 1
3

There is a possibility to grab the list of actual files from the directory automatically and iterate them afterwards..

- name:         get the list of templates to transfer
  local_action: "shell ls templates/* | sed 's~.*/~~g'"
  register:     template_files

- name:         iterate and send templates
  template:     src=templates/{{ item }} dest=/mydestination/{{ item }}
  with_items:
  - "{{ template_files.stdout.splitlines() }}"
  • 1
    Note the standard caveat about splitting on newline - filenames may contain newlines. A safer solution is to use a shell utility that supports `print0`, such as `find`, and then split on `\u0000`. – Dejay Clayton Aug 14 '17 at 02:44
0

I did it and it worked. \o/

- name: "Create file template"
  template:
    src: "{{ item.src }}"
    dest: "{{ your_dir_remoto }}/{{ item.dest }}"
  loop:
    - { src: '../templates/file1.yaml.j2', dest: 'file1.yaml' }
    - { src: '../templates/file2.yaml.j2', dest: 'file2.yaml' }
0

This perfectly recreated all directories and files in the target system:

Important a full path must be provided to the directory you wish to copy. Relative assumptions like 'sm_scripts' in this example would fail.

- set_fact:
    # Important a full path must be provided to the source directory. 
    # Relative assumptions like 'sm_scripts' in this example would fail.
    template_directory: "{{ roles_path }}/scripts_n_crons/templates/sm_scripts"
    destination_directory: /SM_DATA/sm_scripts3

- name: Create directories
  file:
    path: "{{ destination_directory }}/{{ item.path }}"
    state: directory
  when: item.state == 'directory'
  with_filetree: "{{ template_directory }}"

- name: Process template files
  template:
    src: "{{ item.src }}"
    dest: "{{ destination_directory }}/{{ item.path }}"
  when: item.state == 'file'
  with_filetree: "{{ template_directory }}"