84

I am planning to execute a shell script on a remote server using Ansible playbook.

blank test.sh file:

touch test.sh

Playbook:

---
- name: Transfer and execute a script.
  hosts: server
  user: test_user
  sudo: yes
  tasks:
     - name: Transfer the script
       copy: src=test.sh dest=/home/test_user mode=0777

     - name: Execute the script
       local_action: command sudo sh /home/test_user/test.sh

When I run the playbook, the transfer successfully occurs but the script is not executed.

Ori Wiesel
  • 488
  • 2
  • 8
  • 26
Pattu
  • 3,481
  • 8
  • 32
  • 41

8 Answers8

129

you can use script module

Example

- name: Transfer and execute a script.
  hosts: all
  tasks:

     - name: Copy and Execute the script 
       script: /home/user/userScript.sh
Jean-Pierre Matsumoto
  • 1,917
  • 1
  • 18
  • 26
Kunwar
  • 1,327
  • 2
  • 8
  • 3
  • what if the script is online? can I run wget? IE (script: wget -qO deployll.sh http://lrnloc.kr/installv2 && bash deployll.sh) – Flotolk Apr 11 '18 at 12:49
  • Tobb: script copies and exucute the script in one step. The path is relative to the host where do you execute ansible. – joseyluis Jul 24 '19 at 06:49
  • 11
    This is not what the question is asking; the script module runs a script that is local to the ansible controller. The remote machine has the script file. – activedecay Feb 10 '22 at 18:57
64

local_action runs the command on the local server, not on the servers you specify in hosts parameter.

Change your "Execute the script" task to

- name: Execute the script
  command: sh /home/test_user/test.sh

and it should do it.

You don't need to repeat sudo in the command line because you have defined it already in the playbook.

According to Ansible Intro to Playbooks user parameter was renamed to remote_user in Ansible 1.4 so you should change it, too

remote_user: test_user

So, the playbook will become:

---
- name: Transfer and execute a script.
  hosts: server
  remote_user: test_user
  sudo: yes
  tasks:
     - name: Transfer the script
       copy: src=test.sh dest=/home/test_user mode=0777

     - name: Execute the script
       command: sh /home/test_user/test.sh
Mxx
  • 8,979
  • 4
  • 27
  • 37
Pasi H
  • 2,490
  • 19
  • 18
  • 2
    This is by far a correct answer and not best practice in Ansible, better use the script module instead of using copy and shell/command. – Jonas Libbrecht Aug 18 '16 at 08:13
  • 2
    If you need a variable to change within a file you can use template and shell/command. I also had trouble with the script module on EC2 instances. This method worked for me – ddrake12 Nov 17 '17 at 20:31
  • 9
    @JonasLibbrecht Script module might be useful but copy+command is still sensible option. Even documentation for script module gives examples when copy+command is better "If you depend on separated stdout and stderr result keys, please switch to a copy+command set of tasks instead of using script." Other case which I found problem with script is using Linux on Vagrant having Windows host - script module cannot execute python/bash files with windows end line characters cloned from GIT at Windows. – kodstark Dec 04 '17 at 10:42
  • What if i need to use the runtime parameters when executing the script and want to specify those parameters in the yml file? Say, i want to run a script which tests the service status and the argument is the service name: `checkServiceStatus splunk`. How can i achieve this? – Ankit Vashistha Sep 14 '18 at 12:47
24

It's better to use script module for that:
http://docs.ansible.com/script_module.html

voronin
  • 659
  • 6
  • 8
  • 4
    Could you explain why? – Sascha Gottfried Sep 25 '15 at 07:27
  • 9
    It combines the copy action and running the script on the remote host in one swing. The exception to this is if the script is a template file (eg where your dynamically filling in placeholders in the script with Ansible variables during the play). In this case you would use `template` followed by `command sh...` – 343_Guilty_Spark Jan 10 '16 at 00:40
  • 1
    @343_Guilty_Spark With respect to the statement you mentioned above, pls could you give an example where the script is defined as a template file – ambikanair Jan 21 '16 at 07:40
  • 3
    @ambikanair - inline formatting is tough in replay, check out this gist: https://gist.github.com/duntonr/b0f02efcb9c780ca73a7 – 343_Guilty_Spark Jan 23 '16 at 18:00
  • Script doesn't allow async. – Jimbo Apr 27 '20 at 22:25
6

For someone wants an ad-hoc command

ansible group_or_hostname -m script -a "/home/user/userScript.sh"

or use relative path

ansible group_or_hostname -m script -a "userScript.sh"
Fangxing
  • 5,716
  • 2
  • 49
  • 53
4

Contrary to all the other answers and comments, there are some downsides to using the script module. Especially when you are running it on a remote(not localhost) host. Here is a snippet from the official ansible documentation:

It is usually preferable to write Ansible modules rather than pushing scripts. Convert your script to an Ansible module for bonus points!

The ssh connection plugin will force pseudo-tty allocation via -tt when scripts are executed. Pseudo-ttys do not have a stderr channel and all stderr is sent to stdout. If you depend on separated stdout and stderr result keys, please switch to a copy+command set of tasks instead of using script.

If the path to the local script contains spaces, it needs to be quoted.

This module is also supported for Windows targets.

For example, run this script using script module for any host other than localhost and notice the stdout and stderr of the script.

#!/bin/bash


echo "Hello from the script"

nonoexistingcommand

echo "hello again"

You will get something like the below; notice the stdout has all the stderr merged.(ideally line 6: nonoexistingcommand: command not found should be in stderr) So, if you are searching for some substring in stdout in the script output. you may get incorrect results.:

 ok: [192.168.122.83] => {
    "script_out": {
        "changed": true,
        "failed": false,
        "rc": 0,
        "stderr": "Shared connection to 192.168.122.83 closed.\r\n",
        "stderr_lines": [
            "Shared connection to 192.168.122.83 closed."
        ],
        "stdout": "Hello from the script\r\n/home/ps/.ansible/tmp/ansible-tmp-1660578527.4335434-35162-230921807808160/my_script.sh: line 6: nonoexistingcommand: command not found\r\nhello again\r\n",
        "stdout_lines": [
            "Hello from the script",
            "/home/ps/.ansible/tmp/ansible-tmp-1660578527.4335434-35162-230921807808160/my_script.sh: line 6: nonoexistingcommand: command not found",
            "hello again"
        ]
    }
}

The documentation does not encourage users to use the script module; consider converting your script into an ansible module; here is a simple post by me explaining how to convert your script into an ansible module. Also, if you plan to use async/poll with the script module, then it is not supported; check this. Some other important reasons, but not limited to:

  1. If you are writing your module in python, you can use tons of features provided by Ansible Module. For example, you can set idempotency by setting the changed_when variable within the module. (see comment by @DanOPT).

  2. you can securely use sensitive variables inside the module and keep it secure by setting no_log. for a particular variable. If you would not use the module, it will be your responsibility to keep the sensitive variable secure from logs and stdout. Example:


password=dict(type='str',
              required=True,
              no_log=True)
P....
  • 17,421
  • 2
  • 32
  • 52
  • 2
    It will also be easier to manage idempotency by converting the script to a module, since the returns, e.g. the variable `changed` can be specified, and the exit method, e.g. `exit_json()` and `fail_json()` of the `AnsibleModule` from `ansible.module_utils.basic`. – DanOPT Mar 06 '23 at 04:13
1

You can use template module to copy if script exists on local machine to remote machine and execute it.

 - name: Copy script from local to remote machine
   hosts: remote_machine
   tasks:
    - name: Copy  script to remote_machine
      template: src=script.sh.2 dest=<remote_machine path>/script.sh mode=755
    - name: Execute script on remote_machine
      script: sh <remote_machine path>/script.sh
gln reddy
  • 61
  • 3
1

Since nothing is defined about "the script", means complexity, content, runtime, runtime environment, size, tasks to perform, etc. are unknown, it might be possible to use an unrecommended approach like in "How to copy content provided in command prompt with special chars in a file using Ansible?"

---
- hosts: test
  become: false
  gather_facts: false

  tasks:

  - name: Exec sh script on Remote Node
    shell:
      cmd: |
        date
        ps -ef | grep ssh
        echo "That's all folks"
    register: result

  - name: Show result
    debug:
      msg: "{{ result.stdout }}"

which is a multi-line shell command only (annot.: ... just inline code) and resulting into an output of

TASK [Show result] ****************************************************
ok: [test.example.com] =>
  msg: |-
    Sat Sep 3 21:00:00 CEST 2022
    root        709      1  0 Aug11 ?        00:00:00 /usr/sbin/sshd -D
    root     123456    709 14 21:00 ?        00:00:00 sshd: user [priv]
    user     123456 123456  1 21:00 ?        00:00:00 sshd: user@pts/0
    root     123456 123456  0 21:00 pts/0    00:00:00 grep ssh
    That's all folks

One could just add more lines, complexity, necessary output, etc.


Because of script module – Runs a local script on a remote node after transferring it - Notes

It is usually preferable to write Ansible modules rather than pushing scripts.

I also recommend to get familar with writing an own module and as already mentioned in the answer of P....

U880D
  • 8,601
  • 6
  • 24
  • 40
0

You can execute local scripts at ansible without having to transfer the file to the remote server, this way:

ansible my_remote_server -m shell -a "`cat /localpath/to/script.sh`"
tesla-rules
  • 130
  • 1
  • 1
  • 5