Ansible - ad-hoc commands

Ansible is a powerful automation tool for servers, network, cloud, containers and more. You don't even need to write a playbook to use it. And sometimes, you shouldn't write a playbook for simple tasks. Ansible ad-hoc commands can be used whenever you want to automate simple steps.

Ansible - ad-hoc commands

Ansible is a powerful automation tool for servers, network, cloud, containers and more. You don't even need to write a playbook to use it. And sometimes, you shouldn't write a playbook for simple tasks. Ansible ad-hoc commands can be used whenever you want to automate simple steps.

If you are new to Ansible, I strongly suggest reading about "Ansible - Getting Started" and "Ansible - Overview", first.

Why to use ad-hoc?

Ansible ad-hoc commands seems paradox at the first glance. But, writing a playbook for every single task is also not an option.

Ad-hoc commands come in very handy when:

  • a single task is needed
  • a task is rarely repeated
  • you think: "I can write a script instead of a playbook"
  • you think: "ssh HOSTNAME will be faster"
  • idempotency is desired
  • you want to properly document your work
  • you want to use Ansible features like callback plugins

In the following sections I will explain how this works and provide some examples to get started.

Syntax

Let's have a look at the syntax first, to understand how the commands are working. After installing Ansible like described in the "Ansible - Getting Started" article, you will be able to run the ansible command. As you can see below, the syntax is quite simple.

© 2021, Daniel Schier, CC BY-SA 4.0

The "pattern" part is used to address where the command should be executed. This can be an IP Address, a DNS resolvable hostname or a group of an existing inventory.

The "module" part defines, which module will be used. Modules are an important paradigm in Ansible. You can see them as a simplified command helper, so you don't need to write every command on your own. For example, the package module will detect, which package manager is used and install/uninstall/remove a package on your managed node.

The last part are the "module options". Each module has different options, that can be handed over. These options define what the module actually should do. For the package module, this can be the state and the package name.

A working command to install nginx on the localhost can look like this.

© 2021, Daniel Schier, CC BY-SA 4.0

Uninstalling is basically the same.

© 2021, Daniel Schier, CC BY-SA 4.0

Additionally, you can add the typical Ansible options, which are the same as for other Ansible commands. The Ansible documentation provides some detailed explanations about patterns, modules and the Ansible command line tools.

Examples

Enough of the theory. Let's see some examples. In this section, I will provide some useful snippets, that I am using on my own. I hope, these are useful for you, too.

Hint
The guide is tested on Fedora 33 with Ansible 3.0.1.

Gathering Facts

Ansible has the ability to gather lots of facts from a managed node. The module used for this is named "setup module". It is also running at the beginning of each playbook, if you don't change the behavior.

The module is especially useful, if you want to know something like the OS version, IP Addresses or allocated hardware resources.

You can start very simple with the below command to get all facts and search what you need, manually.

# Get all facts and output
$ ansible localhost -m setup

The list of facts, you will get is very huge and provides tons of details. Therefore, it is possible to limit the output to subsets.

# Get subset of facts and output
$ ansible localhost -m setup -a "gather_subset=hardware"

# Only get the minimum
$ ansible localhost -m setup -a "gather_subset=min"

Even this output can be a bit overwhelming. The setup module provides filters, to limit the output to a provided glob pattern. Please take care of the single quotes, to avoid shell expansion.

# Get facts, but limit output to distribution variables
$ ansible localhost -m setup -a 'filter=*distribution*'

# Get facts, but limit output to ipv_addresses
$ ansible localhost -m setup -a 'filter=*ipv4_address*'

Manage Files

Another typical scenario, I am facing is to copy some files or a script to a server. This can be easily done without rsync, SCP or copy-paste. Even better, we are also getting additional options like backups with the "copy module".

Let's copy a file to the managed server:

# Create a working directory and a dummy script
$ mkdir myScripts
$ touch myScripts/dummy.sh

# Copy the script to the managed host
$ ansible localhost -m copy -a "src=myScripts/dummy.sh dest=/tmp/dummy.sh"
localhost | CHANGED => {
    "changed": true,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "/tmp/dummy.sh",
    "gid": 1000,
    "group": "vagrant",
    "md5sum": "d41d8cd98f00b204e9800998ecf8427e",
    "mode": "0644",
    "owner": "vagrant",
    "secontext": "unconfined_u:object_r:user_home_t:s0",
    "size": 0,
    "src": "/home/vagrant/.ansible/tmp/ansible-tmp-1618144298.3542292-10048-272543721204399/source",
    "state": "file",
    "uid": 1000
}

Running the Ansible command again will also indicate, that the script is already there and nothing was changed.

# Run command again
$ ansible localhost -m copy -a "src=myScripts/dummy.sh dest=/tmp/dummy.sh"
localhost | SUCCESS => {
    "changed": false,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "/tmp/dummy.sh",
    "gid": 1000,
    "group": "vagrant",
    "mode": "0644",
    "owner": "vagrant",
    "path": "/tmp/dummy.sh",
    "secontext": "unconfined_u:object_r:user_home_t:s0",
    "size": 0,
    "state": "file",
    "uid": 1000
}

But, what if the script was changed, and I also want to keep a backup on the managed node? Easy!

# Change the script
$ echo "myChange" >> myScripts/dummy.sh

$ ansible localhost -m copy -a "src=myScripts/dummy.sh dest=/tmp/dummy.sh backup=true"
localhost | CHANGED => {
    "backup_file": "/tmp/dummy.sh.10123.2021-04-11@12:35:11~",
    "changed": true,
    "checksum": "d335cfa40dd731ed9e9e3c452284a7fb49f8d502",
    "dest": "/tmp/dummy.sh",
    "gid": 1000,
    "group": "vagrant",
    "md5sum": "805f7a1096240f7b9916ff00005040eb",
    "mode": "0644",
    "owner": "vagrant",
    "secontext": "unconfined_u:object_r:user_home_t:s0",
    "size": 9,
    "src": "/home/vagrant/.ansible/tmp/ansible-tmp-1618144510.9744356-10103-152373137021556/source",
    "state": "file",
    "uid": 1000
}

Ansible also indicates, that the backup was created and named "/tmp/dummy.sh.10123.2021-04-11@12:35:11~".

You can do much more, like making the script executable, change permissions and basically everything the "copy module" provides.

Manage Users

Sometimes, we just need to create or remove a user. Changing a password of a user is very common, too. Maybe you even want to lock the user, so no login is possible. User management can be done with the "user module".

Let's create a user with the name "admin". Adding the user to the group "wheel" give permissions to use sudo. To create users, one needs privileges. These can be granted with "--become" or "-b".

# Create user with sudo permissions
$ ansible localhost -b -m user -a 'name=admin groups=wheel'
localhost | CHANGED => {
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 1001,
    "groups": "wheel",
    "home": "/home/admin",
    "name": "admin",
    "shell": "/bin/bash",
    "state": "present",
    "system": false,
    "uid": 1001
}

The user does not have a password, which should be changed. The user module only allows encrypted passwords. We can generate these with an ad-hoc command, as described in the documentation.

# Generate encrypted password
$ ansible localhost -m debug -a "msg={{ 'password' | password_hash('sha512', 'mysecretsalt') }}"
localhost | SUCCESS => {
    "msg": "$6$mysecretsalt$MIJffjeQyfrKKrGkprGrDL/g2mCJa53koLmYQuuLmY9y37pDvGKPXU1Ov3RbMi.tpQ9cWvxAzUVtBLe7KrZoU."
}

# Update the user with a password
$ ansible localhost -b -m user -a 'name=admin groups=wheel password="$6$mysecretsalt$MIJffjeQyfrKKrGkprGrDL/g2mCJa53koLmYQuuLmY9y37pDvGKPXU1Ov3RbMi.tpQ9cWvxAzUVtBLe7KrZoU."'
localhost | CHANGED => {
    "append": false,
    "changed": true,
    "comment": "",
    "group": 1001,
    "groups": "wheel",
    "home": "/home/admin",
    "move_home": false,
    "name": "admin",
    "password": "NOT_LOGGING_PASSWORD",
    "shell": "/bin/bash",
    "state": "present",
    "uid": 1001
}

That's it. We have created the admin user. Removing the user is easy, too.

# Remove admin user
$ ansible localhost -b -m user -a 'name=admin state=absent'
localhost | CHANGED => {
    "changed": true,
    "force": false,
    "name": "admin",
    "remove": false,
    "state": "absent"
}

Packages and Service

Very often, it is needed to manage packages or services. In this example, I will install, start and enable Apache httpd. Handling services is very useful after updates or if it just stopped. The "package module" and "service module" provide all we need.

# Install Apache httpd
$ ansible localhost -b -m package -a "name=httpd state=present"

# Start and Enable Apache httpd
$ ansible localhost -b -m service -a "name=httpd.service state=started enabled=true"

That's already it. Now you can test, if the Apache httpd server is running by pointing your browser to it or running a curl.

# Open Firefox
$ firefox localhost

# Curl the website
$ curl localhost

Conclusion

Ansible ad-hoc commands provide an option to do arbitrary tasks on multiple machines. These commands will be idempotent and can work on a single host or many machines at the same time. This can be useful for very rare configurations, operational tasks or small jobs.

There is no need to write a playbook or ssh to a bunch of machines. You will no longer "ssh ..., systemctl ..., dnf ..., etc." multiple times.