Ansible - Run A Local Script On Remote Server
Let's imagine you have a script to set up your servers from scratch. In this case, let's call it setup.sh
and for now, it will just be a BASH script that updates a debian/ubuntu server and installs git.
Steps
Create the Setup Script
Lets create the script on our Ansible server because it needs to be local.
#!/bin/bash
sudo apt update
sudo apt-get dist-upgrade -y
sudo apt-get install git -y
For this tutorial I have put it in /home/stuart/scripts/setup.sh
.
Update The Hosts File
If your starting out, then you need to create a group in your hosts file that we will run the setup script on. For this tutorial, my group is my-servers
.
Create the Playbook
Now we need to create the ansible playbook that will transfer that script to the remote server(s) and execute it. For this tutorial, I am calling it setup.yml
.
- hosts: my-servers
sudo: true
tasks:
- script: /home/stuart/scripts/setup.sh
Execute!
Now run the following command to execute your script. You may or may not need to swap out the $USER
variable.
ansible-playbook setup.yml --user=$USER
Execute only once
If you want to ensure that your script only ever gets executed once, then you can simply tweak your playbook to be like this:
- hosts: my-servers
sudo: true
tasks:
- script: /home/stuart/scripts/setup.sh --creates /home/stuart/installed-git.txt
You will see that we added --creates
which specifies the location of a file to check and if it exists, our script will not be executed. It does not implicitly create a blank file at that location, or pipe all the output to that location. Hence we need to update our local script to create that file so that subsequent calls will not be executed.
#!/bin/bash
sudo apt update
sudo apt-get dist-upgrade -y
sudo apt-get install git -y
# Create our file to check for in future
echo "installed git!" >> /home/stuart/installed-git.txt
Removes Option
If we created an uninstall script, we would want that to also only executed once, and only if we have already executed the installation script.
Thus we would want to check if the installed-git.txt
file exists, and only execute if it does. Luckily the --removes
option provides this functionality.
For this, our uninstall.yml playbook would be like so:
- hosts: my-servers
sudo: true
tasks:
- script: /home/stuart/scripts/uninstall-git.sh --removes /home/stuart/installed-git.txt
Our local uninstall-git.sh script would be like so:
#!/bin/bash
sudo apt-get remove git -y
# Remove our file
rm /home/stuart/installed-git.txt
Just like the --creates
option, it does not implicitly remove the file for you, it only make ansible execute the script the file already exists. Your script has to remove the file.
Get Output Of Script
If the purpose of your script is to do something like tell you if the server needs rebooting or not, then you probably want to be able to see the output of the script.
For example, here is a script that will check if the server's /var/run/reboot-required
file is set, indicating that the server needs rebooting.
#!/bin/bash
if [ -f /var/run/reboot-required ]; then
echo 'reboot required'
else
echo "I'm good."
fi
We need to be able to see if the script outputted reboot required or just I'm good. To do this we use the register
keyword in our playbook and then use debug
like so:
- hosts: apt-servers
tasks:
- name: Check if reboot is required
script: /home/ansible/scripts/check.reboot.required.sh
register: output
- debug: var=output.stdout_lines
This should result in you being able to see the ouptut in the debug section like so:
TASK [debug] ***********************************
ok: [server1.programster.org] => {
"output.stdout_lines": [
"reboot required"
]
}
ok: [server2.programster.org] => {
"output.stdout_lines": [
"reboot required"
]
}
ok: [server3.programster.org] => {
"output.stdout_lines": [
"I'm good."
]
}
Coloring Output
I wanted to take this further and color the I'm good responses in green, and the reboot required responses in red. The script would look as follows:
#!/bin/bash
if [ -f /var/run/reboot-required ]; then
echo -e "\e[31mreboot required"
else
echo -e "\e[32mI'm good."
fi
Unfortunately, rather than getting the colored responses, you get the code back like so:
ok: [server3.programster.org] => {
"output.stdout_lines": [
"\u001b[31mreboot required"
]
}
There are two ways to get around this. You can go with a full solution of using a callback plugin (which I have yet to do) or use the following line in your BASH script before you call the playbook....
export ANSIBLE_STDOUT_CALLBACK=debug
... and use at least one -v
. E.g.
export ANSIBLE_STDOUT_CALLBACK=debug
ansible-playbook /home/ansible/playbooks/check.reboot.required.yaml -v
The downside of this is that there is just so much output, that I found it easier just to skim over the previous setup without colored formatting.
References
- Ansible Documentation - script - Runs a local script on a remote node after transferring it
- Preserve formatting when command output is sent to a variable?
- Stack Overflow - Ansible playbook shell output
- ServerFault - Display output with Ansible
First published: 16th August 2018