Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: July 19, 2024
Ansible is a powerful automation tool. It mainly focuses on running tasks on remote hosts. However, in some scenarios, we require running commands on the Ansible controller itself.
Basically, the controller is the system running Ansible. We also refer to this machine as localhost in this tutorial.
In this tutorial, we look at different ways of running commands on the controller. To move on with this tutorial, we need to set up a basic lab.
Running commands on the Ansible controller has several common and important purposes:
These are some examples of running commands on the Ansible controller host. Yet, there can be specific scenarios that require additional commands to run.
Ansible has several ways to run commands directly on the controller host:
Let’s go through each method one by one.
Ansible has many connection plugins. One such plugin is ansible.builtin.local. This plugin also has a short name: local. Basically, it runs tasks on the controller machine.
Moreover, the playbook usually executes quickly as there is no SSH overload.
Let’s see how we can set the connection type inside a playbook.
To run a command locally, we set the connection keyword to local. Further, we can add this configuration to a whole playbook or a single task.
Let’s take an example playbook of getting uptime from localhost:
$ cat connection.yaml
---
- name: Get system uptime
hosts: localhost
connection: local
tasks:
- name: Print uptime
command: uptime
register: "output"
- debug: var=output.stdout
We can break down the above playbook:
When we run the above playbook, we see the system uptime in the output:
$ ansible-playbook connection.yaml
[WARNING]: provided hosts list is empty,...
PLAY [Get system uptime] *************************************
TASK [Gathering Facts] ***************************************
ok...
TASK [Print uptime] ******************************************
changed: [localhost]
TASK [debug] *************************************************
ok: [localhost] => {
"output.stdout": " 07:06:39 up 16 min, 1 user, load average: 0.19, 0.06, 0.03"
}
PLAY RECAP ***************************************************
...
In this case, we don’t use any inventory files with the above playbook.
As another option, we can skip the connection: local line from the playbook. Instead, we can use the –connection=local parameter on the command-line:
$ ansible-playbook connection.yaml --connection=local
[WARNING]: provided hosts list is empty...
PLAY [Get system uptime] *************************************
TASK [Gathering Facts] ***************************************
ok:...
TASK [Print uptime] ******************************************
changed...
TASK [debug] *************************************************
ok: [localhost] => {
"output.stdout": " 06:56:31 up 3:29, 2 users, load average: 0.00, 0.00, 0.00"
}
PLAY RECAP ****************************************************
...
As a result, the above playbook runs on the localhost system. Again, it just shows the system uptime using the debug module.
Markedly, there is an implicit localhost warning in the above two cases. We can define a localhost entry in the inventory file to remove this warning. Furthermore, we supply the inventory file name while running the playbook.
The Ansible inventory file can be configured to define what localhost points to. Consequently, this enables us to target the controller.
Let’s modify the inventory file to contain an entry for localhost:
$ cat inventory.ini
controller ansible_connection=local
In the above file, controller is assigned as the name for localhost.
Then, we add a new playbook, inventory_demo.yaml:
$ cat inventory_demo.yaml
---
- name: Get system uptime
hosts: controller
tasks:
- name: Print uptime
command: uptime
register: "output"
- debug: var=output.stdout
Since we’ve already defined the connection inside the inventory file, we’ve not added any connection directives to the playbook.
Let’s run the above playbook:
$ ansible-playbook inventory_demo.yaml -i inventory.ini
PLAY [Get system uptime] *************************************
TASK [Gathering Facts] ***************************************
ok:...
TASK [Print uptime] *******************************************
changed:...
TASK [debug] *******************************************************************
ok: [localhost] => {
"output.stdout": " 11:00:30 up 25 min, 1 user, load average: 0.16, 0.03, 0.01"
}
PLAY RECAP *********************************************************************
...
As a result, we can see the Ansible controller uptime.
The delegate_to keyword enables delegating tasks to a specific host, including the controller. This is particularly useful in scenarios where a task needs to be performed on one host but requires context or reference from other hosts.
Delegation is frequently used in managing nodes in a load-balanced pool.
For example, we might want to update a configuration file on all servers. Usually, taking them all down at once isn’t an option. In such cases, by using delegate_to, we can tell Ansible to update the files on each server one by one. At the same time, the load balancer distributes traffic to the other online servers.
Let’s take the example of copying a file to and from localhost:
$ cat delegate_to.yaml
---
- name: copy a file on localhost
hosts: all
tasks:
- name: Copy a file locally
copy:
src: /home/vagrant/abc.txt
dest: /home/vagrant/ansible/
delegate_to: localhost
register: copy_result
- name: Display copy result
debug:
var: copy_result
Now, we can break down the above playbook:
When we run the playbook, the results are shown for each host. However, in PLAY RECAP, we see the changes only on localhost:
$ ansible-playbook delegate_to.yaml -i inventory.ini
...
TASK [Display copy result] *****************************************************************************
ok: [controller] => {
"copy_result": {
"changed": false,
...
PLAY RECAP *********************************************************************
127.0.0.1 : ok=3 changed=1...
192.168.29.21 : ok=3 changed=0...
...
Thus, the file transfer is done only on localhost.
Ansible offers a simpler way to run tasks directly on the controller host. This is done using the local_action module. Moreover, this removes the need for the more verbose delegate_to option.
Let’s again write a playbook to see how the local_action module works. This playbook copies a file to and from localhost:
$ cat local_action.yaml
---
- name: copy a file using local_action
hosts: all
tasks:
- name: Print uptime
command: uptime
register: "uptime_result"
- debug: var=uptime_result.stdout
- name: Copy a file locally
local_action: copy src=/home/vagrant/abc.txt dest=/home/vagrant/ansible/
register: "copy_result"
- name: Display copy result
debug:
var: copy_result
when: inventory_hostname == 'localhost'
The above playbook is the same as the previous one, with some exceptions:
Next, we run the above playbook:
$ ansible-playbook local_action.yaml -i inventory.ini
...
TASK [Print uptime] ************************************************************
changed: [localhost]
changed: [192.168.29.21]
changed: [192.168.29.22]
...
TASK [Display copy result] *****************************************************
ok: [localhost] => {
"copy_result": {
"changed": false,
...
skipping: [192.168.29.22]
skipping: [192.168.29.21]
...
Notably, this time the uptime command runs on all hosts. However, the file transfers only on localhost.
We can also run Ansible ad-hoc commands against localhost. To do this, we can use localhost by its name.
Let’s use the shell module to print a message, abc abc:
$ ansible localhost -c local -m shell -a 'echo abc abc'
localhost | CHANGED | rc=0 >>
abc abc
We can break down each part of the command:
As a matter of fact, we can also use the IP address of localhost:
$ ansible 127.0.0.1 -c local -m shell -a 'echo abc abc'
127.0.0.1 | CHANGED | rc=0 >> abc abc
Again, the inventory file can rename localhost. For example, we can again refer to 127.0.0.1 as controller in the inventory file:
$ cat inventory.ini
[controller]
127.0.0.1
Then, we use the Ansible ad-hoc command with name controller:
$ ansible controller -c local -m shell -a 'echo abc abc' -i inventory.ini
127.0.0.1 | CHANGED | rc=0 >>
abc abc
Here, the option -i tells which hosts to select from the inventory file. In the same way, as expected, we can use localhost as the hostname.
As a result, the above command runs the shell command echo abc abc.
In this article, we saw how to run a command on Ansible local system, localhost.
First, we used the local connection method. Then, we used the Ansible inventory file. Further, we worked with the delegate_to keyword and the local_action module.
Finally, we saw how to use the ad-hoc commands for running commands on localhost. With these methods, managing both local and remote tasks usually becomes more effective.