163

In my Ansible playbook many times i need to create a file:

- name: Copy file
  template:
    src: code.conf.j2
    dest: "{{ project_root }}/conf/code.conf"

Many times conf dir is not there. Then I have to create another task to create that dir first.

Is there any easy way to auto create the dir if it doesn't exist with some option?

Kevin C
  • 4,851
  • 8
  • 30
  • 64
user1994660
  • 5,253
  • 11
  • 30
  • 33
  • Possible duplicate of [How to create a directory using Ansible?](http://stackoverflow.com/questions/22844905/how-to-create-a-directory-using-ansible) – BMitch Jun 22 '16 at 18:30
  • 2
    I cant believe this is still an issue in ansible 2.9. How hard would it be for these modules to have a create dir toggle, defaulted to off. It would save so much cruft in the playbooks, having to deal with the pretests like the answers below – krad Feb 04 '20 at 10:28

8 Answers8

174

Right now, this is the only way:

- name: ensures {{ project_root }}/conf dir exists
  file: 
    path: "{{ project_root }}/conf"
    state: directory

- name: Copy file
  template:
    src: code.conf.j2
    dest: "{{ project_root }}/conf/code.conf"
Kevin C
  • 4,851
  • 8
  • 30
  • 64
Alexander Jardim
  • 2,406
  • 1
  • 14
  • 22
  • 2
    I think ensure is the correct word here. Assure: "tell someone something positively to dispel any doubts", ensure: "make certain that (something) will occur or be the case". – Daniel Compton Jan 05 '16 at 06:30
  • That seems to check if the directorty exists, but it doesn't seem to create it. – hookenz Feb 10 '16 at 03:32
  • 13
    Should note that you may want to add `recurse=yes` to the `file` call to get `mkdir -p` type behavior – Mitch Feb 10 '16 at 19:45
  • 1
    Another piece of the puzzle that can be helpful is the [ansible-specific `dirname` filter](http://docs.ansible.com/ansible/playbooks_filters.html#other-useful-filters) to get the directory which contains a file. – Caspar Mar 04 '16 at 09:41
  • Is it best practice to stat the dir to see if it exists first? – radtek Oct 26 '17 at 01:46
  • 1
    @radtek the file module already does it with state=directory. It would fail if it is not possible to ensure that the proposed file path points to a directory. – Alexander Jardim Nov 07 '17 at 18:35
39

If you are running Ansible >= 2.0 there is also the dirname filter you can use to extract the directory part of a path. That way you can just use one variable to hold the entire path to make sure both tasks never get out of sync.

So for example if you have playbook with dest_path defined in a variable like this you can reuse the same variable:

---
- hosts: my_hosts
  vars:
    dest_path: /home/ubuntu/some_dir/some_file.txt
  tasks:
    - name: Make sure destination dir exists
      file:
        path: "{{ dest_path | dirname }}"
        state: directory

    # now this task is always save to run no matter how dest_path get's changed around
    - name: Add file or template to remote instance
      template: 
        src: foo.txt.j2
        dest: "{{ dest_path }}"
Kevin C
  • 4,851
  • 8
  • 30
  • 64
Stefan Horning
  • 1,117
  • 13
  • 17
  • 2
    while this solution is still verbose, I think it's closest to the author's intent. I would *love* it if there was a simple 'makedirs' parameter like Salt has: https://docs.saltstack.com/en/latest/ref/states/all/salt.states.file.html#salt.states.file.managed – lsh Aug 16 '18 at 05:31
  • Avoid `recurse` (see my comment above re: file permissions) – nyet Aug 19 '20 at 22:37
  • made an edit to remove the `recurse`, this answer seems the best one to me aside from that point – Andrew Spencer Jun 15 '21 at 10:04
25

To ensure success with a full path use recurse: true:

- name: ensure custom facts directory exists
  file:
    path: /etc/ansible/facts.d
    recurse: true
    state: directory
Kevin C
  • 4,851
  • 8
  • 30
  • 64
Ian T Price
  • 275
  • 3
  • 2
  • 3
    According to the documentation (and my tests), the subdirectories are always created, and `recurse=yes` only applies permissions recursively. However, the documentation states that this happens automatically since v1.7, so `recurse` might well be obsolete. – To마SE Oct 25 '16 at 11:45
  • 3
    `recurse=yes` has a bad side effect that if you supply mode, files get the mode too (e.g. 0755). It also isn't required: ```If directory, all intermediate subdirectories will be created if they do not exist. Since Ansible 1.7 they will be created with the supplied permissions.``` – nyet Aug 19 '20 at 22:35
  • This parameter is no more supported: Unsupported parameters for (ansible.legacy.copy) module: recurse - Ansible 2.10 – Cyril Jul 11 '23 at 09:53
23

According to the latest document when state is set to be directory, you don't need to use parameter recurse to create parent directories, file module will take care of it.

- name: create directory with parent directories
  file:
    path: /data/test/foo
    state: directory

This is fare enough to create the parent directories data and test with foo.
Please refer the parameter description - "state" of the file_module.

Kevin C
  • 4,851
  • 8
  • 30
  • 64
Albie
  • 331
  • 2
  • 4
9

AFAIK, the only way this could be done is by using the state=directory option. While template module supports most of copy options, which in turn supports most file options, you can not use something like state=directory with it. Moreover, it would be quite confusing (would it mean that {{project_root}}/conf/code.conf is a directory ? or would it mean that {{project_root}}/conf/ should be created first.

So I don't think this is possible right now without adding a previous file task.

- file: 
    path: "{{project_root}}/conf"
    state: directory
    recurse: yes
leucos
  • 17,661
  • 1
  • 44
  • 34
3

you can create the folder using the following depending on your ansible version.

Latest version 2<

- name: Create Folder
  file: 
   path: "{{project_root}}/conf"
   recurse: yes
   state: directory

Older version:

- name: Create Folder
  file: 
      path="{{project_root}}/conf"
      recurse: yes
      state=directory

Refer - http://docs.ansible.com/ansible/latest/file_module.html

-1

The question says "copy" but it then specifically ask for "template" file creation.

For using template:, I believe a preceding step is required, as answered by many others here already.

But for copy: however (plain copying over things, no templating) then you can do it in one instead of two steps, simply by copying the full directory instead an individual file:

- name: SBT repo configuration
  copy:
    src: files/.sbt/ # Copying the file `./files/.sbt/repositories`
    dest: $HOME/.sbt/ # Works irrespective of whether the directory exists or not

Other files already present in $HOME/.sbt/ are unaffected.

conny
  • 9,973
  • 6
  • 38
  • 47
  • you're not answering the question: he wants to automatically create directories in the path. in your case `$HOME` already exists and you're copying a folder into another folder for which ansible will create the folder aumatically. It's an entirely different scenario. – Eric Sep 12 '21 at 17:57
-3

copy module creates the directory if it's not there. In this case it created the resolved.conf.d directory

- name: put fallback_dns.conf in place                                                                 
  copy:                                                                                                
    src: fallback_dns.conf                                                                             
    dest: /etc/systemd/resolved.conf.d/                                                                
    mode: '0644'                                                                                       
    owner: root                                                                                        
    group: root                                                                                        
  become: true                                                                                         
  tags: testing
  • 2
    From the [copy](https://docs.ansible.com/ansible/latest/modules/copy_module.html) module: If `src` and `dest` are files, the parent directory of `dest` is not created and the task fails if it does not already exist. – xref Aug 19 '20 at 23:13
  • @xref the question asked is how to create a non existing destination directory, not a file like you are saying. – Patrick Brunswyck Oct 17 '20 at 14:58
  • 1
    @PatrickBrunswyck you obviously haven't read the comment correctly. The *directory* isn't going to be created, not the files. Your answer seems wrong. Moreover, you probably haven't tested yourself. I've justed tested it, and no, it doesn't work, it complains that the directory doesn't exist: {"changed": false, "checksum": "438d05237dc5b2b294f1f5a651692403334b1aef", "msg": "Destination directory /etc/docker does not exist – Lethargos Aug 30 '21 at 17:21