147

What is the easiest way to create an empty file using Ansible? I know I can save an empty file into the files directory and then copy it to the remote host, but I find that somewhat unsatisfactory.

Another way is to touch a file on the remote host:

- name: create fake 'nologin' shell
  file: path=/etc/nologin state=touch owner=root group=sys mode=0555

But then the file gets touched every time, showing up as a yellow line in the log, which is also unsatisfactory...

Is there any better solution to this simple problem?

dokaspar
  • 8,186
  • 14
  • 70
  • 98

10 Answers10

245

The documentation of the file module says:

If state=file, the file will NOT be created if it does not exist, see the copy or template module if you want that behavior.

So we use the copy module, using force: false to create a new empty file only when the file does not yet exist (if the file exists, its content is preserved).

- name: ensure file exists
  copy:
    content: ""
    dest: /etc/nologin
    force: false
    group: sys
    owner: root
    mode: 0555

This is a declarative and elegant solution.

Kevin C
  • 4,851
  • 8
  • 30
  • 64
René Pijl
  • 4,310
  • 1
  • 19
  • 25
  • Great answer, was curious about how would one create two empty files using the same construct that you have provided? – Tasdik Rahman May 03 '17 at 09:47
  • Is there a way to make this create the parent directory if it doesn't exist, or do I need to do that separately? – falsePockets Feb 06 '19 at 05:32
  • You need to ensure the parent directory exists and is writable. See https://stackoverflow.com/questions/22844905/how-to-create-a-directory-using-ansible – René Pijl Feb 13 '19 at 13:26
  • 1
    This won't fix the permission if it's incorrect and file already exists. See https://github.com/ansible/ansible/issues/7490 ... unfortunately, seems only better way is 2.7+ only. – Honza Nov 12 '19 at 08:34
  • 1
    @Tasdik Rahman Simply set dest: "{{ item }}" and then use with_items: - /tmp/file1 - /tmp/file2 – michaelkrieger May 02 '23 at 00:09
41

Something like this (using the stat module first to gather data about it and then filtering using a conditional) should work:

- stat: 
    path: /etc/nologin
  register: p

- name: create fake 'nologin' shell
  file: 
    path: /etc/nologin
    state: touch 
    owner: root 
    group: sys 
    mode: 0555
    when: p.stat.exists is defined and not p.stat.exists

You might alternatively be able to leverage the changed_when functionality.

Kevin C
  • 4,851
  • 8
  • 30
  • 64
ceejayoz
  • 176,543
  • 40
  • 303
  • 368
29

Another option, using the command module:

- name: touch file only when it does not exists
  command: touch /path/to/file
    args:
    creates: /path/to/file

The creates argument ensures that this action is not performed if the file exists.

Kevin C
  • 4,851
  • 8
  • 30
  • 64
Leynos
  • 549
  • 5
  • 10
  • 8
    You should avoid command as much as possible since it is not idempotent. http://ryaneschinger.com/blog/ensuring-command-module-task-is-repeatable-with-ansible/ – redshark1802 Oct 10 '16 at 19:55
  • 5
    @redshark1802 Agreed. Although in this case, the task is idempotent, since it will not be executed if "/path/to/file" already exists. I think René Pijl's solution is the more Ansible-like of the three top answers, and definitely the one you should use if you need to set ownership, mode, etc. – Leynos Oct 13 '16 at 07:36
27

With ansible 2.7+ only

Ansible file module provide a way to touch file without modifying its time.

- name: touch a file, but do not change access time, making this task idempotent
  file:
    path: /etc/foo.conf
    state: touch
    mode: u+rw,g-wx,o-rwx
    modification_time: preserve
    access_time: preserve

Reference: https://docs.ansible.com/ansible/latest/modules/file_module.html

Kevin C
  • 4,851
  • 8
  • 30
  • 64
Ravi Kulkarni
  • 527
  • 1
  • 7
  • 12
  • 1
    This is correct answer for ansible 2.7+, however that important information is missing in it. – Honza Nov 12 '19 at 08:37
16

Building on the accepted answer, if you want the file to be checked for permissions on every run, and these changed accordingly if the file exists, or just create the file if it doesn't exist, you can use the following:

- stat: path=/etc/nologin
  register: p

- name: create fake 'nologin' shell
  file: path=/etc/nologin 
        owner=root
        group=sys
        mode=0555
        state={{ "file" if  p.stat.exists else "touch"}}
AllBlackt
  • 710
  • 6
  • 9
  • 3
    This answer is awesome because of the flexibility it gives you in defining the file attributes of a file if it doesn't exist. – Dejay Clayton Jun 13 '16 at 02:55
11

file: path=/etc/nologin state=touch

Full equivalent of touch (new in 1.4+) - use stat if you don't want to change file timestamp.

jalmasi
  • 352
  • 3
  • 5
  • 4
    It's not idempotent, the file date will be modified on eachexecution of the ansible playbook. – Jérôme B Jun 22 '18 at 13:11
  • 5
    @Jérôme B New in Ansible 2.7: you can make it idempotent with `file: path=/etc/nologin state=touch modification_time=preserve access_time=preserve`. – GregV Sep 16 '19 at 17:26
3

Turns out I don't have enough reputation to put this as a comment, which would be a more appropriate place for this:

Re. AllBlackt's answer, if you prefer Ansible's multiline format you need to adjust the quoting for state (I spent a few minutes working this out, so hopefully this speeds someone else up),

- stat:
    path: "/etc/nologin"
  register: p

- name: create fake 'nologin' shell
  file:
    path: "/etc/nologin"
    owner: root
    group: sys
    mode: 0555
    state: '{{ "file" if  p.stat.exists else "touch" }}'
Andrew Richards
  • 1,392
  • 11
  • 18
1

Changed if file not exists. Create empty file.

- name: create fake 'nologin' shell
  file:
    path: /etc/nologin
    state: touch
  register: p
  changed_when: p.diff.before.state == "absent"
1

A combination of two answers, with a twist. The code will be detected as changed, when the file is created or the permission updated.

- name: Touch again the same file, but dont change times this makes the task idempotent
  file:
    path: /etc/foo.conf
    state: touch
    mode: 0644
    modification_time: preserve
    access_time: preserve
  changed_when: >
    p.diff.before.state == "absent" or
    p.diff.before.mode|default("0644") != "0644"

and a version that also corrects the owner and group and detects it as changed when it does correct these:

- name: Touch again the same file, but dont change times this makes the task idempotent
  file:
    path: /etc/foo.conf
    state: touch
    state: touch
    mode: 0644
    owner: root
    group: root
    modification_time: preserve
    access_time: preserve
  register: p
  changed_when: >
    p.diff.before.state == "absent" or
    p.diff.before.mode|default("0644") != "0644" or
    p.diff.before.owner|default(0) != 0 or
    p.diff.before.group|default(0) != 0
0

In order to create a file in the remote machine with the ad-hoc command

ansible client -m file -a"dest=/tmp/file state=touch"

Please correct me if I am wrong

Employee
  • 3,109
  • 5
  • 31
  • 50