Ansible Shell Module Examples

 

Ansible shell module is designed to execute Shell commands against the target Unix based hosts. Unlike the Ansible command module, Ansible Shell would accept any highly complexed commands with pipes, redirection etc and you can also execute Shell scripts using Ansible Shell module.

Ansible Shell

The Advantage of Ansible Shell module of supporting highly complexed commands with pipes and semicolons  can also be a disadvantage from the security perspective as a single mistake could cost a lot and break the system integrity. But there are some security best practices you can follow while using Shell module and you will be fine. It will also be discussed in this post.

Before we proceed further to see the examples. I must mention something important.

Ansible Shell module is designed to work only with Linux based Machines and not Windows. For windows you should use win_shell module

Though Ansible Shell module can be used to execute Shell scripts. Ansible  has a dedicated module named Script which can be used to copy the Shell script from the control machine to the remote server and to execute.

Based on your requirement you can use either Script or Shell module to execute your scripts.

Let us see the syntax of how to use ansible shell module in Adhoc and playbooks.

 

Index

  • Quick Syntax of Ansible Shell in ADHOC
  • Quick Syntax of Ansible Shell in Playbook
  • Ansible Shell module Examples
    1. Ansible Shell module to execute a Single Command
    2. Ansible Shell - Execute a Command with Pipe and Redirection
    3. Execute a Shell Script with Ansible Shell command
    4. Run a Shell command in a specific directory
    5. Run a Shell Command only if the file present or not present
    6. Using Templated Variable and Usage of Quote Filter - Security Best practice
    7. Execute Multiple commands in a Single Shell
  • Conclusion

 

A Quick Syntax of Ansible Shell module - ADHOC

Here is the quick Syntax of Ansible Shell module in ADHOC manner.

Ansible Shell

To know more about Ad hoc commands and how to use them please refer to the following Cheat Sheet.

Ansible AD HOC commands Cheatsheet

 

A Quick Syntax of Ansible Shell module in a Playbook

The Beauty of Playbook is the way it looks and written. Since it is written in yaml it can be easily understood as well.

The following picture would demonstrate how an ADHOC command would be transformed as a PLAY of a Ansible Playbook.

If you are new to Playbook. I recommend you reading this article before proceeding further

Ansible Playbook Introduction and Example

 

 

Ansible Shell Examples

  1. Ansible Shell module to execute a Single Command
  2. Ansible Shell - Execute a Command with Pipe and Redirection
  3. Execute a Shell Script with Ansible Shell command
  4. Run a Shell command in a specific directory
  5. Run a Shell Command only if the file present or not present
  6. Using Templated Variable and Usage of Quote Filter - Security Best practice
  7. Execute Multiple commands in a Single Shell

 

Before we proceed.  I presume that you have basic knowledge about Ansible and know what is hostgroup, Play etc. If not. please read the Ansible Playbook Introduction and come back here

 

 

Example 1: Execute a Single Command with Ansible Shell

Our First Example is to run any command on the remote host using the Shell module.

Rather taking any command, Let us follow the same convention we have followed in this post and get the date of the remote server. In my case the remote server is under the hostgroup named testservers

---
  - name: Shell Examples
    hosts: testservers
    tasks:

    - name: Check Date with Shell command
      shell:
         "date"
      register: datecmd
      tags: datecmd

    - debug: msg="{{datecmd.stdout}}"

In the preceding example, you can see that we are running our playbook against a hostgroup named testservers and executing a simple date command and saving the output of that command into a Register variable named datecmd

At the last line, we are retrieving the registered variable and printing only the date command output stored in the  stdout property of datacmd

Here is the execution output of the same playbook as recorded

Hope this gives you an idea about the playbook and how Shell module works. Let's explore more.

 

Example 2: Execute a Command with Pipe and Redirection

As said earlier Shell module would accept the Linux commands with symbols such as Pipes, Redirections and Semicolons.

Now let us take some Linux command with Redirection and Pipes.

Let us suppose that we want to execute a given below command with Pipe and redirection. It is a basic  ls command with some processing with awk and we remove the empty lines with sed and redirect the final output to a file named dirlist.txt

ls -lrt|awk '{print $9}'|sed '/^$/d' > /tmp/dirlist.txt

Here is the playbook with the preceding command.

 

---
  - name: Shell Examples
    hosts: testservers
    tasks:

    - name: Dir list and write to file
      shell:
         " ls -lrt /apps|awk '{print $9}'|sed '/^$/d' > /tmp/dirlist.txt "
      register: lsout
      tags: lsout

    - name: Display the file
      shell: cat /tmp/dirlist.txt
      register: displaylist
      
    - debug: msg="{{displaylist.stdout_lines}}"

In the preceding playbook, we are executing the command without any modification and on the next play Display the file we are trying displaying the file content with cat and saving the output to a register variable named displaylist

At last, we are using debug to display the output saved in the displaylist variable

Here is the execution output of the playbook.

 

 

Example 3:  Execute a Shell Script with Shell command

Though it is recommended to use script module for executing the shell script.  Shell  module can also execute the scripts just the way you would execute it on your terminal.

Here is the playbook which starts the tomcat server on the remote server by executing the tomcat's startup script.

---
  - name: Shell Examples
    hosts: testservers
    tasks:

    - name: Start tomcat
      become: yes
      become_user: tomcat
      async: 10
      poll: 0
      shell:
         "./startup.sh"
      args:
        chdir: "/apps/tomcat/tomcat8/bin" 
      register: datecmd
      tags: datecmd

    - name: Validate if tomcat is UP
      tags: tomvalidate
      wait_for:
        host: "localhost"
        port: 8080
        delay: 10
        timeout: 30
        state: started
        msg: "Tomcat server is not running"

There are playbooks in the playbook, The First one is to start the tomcat using the shell module and the another is to validate if the service is running by validating the port status  using  wait_formodule.

 Some Important Note *:

You might wonder why I have used the async  and the poll directives here along with the shell module.

The reason is that my Tomcat JVM is a long running process, If I try to start my Tomcat without this asynchrnous flag. My Tomcat process would be stopped as soon as my play is completed.

If you want your command or the script you are using in Shell  to start something which should be UP and RUNNING even after your playbook completed. You should use async flag

In other words this is called `Fire and Forget` in Ansible parlance. I will write a post about this shortly.

Here is the execution output of the preceding Playbook.

 

 

Example 4: Run a Shell Command in a Specific directory

Sometimes we want to execute a command after cd ing into some specific directory.  For example, you can consider the previous example of starting tomcat. If you have noticed, we used a parameter named chdir to specify in which directory the command should be executed.

So this example is about the same chdir parameter.

Since this parameter is self-descriptive. I hope you can easily understand it without further ado.

In the previous example we have already used this chdir .However, I will give one more example down below

---
  - name: Shell Examples
    hosts: testservers
    tasks:

    - name: Open /etc/password file
      become: yes
      async: 10
      poll: 0
      shell:
         "cat password"
      args:
        chdir: "/etc" 
      register: fileout
      tags: fileout

    - debug: msg="{{ fileout.stdout-lines }}"

We all know that the Linux passwd file is available in the /etc directory.  I am trying to display the content of this file, In order to explain this chdir, I have made my shell command with just a file name and not the path.

I am passing the directory name with the help of  chdir so the this play would successfully display the content of  etc/passwd

 

 

Example 5: Run a Shell Command based on file availability

We cannot always go blind and try to execute a command/script without any prior validation. In most of the cases, it might be necessary that we should check if the execution is really needed?

There are various conditional based execution methods available in Ansible. Here we are going to see a quick one, almost built-in with many modules.

The creates parameter.

All it does is to tell a Shell that the command we are executing would create some file.  As a biproduct it also validates if the file is already available and skips the command execution if the file is already present. Thereby avoiding a Duplicate process Creation and protect the system integrity by an accidental overdo.

Here is a simple example with creates in it.

---
  - name: Shell Examples
    hosts: testservers
    tasks:

    - name: Start the DB batch upload
      become: yes
      async: 10
      poll: 0
      shell:
         "./startbatchupload.sh"
      args:
        chdir: "/apps/dbscripts/"
        creates: "startbatchupload.lock" 
      register: fileout
      tags: fileout

What we do in the previous example is that we try to start an important db patch process after validating the presence of a lock file startbatchupload.lock

The lock file is supposed to be created by the script that we are trying to execute. If it is already present which means there is another instance of the same script already running on the server. Thats the logic behind this validation.

 

Example 6:  Usage of quote filter to avoid Injection

In all these previous examples, we were executing a Shell commands directly specified in the Shell module. We have not  used any variables and templating like declaring the command to be executed as a varible and calling it out later in the play.

I presume you are aware of the Jinja2 templates in Ansible. It is the way we should access the variables inside the playbook. ( more like a $VARIABLE_NAME in shell)

First, let us see how to declare the command as variable and use it later.

For better understanding, Let me take the same playbook we have used in Example 5 and make some modification.

---
  - name: Shell Examples
    hosts: testservers
    vars:
      - script-to-start: "./startbatchupload.sh"
    tasks:
    - name: Start the DB batch upload
      become: yes
      async: 10
      poll: 0
      shell:
          "{{ script-to-start }}"
      args:
        chdir: "/apps/dbscripts/"
        creates: "startbatchupload.lock" 
      register: fileout
      tags: fileout

In the preceding playbook you can see that we have declared the script name as a variable and calling it out later inside the play using Jinja2 filter "{{ script-to-start }}"

Thought it would look Good and work good. It has a security issue. As it is prone to Injection attack it is more like an `SQL injection` where the hacker can change the command at the runtime.

In order to avoid the same. Ansible recommends using quote filter whenever you have to template the variable in the Shell module like given below.

 WRONG WAY : "{{ script-to-start }}"

 THE RIGHT WAY :  "{{ script-to-start | quote }}"

as shown in the above snippet, the right way to template the Shell Command is to use a quote at the end.

the modified playbook would like this

---
  - name: Shell Examples
    hosts: testservers
    vars:
      - script-to-start: "./startbatchupload.sh"
    tasks:
    - name: Start the DB batch upload
      become: yes
      async: 10
      poll: 0
      shell:
          "{{ script-to-start | quote}}"
      args:
        chdir: "/apps/dbscripts/"
        creates: "startbatchupload.lock" 
      register: fileout
      tags: fileout

 

Example 7:  Execute multiple commands in a Single Shell - Play

In all the previous examples we have only seen a Single command in a Shell module, Last but not least you should also know that Shell can accept multiple commands ( a batch) together in a Single Shell Play.

In fact, You can write your own Shell script alike inline with Ansible Shell module.

Here is the playbook, where I have grouped some shell commands to perform a Controlled and Clean tomcat restart.

The playbook is designed to perform the following steps in an order.

  • Stop the TomcatServer
  • Clear the Cache
  • Truncate the Log file
  • Start the instance
---
  - name: Shell Examples
    hosts: testservers
    tasks:
    - name: Clear Cache and Restart tomcat
      become: yes
      delay: 10
      async: 10
      poll: 60
      shell: |

        echo -e "\n Change directory to the Tomcat"
        cd tomcat8/

        echo -e "\n Present working directory is" `pwd`
        
        echo -e "\n Stopping the tomcat instance"
        bin/shutdown.sh
      
        echo -e "\n Clearning the tmp and work directory of tomcat"
        rm -rfv tmp/*
        rm -rfv work/*

        echo -e "\nTruncate the log file"
        > logs/catalina.out

        echo -e "\nDirectory listing"
        ls -lrtd logs/catalina.out

        echo -e "\nStarting the instance"
        bin/startup.sh
          
      args:
        chdir: "/apps/tomcat/"
      register: fileout
      tags: fileout 

    - debug: msg="{{ fileout.stdout_lines }}"

You might really get a question this time how this would run and what output it would give. No worries, refer the following screen record.


So it works!.

However!. I recommend writing a Script and using the script module instead of writing an inline script like this but let the requirement be the Judge.

 

Conclusion

Ansible Shell module is powerful and widely used, but with a lot of power comes the great fear.   As mentioned earlier, There are chances Ansible Shell module could lead to an Injection attack or break the system integrity so remember to use quote with Template variables and follow other best practices.

For controlled execution of Shell commands, You can use the Ansible Command module but as I already mentioned, Let your requirement be the judge.

Hope it helps!.

Rate this article [ratings]

Cheers
Sarav AK

Follow me on Linkedin My Profile
Follow DevopsJunction onFacebook orTwitter
For more practical videos and tutorials. Subscribe to our channel

Buy Me a Coffee at ko-fi.com

Signup for Exclusive "Subscriber-only" Content

Loading