0

Given the tree on the controller

shell> tree /tmp/test/
/tmp/test/
├── override
│   ├── dir1
│   │   └── file_B.txt
│   ├── file_2.txt
│   └── file_4.txt
└── template
    ├── dir1
    │   ├── file_A.txt
    │   └── file_B.txt
    ├── file_1.txt
    └── file_2.txt

4 directories, 7 files

Copy the files to the remote host:

  • file_1.txt From template
  • file_2.txt From override
  • file_4.txt From override, even if it is not in template
  • file_A.txt From template
  • file_B.txt From override
Vladimir Botka
  • 5,138
  • 8
  • 20

1 Answers1

0

Given the tree for testing

shell> tree /tmp/test
/tmp/test
├── override
│   ├── dir1
│   │   └── file_B.txt
│   ├── file_2.txt
│   └── file_4.txt
└── template
    ├── dir1
    │   ├── file_A.txt
    │   └── file_B.txt
    ├── file_1.txt
    └── file_2.txt

4 directories, 7 files
shell> find /tmp/test -type f | sort | xargs cat
override dir1 file_B.txt
override file_2.txt
override file_4.txt
template dir1 file_A.txt
template dir1 file_B.txt
template file_1.txt
template file_2.txt

Declare the paths and the list of overlays

  remote_path: /tmp/test
  local_path: /mnt/test
  overlays:
    - /tmp/test/override
    - /tmp/test/template

There are more options how to copy the files

  1. unionfs

Install the package

    - name: Install unionfs
      package:
        name: unionfs-fuse
        state: present
      run_once: true
      delegate_to: localhost
      when: install|d(false)|bool

Mount unionfs

    - name: Mount unionfs
      block:
        - file:
            state: directory
            path: "{{ local_path }}"
        - debug:
            msg: "dirs={{ overlays|product(['=ro'])|map('join')|join(':') }}"
        - mount:
            state: mounted
            path: "{{ local_path }}"
            fstype: unionfs
            opts: "allow_other,dirs={{ overlays|product(['=ro'])|map('join')|join(':') }}"
            src: dummy
      run_once: true
      delegate_to: localhost

The mount point was created

shell> tree /mnt/test
/mnt/test
├── dir1
│   ├── file_A.txt
│   └── file_B.txt
├── file_1.txt
├── file_2.txt
└── file_4.txt

1 directory, 5 files

The files from override replaced the files from template

shell> find /mnt/test -type f | sort | xargs cat
template dir1 file_A.txt
override dir1 file_B.txt
template file_1.txt
override file_2.txt
override file_4.txt

Create the remote directory and synchronize the files

    - name: Create {{ remote_path }}
      file:
        state: directory
        path: "{{ remote_path }}"

    - name: Sync {{ local_path }} to {{ remote_path }}
      synchronize:
        src: "{{ local_path }}/"
        dest: "{{ remote_path }}"

The files were synchronized to the remote host

shell> ssh admin@test_13 find /tmp/test -type f | sort
/tmp/test/dir1/file_A.txt
/tmp/test/dir1/file_B.txt
/tmp/test/file_1.txt
/tmp/test/file_2.txt
/tmp/test/file_4.txt
shell> ssh admin@test_13 'find /tmp/test -type f | sort | xargs cat'
template dir1 file_A.txt
override dir1 file_B.txt
template file_1.txt
override file_2.txt
override file_4.txt

Example of a complete playbook for testing

- hosts: all
  become: true

  vars:

    remote_path: /tmp/test
    local_path: /mnt/test
    overlays:
      - /tmp/test/override
      - /tmp/test/template

  tasks:

    - name: Install unionfs
      package:
        name: unionfs-fuse
        state: present
      run_once: true
      delegate_to: localhost
      when: install|d(false)|bool

    - name: Mount unionfs
      block:
        - file:
            state: directory
            path: "{{ local_path }}"
        - debug:
            msg: "dirs={{ overlays|product(['=ro'])|map('join')|join(':') }}"
        - mount:
            state: mounted
            path: "{{ local_path }}"
            fstype: unionfs
            opts: "allow_other,dirs={{ overlays|product(['=ro'])|map('join')|join(':') }}"
            src: dummy
      run_once: true
      delegate_to: localhost

    - name: Create {{ remote_path }}
      file:
        state: directory
        path: "{{ remote_path }}"

    - name: Sync {{ local_path }} to {{ remote_path }}
      synchronize:
        src: "{{ local_path }}/"
        dest: "{{ remote_path }}"

    - name: Umount {{ local_path }}
      mount:
        state: unmounted
        path: "{{ local_path }}"
      run_once: true
      delegate_to: localhost
      when: umount|d(false)|bool

  1. Link the overlays

You can link the overlays on your own if you can't use unionfs. Declare the variables

  dirs: "{{ dirs_out.results|json_query('[].stdout_lines')|flatten|unique }}"
  files: "{{ files_out.results|json_query('[].stdout_lines') }}"
  files_dict: "{{ dict(overlays|zip([files.0, files.1|difference(files.0)])) }}"

Create the directories and link the overlays

    - name: Link overlays
      block:

        - name: Find directories
          command: "sh -c 'cd {{ item }}; find * -type d'"
          loop: "{{ overlays }}"
          register: dirs_out
          changed_when: false
        - debug:
            var: dirs|to_yaml
        - name: Create directories
          file:
            state: directory
            path: "{{ item }}"
          loop: "{{ [local_path]|product(dirs)|map('path_join') }}"

        - name: Find files
          command: "sh -c 'cd {{ item }}; find * -type f'"
          loop: "{{ overlays }}"
          register: files_out
          changed_when: false
        - debug:
            var: files|to_yaml
        - debug:
            var: files_dict|to_yaml
        - name: Link files
          file:
            state: link
            src: "{{ (item.0.key, item.1)|path_join }}"
            dest: "{{ (local_path, item.1)|path_join }}"
          with_subelements:
            - "{{ files_dict|dict2items }}"
            - value

      run_once: true
      delegate_to: localhost
shell> tree /mnt/test
/mnt/test
├── dir1
│   ├── file_A.txt -> /tmp/test/template/dir1/file_A.txt
│   └── file_B.txt -> /tmp/test/override/dir1/file_B.txt
├── file_1.txt -> /tmp/test/template/file_1.txt
├── file_2.txt -> /tmp/test/override/file_2.txt
└── file_4.txt -> /tmp/test/override/file_4.txt

1 directory, 5 files

Create the remote directory and synchronize the files

    - name: Create {{ remote_path }}
      file:
        state: directory
        path: "{{ remote_path }}"

    - name: Sync {{ local_path }} to {{ remote_path }}
      synchronize:
        src: "{{ local_path }}/"
        dest: "{{ remote_path }}"
        copy_links: true

Example of a complete playbook for testing

- hosts: all
  become: true

  vars:

    remote_path: /tmp/test
    local_path: /mnt/test
    overlays:
      - /tmp/test/override
      - /tmp/test/template

    dirs: "{{ dirs_out.results|json_query('[].stdout_lines')|flatten|unique }}"
    files: "{{ files_out.results|json_query('[].stdout_lines') }}"
    files_dict: "{{ dict(overlays|zip([files.0, files.1|difference(files.0)])) }}"

  tasks:

    - name: Link overlays
      block:

        - name: Find directories
          command: "sh -c 'cd {{ item }}; find * -type d'"
          loop: "{{ overlays }}"
          register: dirs_out
          changed_when: false
        - debug:
            var: dirs|to_yaml
        - name: Create directories
          file:
            state: directory
            path: "{{ item }}"
          loop: "{{ [local_path]|product(dirs)|map('path_join') }}"

        - name: Find files
          command: "sh -c 'cd {{ item }}; find * -type f'"
          loop: "{{ overlays }}"
          register: files_out
          changed_when: false
        - debug:
            var: files|to_yaml
        - debug:
            var: files_dict|to_yaml
        - name: Link files
          file:
            state: link
            src: "{{ (item.0.key, item.1)|path_join }}"
            dest: "{{ (local_path, item.1)|path_join }}"
          with_subelements:
            - "{{ files_dict|dict2items }}"
            - value

      run_once: true
      delegate_to: localhost

    - name: Create {{ remote_path }}
      file:
        state: directory
        path: "{{ remote_path }}"

    - name: Sync {{ local_path }} to {{ remote_path }}
      synchronize:
        src: "{{ local_path }}/"
        dest: "{{ remote_path }}"
        copy_links: true

    - name: Unlink {{ local_path }}
      file:
        state: absent
        path: "{{ (local_path, item.1)|path_join }}"
      with_subelements:
        - "{{ files_dict|dict2items }}"
        - value
      run_once: true
      delegate_to: localhost
      when: umount|d(false)|bool
Vladimir Botka
  • 5,138
  • 8
  • 20