ayuuk ja'ay
ayuuk ja'ay

Reputation: 384

Ansible create multiple directories and files within each directory

I am trying to create multiple directories (with different names) and files inside each directory.

I have a variable named directories that contains directory and file names like this:

directories:
  - directory: dir1
    static_files: 
      files: 
        - file1
        - file2

  - directory: dir2
    static_files: 
      files: 
        - file1
        - file2

This is the workaround I was able to come up with using with_subelments but with two tasks which seems working. However, I believe there must be a solution with only one task to achieve this.

- name: Creates directories
  file:
    path: "/tmp/{{ item[0].directory }}"
    state: directory
    recurse: yes
    mode: 0755
    owner: user
    group: group
  become: true
  with_subelements: 
    - "{{ directories }}"
    - static_files.files

- name: Creates files
  file:
    path: "/tmp/{{ item[0].directory }}/{{ item[1] }}"
    state: touch
    mode: 0644
    owner: user
    group: group
  become: true
  with_subelements: 
    - "{{ directories }}"
    - static_files.files

I might be missing a module that can do the job. I also tried to leverage this solution without any luck How to create directory and file inside the same directory in ansible using single task

My question is, how can this be achieve, if possible, with a single task? Any other solution and approach would also be great.

Expected result:

/tmp/dir1/file1
/tmp/dir1/file2
/tmp/dir2/file1
/tmp/dir2/file2

Any help is greatly appreciated.

Upvotes: 1

Views: 1036

Answers (1)

Vladimir Botka
Vladimir Botka

Reputation: 67959

Create the structure

  my_files: |
    {% filter from_yaml %}
    {% for dir in directories %}
    - [{{ dir.directory}}, directory]
    {% for file in dir.static_files.files %}
    - [{{ dir.directory }}/{{ file }}, touch]
    {% endfor %}
    {% endfor %}
    {% endfilter %}

gives

  my_files:
    - [dir1, directory]
    - [dir1/file1, touch]
    - [dir1/file2, touch]
    - [dir2, directory]
    - [dir2/file1, touch]
    - [dir2/file2, touch]

The iteration of the list

    - file:
        path: "/tmp/{{ item.0 }}"
        state: "{{ item.1 }}"
      loop: "{{ my_files }}"

creates the directories and the files

TASK [file] ***************************************************************
changed: [localhost] => (item=['dir1', 'directory'])
changed: [localhost] => (item=['dir1/file1', 'touch'])
changed: [localhost] => (item=['dir1/file2', 'touch'])
changed: [localhost] => (item=['dir2', 'directory'])
changed: [localhost] => (item=['dir2/file1', 'touch'])
changed: [localhost] => (item=['dir2/file2', 'touch'])
shell> tree /tmp/dir1
/tmp/dir1
├── file1
└── file2

0 directories, 2 files
shell> tree /tmp/dir2
/tmp/dir2
├── file1
└── file2

0 directories, 2 files

This task is not idempotent. When you run it again the files will be touched and both the access and modification time will be updated. Set the parameters access_time and modification_time to 'preserve' if you don't want to change the times

  my_files: |
    {% filter from_yaml %}
    {% for dir in directories %}
    - [{{ dir.directory}}, directory]
    {% for file in dir.static_files.files %}
    - [{{ dir.directory }}/{{ file }}, touch, preserve, preserve]
    {% endfor %}
    {% endfor %}
    {% endfilter %}

gives

  my_files:
    - [dir1, directory]
    - [dir1/file1, touch, preserve, preserve]
    - [dir1/file2, touch, preserve, preserve]
    - [dir2, directory]
    - [dir2/file1, touch, preserve, preserve]
    - [dir2/file2, touch, preserve, preserve]

Make the parameters access_time and modification_time optional

    - file:
        path: "/tmp/{{ item.0 }}"
        state: "{{ item.1 }}"
        access_time: "{{ item.2|d(omit) }}"
        modification_time: "{{ item.3|d(omit) }}"
      loop: "{{ my_files }}"

This makes the task idempotent

TASK [file] ***************************************************************
ok: [localhost] => (item=['dir1', 'directory'])
ok: [localhost] => (item=['dir1/file1', 'touch', 'preserve', 'preserve'])
ok: [localhost] => (item=['dir1/file2', 'touch', 'preserve', 'preserve'])
ok: [localhost] => (item=['dir2', 'directory'])
ok: [localhost] => (item=['dir2/file1', 'touch', 'preserve', 'preserve'])
ok: [localhost] => (item=['dir2/file2', 'touch', 'preserve', 'preserve'])

Example of a complete playbook for testing

- hosts: localhost

  vars:

    directories:
      - directory: dir1
        static_files: 
          files: 
            - file1
            - file2
      - directory: dir2
        static_files: 
          files: 
            - file1
            - file2

    my_files: |
      {% filter from_yaml %}
      {% for dir in directories %}
      - [{{ dir.directory}}, directory]
      {% for file in dir.static_files.files %}
      - [{{ dir.directory }}/{{ file }}, touch, preserve, preserve]
      {% endfor %}
      {% endfor %}
      {% endfilter %}

  tasks:

    - debug:
        var: my_files

    - file:
        path: "/tmp/{{ item.0 }}"
        state: "{{ item.1 }}"
        access_time: "{{ item.2|d(omit) }}"
        modification_time: "{{ item.3|d(omit) }}"
      loop: "{{ my_files }}"

Upvotes: 7

Related Questions