A smart way to determine whether playbooks should only run for localhost?

I am working on a set of Ansible Playbooks which need to be a bit more versatile. The playbooks need to execute on localhost where it is not setup as a Controller node. If it is a Controller Node it performs the duties of a controller node in a network (playbook executed on each device it needs to manage).

I do not wish to programatically change the playbooks from hosts: all to hosts: localhost so I currently have localhost in my inventory file:

all:
   children:
      ungrouped:
        localhost:
          ansible_connection: local
          ansible_host: 127.0.0.1

The repercussion is that based on whether the playbook is executed on localhost or as a control node some templates need to be adapted or some tasks need to be skipped. e.g. if controller node is setup → setup a file-server etc. but if standalone then perform some other tasks.

What would be an optimal way to achieve this task? Should I have to setup conditionals to check for ungrouped group and check if localhost exists or is there some smart way to achieve some distinction for standalone vs controller logic?

As a general rule of thumb, I never define localhost in inventory. I always use the implicit localhost or else I will shoot myself in the foot. Secondly, the “control node” or “controller” is always the localhost you run Ansible from. There is no “localhost that is not setup as a Controller node”.

If you have playbooks where you need to do something on the controller before it can do something for the remote inventory, like install pip packages, do something like this:

---
- hosts: all
  gather_facts: true
  pre_tasks:
    - name: Install Controller Pre-requisites
      ansible.builtin.pip:
        name: pypsrp[kerberos]
        state: present
      delegate_to: localhost
      run_once: true
  tasks:
     - name: Verify windows target connection
       ansible.windows.win_ping:
       when: ansible_connection == 'psrp'

Or, you can define multiple plays in the same playbook:

---
- name: Configure Controller
  hosts: localhost
  tasks:
    - name: Install Controller Pre-requisites
      ansible.builtin.pip:
        name: pypsrp[kerberos]
        state: present

- name: Configure Inventory Hosts
  hosts: all
  tasks:
     - name: Verify windows target connection
       ansible.windows.win_ping:
       when: ansible_connection == 'psrp'

I would not recommend trying to define localhost, include it in the all group, and then try to use conditionals to decide whether something should or shouldn’t run on the localhost. That’s what delegate_to (and depending on the task, run_once) are for.

1 Like

Secondly, the “control node” or “controller” is always the localhost you run Ansible from. There is no “localhost that is not setup as a Controller node”.

I believe this will not work in general but only when delegate_to is explicitly mentioned. As an example:

---
- name: Test pinging on localhost when nothing in inventory
  hosts: all
  tasks:
    - ping:

will not ping the localhost because all does not have any explicit mention of localhost

PLAY [all] *********************************************************************************************************************************************************************************
skipping: no hosts matched

PLAY RECAP *******************************************************************************************************************************************************************************

scenario

I have an offline system. I start with a single device and want to execute a set of playbooks on it to configure the device with some offline artifacts. In this case, having a playbook with hosts: localhost would make sense. But once I introduce more devices into this offline network and set the device, previously mentioned to a controller, and wish to do the same playbook execution on the other devices I would need to set the playbook hosts: all given that I have an inventory of the new devices added.

This implies that the plays will be executed sequentially, once on the localhost and then on the inventory devices. There is no way of execution logic based on whether the playbook should perform task set A if it is only meant for localhost and task set B if it is meant for remote devices.

As an example, I have some docker image tarballs that I wish to deploy. On a single device I would just use the equivalent of docker load from the Docker collection. Now if this device is responsible for distributing the image tarballs to multiple devices a sane way would be for the controller to push these images to a docker registry and then changing things on the remote devices (docker pull etc.)

Keeping localhost and all separate is something I would really consider best-practice.

That said, if you really want to include localhost as an inventory target together with all in the same play, then I would recommend simply mentioning them both in hosts:

- name: Configure docker
  hosts: all,localhost
  gather_facts: true
  tasks:
...

Then if need be, you can use --limit to restrict your inventory targets when necessary.

As an aside, if you’re wanting to configure the localhost identically to all of your remotes, you might benefit from using ansible-pull instead. If each of your “offline” devices have access to the same local-network resource for offline artifacts, then you can store your ansible playbook(s) there, and use ansible-pull on each device to run the exact same play in the exact same context (against only hosts: localhost)

As others have mentioned, I would advise against adding localhost to the inventory, but if you really have to, please follow the docs:

https://docs.ansible.com/ansible/latest/inventory/implicit_localhost.html

1 Like

You are correct. I have a project based on ansible-runner which currently doesn’t have --pull functionality but I am assuming performing ansible-pull will work in this case too. I think I would consider your tips. appreciate it.

Oh? Now that’s interesting. I can’t say I have any real experience with using ansible-runner myself, so I don’t know if that works well together with ansible-pull.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.