99

I can ssh to the remote host and do a source /home/username/.bashrc - everything works fine. However if I do:

- name: source bashrc
  sudo: no
  action: command source /home/username/.bashrc

I get:

failed: [hostname] => {"cmd": ["source", "/home/username/.bashrc"], "failed": true, "rc": 2}
msg: [Errno 2] No such file or directory

I have no idea what I'm doing wrong...

pldimitrov
  • 1,597
  • 2
  • 16
  • 21
  • `source` only makes sense when you run it inside an existing shell -- it runs commands *in that shell*, and is thus only useful/helpful when there's an existing shell whose state or configuration you want to change. When you run an ansible action, that **creates a whole new shell**, and runs a command inside *that* shell -- so you wouldn't be updating the environment variables in any other context, so it wouldn't actually have any useful/lasting effecet, even if you got this to run without errors. – Charles Duffy Dec 08 '18 at 18:50
  • @CharlesDuffy If you want to execute a command that expects environment variables to be defined then trying to source something like .bashrc or .bash_profile to defien such variables is a valid use case isn't it? – htellez Jan 23 '19 at 20:00
  • @htellez, running `source` only defines variables **for the duration of the shell it runs in**. And that shell has exited (and the variables it defines been lost) by the time that ansible command exits and the next one starts. – Charles Duffy Jan 23 '19 at 20:34
  • @htellez, ...thus, the only answer here that's actually useful in any meaningful way is [the one by Steve Midgley](https://stackoverflow.com/a/27541856/14122), since it has you doing something else in the same shell that ran `source`, before it exited. – Charles Duffy Jan 23 '19 at 20:37
  • That is exactly the use case I tried to describe, I'm sorry if I wasn't clear. I tried to describe an scenario in which you want to run something that expects a particular environment defined. I got to this thread because I has getting the same error, and by reading [Steve's answer](https://stackoverflow.com/a/27541856/1399310) is that I realized that the ansible's shell task uses sh by default instead of bash. Making the command a bash command explicitly makes `source` work the way you are most likely used to. – htellez Jan 25 '19 at 06:42

10 Answers10

105

You have two options to use source with ansible. One is with the "shell:" command and /bin/sh (the ansible default). "source" is called "." in /bin/sh. So your command would be:

- name: source bashrc
  sudo: no   
  shell: . /home/username/.bashrc && [the actual command you want run]

Note you have to run a command after sourcing .bashrc b/c each ssh session is distinct - every ansible command runs in a separate ssh transaction.

Your second option is to force Ansible shell to use bash and then you can use the "source" command:

- name: source bashrc
  sudo: no   
  shell: source /home/username/.bashrc && [the actual command you want run]
  args:
     executable: /bin/bash

Finally, I'll note that you may want to actually source "/etc/profile" if you're on Ubuntu or similar, which more completely simulates a local login.

Steve Midgley
  • 2,226
  • 2
  • 18
  • 20
  • 3
    Also note this issue has been filed (and commented on by me) as a bug/feature request in Ansible core. But Ansible closed it and said "write a plugin." Bah. https://github.com/ansible/ansible/issues/4854 – Steve Midgley Feb 18 '15 at 22:37
  • 1
    Are you reading my mind? You answered this 3 moths ago, and I was thinking to edit this `.` -> `source` - and you immediately did this :) – warvariuc Mar 24 '15 at 16:42
  • 1
    I tried `source "/etc/profile"` - it didn't work for me. This worked: `source "~/.profile"` – warvariuc Mar 24 '15 at 16:43
  • @warvariuc - /etc/profile is usually found in Ubuntu - and probably some other Debian or Ubuntu derivatives. But other flavors do it differently: sourcing just the ~/.profile will usually only load the user profile context. It won't execute a lot of system-wide stuff that happens for all users when they login. For your particular Unix flavor, search around for the call stack sequence of files that are executed.. – Steve Midgley Mar 30 '15 at 20:56
  • In my case sourcing /etc/profile did not import the user configuration. That was my problem -- I had some env. variables in it. I guess sourcing ~/.profile doesn't automatically source /etc/profile – warvariuc Mar 31 '15 at 05:24
  • @warvariuc - It should work the other way around. There should be some kind of system profile file that runs on login for all users and then variously calls other script files system-wide or for the individual user.. You can see the various files involved in a "normal" login sequence here: http://how-to.wikia.com/wiki/Guide_to_linux_boot_sequence - `/etc/profile or /etc/profile.d/*.sh => ~/.bash_profile => ~/.profile => ~/.bashrc` – Steve Midgley Mar 31 '15 at 23:58
  • > Shell scripts automatically loaded for a login terminal. < My case is to load environment variables for the Ansible's `shell` ([as you already know](https://github.com/ansible/ansible/issues/4854)). `shell: . /etc/profile && ember install` did not load user's profile variables. So I had to do `shell: . ~/.profile && ember install` – warvariuc Apr 01 '15 at 05:47
  • @warvariuc - you might consider sourcing them both then. Depending on your setup you might need system stuff set up by /.profile. For example I think it loads everything in /etc/profile.d -- I believe rbenv (for example) installs itself correctly by adding a script to profile.d folder.. – Steve Midgley Apr 06 '15 at 21:29
  • executable: /bin/bash this is something that is not added to documentation – podarok Oct 22 '15 at 12:00
  • What is meant by [the actual command you want run] ? In my case the ```source``` command is the actual command I want to run. – codecowboy Jan 11 '16 at 11:51
  • @codecowboy - running source alone, afaik, won't do anything. This command is designed to load your bashrc file first, so you have the full environment context, and then run a command. As a simple example, you might have a bash alias defined in bashrc - you could run that command in [the actual command you want to run] area. Or you might run a command which is found in a folder that is added to your path by bashrc. – Steve Midgley Jan 12 '16 at 18:56
  • another (more ugly) way is to create a wrapper-script, found that idea on another problem: http://stackoverflow.com/a/20572360/1961102 – FibreFoX Apr 05 '16 at 17:13
  • 6
    I have some bash functions defined inside .bashrc and after sourcing the .bashrc. how can I execute/call those functions? I am trying `shell: . ~/.bashrc && nvm install {{ node_version }}` and it is saying, `nvm command not found`. How can I solve this? – RaviTezu May 10 '16 at 14:42
  • @RaviTezu Are you sure ~ is resolving the way you expect? If you change it to `. /home/[username]/.bashrc` does it work as expected? – Steve Midgley May 15 '16 at 04:11
  • @SteveMidgley I am facing exactly same issue as mentioned by RaviTezu. ~ does resolve to the home directory of the user. – Divick Feb 18 '17 at 08:50
  • 5
    @RaviTezu: The problem in my case was due to the following lines in .bashrc: # If not running interactively, don't do anything case $- in *i*) ;; *) return;; esac This is at least a problem on ubuntu-16.04 xenial64 where .bashrc is not run on non interactive shells which is the case when running commands via ssh. To try it out, set some PATH in ~/.bashrc and run(assuming you have set port 2222 forwarded to 22 on guest os): ssh -p 2222 ubuntu@127.0.0.1 'echo $PATH' If the above command doesn't show the PATH you have set in .bashrc then fix .bashrc – Divick Feb 18 '17 at 09:15
  • @RaviTezu Were you able to fix the `command not found` error? Facing the same issue when I put the files and sourced the `/etc/profile` – Tasdik Rahman Feb 27 '17 at 14:13
  • I wonder that `source /home/username/.bashrc` is working in bash. `source {file}` is used in `csh`, `sh` and `bash` use `. {file}` – Wernfried Domscheit May 21 '20 at 12:10
25

So command will only run executables. source per se is not an executable. (It's a builtin shell command). Is there any reason why you want to source a full environment variable?

There are other ways to include environment variables in Ansible. For example, the environment directive:

- name: My Great Playbook
  hosts: all
  tasks:
    - name: Run my command
      sudo: no
      action: command <your-command>
      environment:
          HOME: /home/myhome

Another way is to use the shell Ansible module:

- name: source bashrc
  sudo: no
  action: shell source /home/username/.bashrc && <your-command>

or

- name: source bashrc
  sudo: no   
  shell: source /home/username/.bashrc && <your-command>

In these cases, the shell instance/environment will terminate once the Ansible step is run.

Rico
  • 58,485
  • 12
  • 111
  • 141
  • 2
    almost good, unfortunately /bin/sh doesn't have source commmand only . so `shell source /home/username/.bashrc` becomes `shell . /home/username/.bashrc` – b1r3k Dec 05 '14 at 11:53
  • The shell task takes a parameter as such: `executable=/usr/bin/bash` which will then run it in bash if it is available as such. – fgysin Jan 07 '15 at 08:24
20

I know this answer come too late but I have seen in enough code you can use the sudo option -i so:

- name: source bashrc
  shell: sudo -iu {{ansible_user_id}} [the actual command you want run]

As said in the documentation

The -i (simulate initial login) option runs the shell specified by the password database entry of the target user as a login shell.  This means that login-specific
               resource files such as .profile or .login will be read by the shell.  If a command is specified, it is passed to the shell for execution via the shell's -c option.
               If no command is specified, an interactive shell is executed.  sudo attempts to change to that user's home directory before running the shell.  It also initializes
               the environment to a minimal set of variables, similar to what is present when a user logs in.  The Command environment section below documents in detail how the -i
               option affects the environment in which a command is run.
Clempat
  • 498
  • 4
  • 10
11

I was experiencing this same issue when trying to get virtualenvwrapper to work on an Ubuntu server. I was using Ansible like this:

- name: Make virtual environment
  shell: source /home/username/.bashrc && makevirtualenv virenvname
  args:
    executable: /bin/bash

but the source command was not working.

Eventually I discovered that the .bashrc file has a few lines at the top of the file that prevent source from working when called by Ansible:

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

I commented out those lines in .bashrc and everything worked as expected after that.

gwerner
  • 141
  • 1
  • 5
  • That is perfectly reasonably and standard header for most `.bashrc` files. You probably want to source a different shell file, or use `BASH_ENV` as discussed in the bash docs. –  May 03 '18 at 21:07
10

Many responses recommend to source ~/.bashrc but main problem is that ansible shell is not interactive and ~/.bashrc implementation by default ignores non interactive shell (check its beginning).

The best solution for executing commands as user after its ssh interactive login I found is:

- hosts: all
  tasks:
    - name: source user profile file
      #become: yes
      #become_user: my_user  # in case you want to become different user (make sure acl package is installed)
      shell: bash -ilc 'which python' # example command which prints
      register: which_python
    - debug:
      var: which_python

bash: '-i' means interactive shell, so .bashrc won't be ignored '-l' means login shell which sources full user profile

Juraj Michalak
  • 1,138
  • 10
  • 9
  • This is the only solution that worked for me - using `bash -ilc` . It works flawlessly. – Aleks Sep 01 '22 at 23:46
5

I've tried all the options above with ansible 2.4.1.0 and no one works until another two and here is the detail to re-produce the case.

$ cat ~/.bash_aliases 
alias ta="echo 'this is test for ansible interactive shell'";

And this is the ansible test:

- name: Check the basic string operations
  hosts: 127.0.0.1 
  connection: local

  tasks:
  - name: Test Interactive Bash Failure
    shell: ta
    ignore_errors: True

  - name: Test Interactive Bash Using Source
    shell: source ~/.bash_aliases && ta
    args:
      executable: /bin/bash
    ignore_errors: yes

  - name: Test Interactive Bash Using .
    shell: . ~/.bash_aliases && ta
    ignore_errors: yes

  - name: Test Interactive Bash Using /bin/bash -ci
    shell: /bin/bash -ic 'ta'
    register: result
    ignore_errors: yes

  - debug: msg="{{ result }}"

  - name: Test Interactive Bash Using sudo -ui
    shell: sudo -ui hearen ta
    register: result
    ignore_errors: yes

  - name: Test Interactive Bash Using ssh -tt localhost /bin/bash -ci
    shell: ssh -tt localhost /bin/bash -ci 'ta'
    register: result
    ignore_errors: yes

And this is the result:

$ ansible-playbook testInteractiveBash.yml 
 [WARNING]: Could not match supplied host pattern, ignoring: all

 [WARNING]: provided hosts list is empty, only localhost is available


PLAY [Check the basic string operations] ************************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************************************************
ok: [127.0.0.1]

TASK [Test Interactive Bash Failure] ****************************************************************************************************************************************************
fatal: [127.0.0.1]: FAILED! => {"changed": true, "cmd": "ta", "delta": "0:00:00.001341", "end": "2018-10-31 10:11:39.485897", "failed": true, "msg": "non-zero return code", "rc": 127, "start": "2018-10-31 10:11:39.484556", "stderr": "/bin/sh: 1: ta: not found", "stderr_lines": ["/bin/sh: 1: ta: not found"], "stdout": "", "stdout_lines": []}
...ignoring

TASK [Test Interactive Bash Using Source] ***********************************************************************************************************************************************
fatal: [127.0.0.1]: FAILED! => {"changed": true, "cmd": "source ~/.bash_aliases && ta", "delta": "0:00:00.002769", "end": "2018-10-31 10:11:39.588352", "failed": true, "msg": "non-zero return code", "rc": 127, "start": "2018-10-31 10:11:39.585583", "stderr": "/bin/bash: ta: command not found", "stderr_lines": ["/bin/bash: ta: command not found"], "stdout": "", "stdout_lines": []}
...ignoring

TASK [Test Interactive Bash Using .] ****************************************************************************************************************************************************
fatal: [127.0.0.1]: FAILED! => {"changed": true, "cmd": ". ~/.bash_aliases && ta", "delta": "0:00:00.001425", "end": "2018-10-31 10:11:39.682609", "failed": true, "msg": "non-zero return code", "rc": 127, "start": "2018-10-31 10:11:39.681184", "stderr": "/bin/sh: 1: ta: not found", "stderr_lines": ["/bin/sh: 1: ta: not found"], "stdout": "", "stdout_lines": []}
...ignoring

TASK [Test Interactive Bash Using /bin/bash -ci] ****************************************************************************************************************************************
changed: [127.0.0.1]

TASK [debug] ****************************************************************************************************************************************************************************
ok: [127.0.0.1] => {
    "msg": {
        "changed": true, 
        "cmd": "/bin/bash -ic 'ta'", 
        "delta": "0:00:00.414534", 
        "end": "2018-10-31 10:11:40.189365", 
        "failed": false, 
        "rc": 0, 
        "start": "2018-10-31 10:11:39.774831", 
        "stderr": "", 
        "stderr_lines": [], 
        "stdout": "this is test for ansible interactive shell", 
        "stdout_lines": [
            "this is test for ansible interactive shell"
        ]
    }
}

TASK [Test Interactive Bash Using sudo -ui] *********************************************************************************************************************************************
 [WARNING]: Consider using 'become', 'become_method', and 'become_user' rather than running sudo

fatal: [127.0.0.1]: FAILED! => {"changed": true, "cmd": "sudo -ui hearen ta", "delta": "0:00:00.007906", "end": "2018-10-31 10:11:40.306128", "failed": true, "msg": "non-zero return code", "rc": 1, "start": "2018-10-31 10:11:40.298222", "stderr": "sudo: unknown user: i\nsudo: unable to initialize policy plugin", "stderr_lines": ["sudo: unknown user: i", "sudo: unable to initialize policy plugin"], "stdout": "", "stdout_lines": []}
...ignoring

TASK [Test Interactive Bash Using ssh -tt localhost /bin/bash -ci] **********************************************************************************************************************
hearen@localhost's password: 
changed: [127.0.0.1]

PLAY RECAP ******************************************************************************************************************************************************************************
127.0.0.1                  : ok=8    changed=6    unreachable=0    failed=0  

There are two options worked:

  • shell: /bin/bash -ic 'ta'
  • shell: ssh -tt localhost /bin/bash -ci 'ta' but this one requires password input locally.
Hearen
  • 7,420
  • 4
  • 53
  • 63
3

Well I tried the listed answers but those didn't worked for me while installing ruby through rbenv. I had to source below lines from /root/.bash_profile

PATH=$PATH:$HOME/bin:$HOME/.rbenv/bin:$HOME/.rbenv/plugins/ruby-build/bin
export PATH
eval "$(rbenv init -)"

Finally, I came up with this

- shell: sudo su - root -c 'rbenv install -v {{ ruby_version }}'

One can use this with any command.

- shell: sudo su - root -c 'your command'
vikas027
  • 5,282
  • 4
  • 39
  • 51
  • 1
    This classic approach works with Ansible `2.2.0.0`. However, it nags that I should use `become`, `become_method` and `become_user` instead... I couldn't figure out a combination of those "method" params which would work anyway. – Yuri Dec 14 '16 at 14:45
2

I found become as best solution:

- name: Source .bashrc
  shell: . .bashrc
  become: true

You can change the user by adding (default: root):

- name: Source .bashrc
  shell: . .bashrc
  become: true
  become-user: {your_remote_user}

More info here: Ansible become

Brian Moeskau
  • 20,103
  • 8
  • 71
  • 73
Zlopez
  • 660
  • 1
  • 5
  • 11
1

My 2 cents, i circumnavigated the problem sourcing ~/.nvm/nvm.sh into ~/.profile and then using sudo -iu as suggested in another answer.

Tried on January 2018 vs Ubuntu 16.04.5

- name: Installing Nvm 
  shell: >
    curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash
  args:
    creates: "/home/{{ ansible_user }}/.nvm/nvm.sh"
  tags:
    - nodejs    

- name: Source nvm in ~/.profile
  sudo: yes
  sudo_user: "{{ ansible_user }}"
  lineinfile: >
    dest=~/.profile
    line="source ~/.nvm/nvm.sh"
    create=yes
  tags: 
    - nodejs
  register: output    

- name: Installing node 
  command: sudo -iu {{ ansible_user }} nvm install --lts
  args:
     executable: /bin/bash
  tags:
    - nodejs    
realtebo
  • 23,922
  • 37
  • 112
  • 189
-3

The right way should be:

- hosts: all
  tasks:
    - name: source bashrc file
      shell: "{{ item }}"
      with_items:
         - source ~/.bashrc
         - your other command

Note: it's test in ansible 2.0.2 version

Tunaki
  • 132,869
  • 46
  • 340
  • 423
kwin wng
  • 105
  • 1
  • 4