Deploy directory tree, if the tree exists in files-templates - how do I approach host specific files tree conditionally?

Deploy directory tree: if the tree exists in files-templates - how do I approach host specific files tree conditionally?

Please advise me on an approach I should follow during my implementation of files tree deployment to a target machine, regardless of whether the tree exists or not among files or templates on a control machine.

Should I use a boolean Ansible host variable, like a flag, say, deploy_it equal to false|true to indicate manually that I have a files tree to be copied to a host?

Or can I implement in a task a kind of look-up inside role-name/templates/{{ hostname }}/*/ or role-name/files/{{ hostname }}/*/ to auto-detect if the files tree is present for a particular host’s name? That way I would not have any other flags to be added/changed for a particular host when a host is added to or excluded from the play, other than the files’ presence on the control machine. (I have the exact host name among variables and can insert it into a path to be checked.)

May be templates/{{ hostname }}/*/ or files/{{ hostname }}/*/ at the playbook top-level directory, but that way I am incurring the role files tree (tasks, handlers, defaults and so on) to be split into two places - the role dir. and the top level of the play dir., which I am in doubt about.

Id est:

At control machine, in particular role directory the tree is:

templates
└── dot-config
    ├── host-name-01
    │   └── foo
    │       ├── bar-x
    │       │   ├── a.conf
    │       │   └── b.conf
    │       └── bar-y
    │           ├── c.conf
    │           └── d.conf
    └── host-name-02
        └── foo
            ├── bar-x
            │   ├── a.conf
            │   ├── b.conf
            │   └── c.conf
            └── bar-y
                ├── d.conf
                ├── e.conf
                └── f.conf


The parts of the tree above are selected by a host name

and are deployed at target machine as

.config
└── foo
    ├── bar-x
    │   ├── smth-a.conf
    │   └── smth-b.conf
    └── bar-y
        ├── smth-c.conf
        └── smth-d.conf

What is an optimal approach to launch deploy if the tree exists?
And do not have any other flags to be added/changed for a particular host, when a host is added to or excluded from the play. Files presence at the control machine is the only flag.

To do what you’re describing, I’d first run an ansible.builtin.find task to get a list of all the *.conf files for a target host, followed by a copy task that loops over that list. If no files are found for a particular host, then the list is empty and the copy nicely devolves into a no-op.

But my instinct is to not do it this way. You’ve traded maintaining host-specific files on each host for maintaining host-specific files in one project. While that is a small improvement, it’s not that much of a win for whoever has to maintain those files.

A more canonical, Ansible way to do this is to have a template for each of the various *.conf files, with host_vars and group_vars providing values as appropriate for each host to generate via ansible.builtin.template the custom, correct .conf file(s) for the target hosts. This is more work up front, but it pays off pretty soon. You’ll be able to achieve a high level of consistency among your deployed files which would be almost impossible to achieve when manually maintaining separate host-specific sets of files.

Let us know what you come up with. Good luck!

Thank you for the answer.

Unfortunately, the file set is a quite unpredictable set of XML files with software configuration. Which, as I think, suggests using a files tree snapshot, or setting tons of parameters via CLI utility calls for each parameter.

My error was not visible from the post above: I needed to temporarily revert become by specifying become: no.

Now I have landed on the following approach for a host and account specific file tree deployment (comments and suggestions on the approach are appreciated, thank you!):

---

#
#  This file is part of "Example".
#
#  Example is free software: you can redistribute it and/or
#  modify it under the terms of the GNU General Public License as
#  published by the Free Software Foundation, either version 3 of
#  the License, or (at your option) any later version.
#
#  Example is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with Example.
#  If not, see <http://www.gnu.org/licenses/>.
#
##


-   name: "Specify a target account (looping over all is possible)."
    set_fact:
        target_account: "{{ ansible_user }}"


-   name: "Evaluate home directory ('getent' Ansible module with DB 'passwd')."
    set_fact:
        home_directory: "/home/{{ ansible_user }}"


-   name: "{{ target_account }} : Deploy files tree branch."
    become: yes
    become_user: "{{ target_account }}"
    block:

    -   name: "{{ target_account }} : Declare files tree branch source directory."
        set_fact:
            files_tree_branch_directory: "{{ role_path ~ '/files/' ~ ansible_host ~ '/' ~ target_account ~ '/THE_BRANCH' }}"


    -   name: "{{ target_account }} : Retrieve filesystem metadata to determine later if files tree branch source is present."
        stat:
            path: "{{ files_tree_branch_directory }}"
        register: fs_metadata
        delegate_to: localhost

        # The previous 'become' at the block start used a different account,
        # different from the one used for playbook development,
        # leading to 'access denied' unless reverted.
        become: no


    -   name: "{{ target_account }} : Report state reasons."
        when: not fs_metadata.stat.exists
        debug:
            msg: "Source directory '{{ files_tree_branch_directory }}' is not prepared for account '{{ target_account }}'."


    -   name: "{{ target_account }} : Upload files tree branch."
        when: fs_metadata.stat.exists
        copy:
            src: "{{ files_tree_branch_directory }}/"  # Note trailing '/' - it is the obligatory magic, see `copy` module docs.
            dest: "{{ home_directory }}/{{ files_tree_branch_directory | basename }}"
            owner: "{{ target_account }}"
            group: "{{ target_account }}"
            mode: '0644'
            directory_mode: '0755'

Iteration over available accounts and home directory evaluation for each account are omitted as they are out of scope above.

You may want a when: fs_metadata.stat.exists on that last task.
Looks like you’re well on your way. :+1: