Permissions / non-existent file errors on handler task attempting to source .bashrc?

I’m 99% sure this is something on my end rather than Ansible’s and in turn am seeking some pointers as to what I’m doing wrong. In order to save replicating a lot of the basic infrastructure I have in place I’ve pushed my HEAD version of my repository up to:
https://github.com/stevenhaddox/ansible_rails_enterprise/tree/gnu_stow

When I run the ansible provisioner (manually or via vagrant) I receive the following stack trace stating that there is “No such file or directory”:
https://gist.github.com/stevenhaddox/a17aa682c7da7dfb3ffc

Oddly, this same file is already being modified with lines appended to it (which is what triggers the handler to be called in the first place).

The setup is “Enterprise” oriented so it basically works that all command are going via sudo from a user with sudo rights to a regular (limited) user account. In this case we login as vagrant and sudo is set to True and sudo_user is sysadmin (a generic user with no sudo rights that I made on the base box). I can see the handler task command running with the same syntax as the tasks in the playbook that work fine so I’m not sure why it’s struggling to find the file to source…

Overall this one seems like I’ve got to be making some blatantly obvious mistake I’m not seeing. It’s too simple a setup to have it be Ansible’s fault I’d assume.

The handler itself looks like:

https://github.com/stevenhaddox/ansible_rails_enterprise/blob/gnu_stow/provisioning/roles/common/handlers/main.yml

$ cat provisioning/roles/common/handlers/main.yml

Questions about the vagrant support for ansible in vagrant should really be asked on the vagrant list.

We don’t maintain them.

This isn’t vagrant related at all. I’m using vagrant to run my playbook, but I get the same error and stack trace when running the playbook directly with:

$ ansible-playbook -i provisioning/hosts/vagrant -u vagrant -vvv provisioning/site.yml

You can see this at the following gist:

https://gist.github.com/stevenhaddox/460887039aa6b7ffb88f

Sorry if I wasn’t more clear on that and thanks for your help. I just don’t understand why it’s able to add a line to the .bashrc file of the sudo_user when done as a playbook task, but it can’t seem to source it when called from the command module in a handler task.

-Steven

You did say “When I run the ansible provisioner” … which is a vagrant term.

You can see where this may be confusing :slight_smile:

Anyway, in your output:


<33.33.33.10> REMOTE_MODULE command source /home/sysadmin/.bashrc

You're using the command module to source .bashrc.

That's fine, but it doesn't exist, and that's an error.  Fine, no?  How would that work if it didn't exist.

You should note that this isn't going to do anything in Ansible though.  If you want to load environment variables, you can use the "environment:" keyword.

Michael,

I can definitely see where I misguided saying I was running the playbook via vagrant’s provisioner.

As to the output, it states the file doesn’t exist, but it most certainly does exist. It modifies the file earlier in the playbook tasks that actually invoke the handler:
https://github.com/stevenhaddox/ansible_rails_enterprise/blob/gnu_stow/provisioning/roles/common/tasks/main.yml#L13-L22

https://github.com/stevenhaddox/ansible_rails_enterprise/blob/gnu_stow/provisioning/roles/common/tasks/main.yml#L24-L33

Those two tasks both execute properly and append the properly lines to the user’s ~/.bashrc file as expected, but the handler that’s called throws an error about not being able to find the file which is where I’m not sure what I’m doing wrong:
https://github.com/stevenhaddox/ansible_rails_enterprise/blob/gnu_stow/provisioning/roles/common/handlers/main.yml

I’m not expecting it to modify Ansible’s environment at all. The goal of this is to export variables in ~/.bashrc for the user who will be using those variables a lot later in the playbook(s) for this setup.

Thanks again,

Steven

Dug into this a little bit more and wanted to confirm that the file is definitely there when being run properly. The following handler command lines yield the following results, but as soon as I switch it from “echo” or “cat” to “source” it blows up stating the file doesn’t exist…

command: echo {{HOME.stdout}}/.bashrc #=>

NOTIFIED: [source bash] *******************************************************
changed: [33.33.33.10] => {“changed”: true, “cmd”: [“echo”, “/home/sysadmin/.bashrc”], “delta”: “0:00:00.001029”, “end”: “2013-11-18 22:20:43.892515”, “item”: “”, “rc”: 0, “start”: “2013-11-18 22:20:43.891486”, “stderr”: “”, “stdout”: “/home/sysadmin/.bashrc”}

command: cat {{HOME.stdout}}/.bashrc #=>

NOTIFIED: [source bash] *******************************************************

changed: [33.33.33.10] => {“changed”: true, “cmd”: [“cat”, “/home/sysadmin/.bashrc”], “delta”: “0:00:00.001707”, “end”: “2013-11-18 22:18:41.067129”, “item”: “”, “rc”: 0, “start”: “2013-11-18 22:18:41.065422”, “stderr”: “”, “stdout”: “# .bashrc\n\n# Source global definitions\nif [ -f /etc/bashrc ]; then\n\t. /etc/bashrc\nfi\n\necho "bashrc output!"\nexport STOW=/home/sysadmin/opt/stow\nexport PATH=${STOW}/…/bin:${PATH}”}

Source is normally a shell-builtin, not an actual executable. As
you're calling the command module rather than the shell module, it
doesn't run an actual shell and so it can't find an executable called
'source'. That likely explains the error you are seeing, and using
'shell' rather than 'command' will likely solve that.

That said, 1) I don't believe Ansible runs two consecutive shell tasks
in the same shell anyway, and 2) it uses /bin/sh rather than the
user's default shell, making the point moot.

The goal of this is to export variables in ~/.bashrc for the user who will be using those variables a lot later in the playbook(s) for this setup.

The above issues of making it work aside, relying on the user's bashrc
sounds very brittle. What if he/she decides to start using another
shell in the future (such as zsh for example)? Is there a specific
reason you can't just set any necessary environment variables in your
playbook, and/or call programs by their full path instead of relying
on $PATH?

Nick,

Thanks very much for your reply and thoughts. This has helped tremendously (and will help as I write the rest of these playbooks). Alright… here goes:

Source is normally a shell-builtin, not an actual executable. As
you’re calling the command module rather than the shell module, it
doesn’t run an actual shell and so it can’t find an executable called
‘source’. That likely explains the error you are seeing, and using
‘shell’ rather than ‘command’ will likely solve that.

Thanks for this. The thought hadn’t even occurred to me that command vs shell would’ve been a way to fix it or that command wouldn’t recognize source. As Gru says, “liiiight buuulb!” I changed the module to shell quickly and it looks like that definitely got it working instantly for that particular command.

That said, 1) I don’t believe Ansible runs two consecutive shell tasks
in the same shell anyway, and 2) it uses /bin/sh rather than the
user’s default shell, making the point moot.

As to point 1 I actually had this exact thought as I was laying my head down on the pillow last night and realized I may be trying to solve a task that doesn’t even need to exist as all subsequent commands would probably already have sourced the newly updated ~/.bashrc when ansible connects to issue the next command. Do we know for a fact that this is how it occurs? What about in “fireball” mode? Does that still reconnect for each subsequent task? Trying to figure out if my playbook will be properly usable in all modes that Ansible could be run in.

As to point 2 this could give extra emphasis as to your point you made later. I was quasi-expecting Ansible to use the non-login shell (e.g. the user that’s sudoed into would have their ~/.bashrc loaded before executing commands) and that’s why I thought to put these variables here for Ansible (primarily just due to Linux/Unix defaults). I’ll get into why they go here in my particular situation next.

The above issues of making it work aside, relying on the user’s bashrc
sounds very brittle. What if he/she decides to start using another
shell in the future (such as zsh for example)?

Okay, so to put this all in perspective… I’m using Ansible to automatically provision / setup servers that have very restrictive access controls. Once I login as a non-privileged user with my SSH key I’m then able to sudo into only two other users. One is meant to install libraries / tools from source, the other to deploy the application. This is a pretty common Enterprise / restrictive setup in my experience. As a result I have learned to very strongly love GNU Stow which allows me to basically have my own /opt dir in my home folder and handles symlinking active tools / packages / libraries and all their differing subfolders, etc. into a target directory (one folder above the path where you stow things by default). Rather than having to ensure that everyone remembers the stow path properly (there’s a few of us and we’re all human after all) I’ve taken to setting a $STOW environment variable that’s loaded in the .bashrc so that we can ensure that we always use the same directory. We also want to use all the programs we’ve stowed away like pack rats so I’ve gotten used to prepending PATH with $STOW/…/bin so that we have access to git, tmux, vim, python, ruby, node, bzip2, etc., etc. all compiled from source. This way we don’t have to prepend / remember the stow directory and we don’t have to invoke git with: $ stow_path/…/bin/git, we can just simply call $ git and get the proper version.

Now… as to why .bashrc. I touched on this lightly above, but basically I need to have somewhere for these global environment variables to be set, and to my knowledge the best place for that in Linux / Unix has always been the .bashrc file. Granted, if a user somehow installs (from source in my situation) zshell or another alternative then they don’t get these variables, but keep in mind I’m 9/10 times using this to provision staging and production boxes where users shouldn’t be customizing things to their desired intent. My goal is to use Ansible to get rid of those customizations on those boxes and have a reliably consistent environment. I don’t think I’ve ever used a production box with something besides the default bash shell running and so .bashrc has always been the best solution. I would love to level up and be taught how to do this properly / differently if that isn’t correct (just so I don’t come off defensive, trying to explain my thought process, not seem stubborn as I’m 100% open to change if I’m doing it wrong).

Is there a specific reason you can’t just set any necessary environment
variables in your playbook, and/or call programs by their full path instead
of relying on $PATH?

So… the problem with putting these variables into my playbook instead of directly into $PATH is that we do our deployments using Capistrano (I’ve used it for 5 years and couldn’t be happier and have no desire to use a provisioning tool for something as simple as deployments). In turn this means that any environment variables set in Ansible playbooks won’t be accessible to my deployment environments. It also means that I’d either have to rewrite everything to deploy with Ansible (which isn’t the community best practice in Ruby world, even to use Chef or Puppet wouldn’t be ideal for the vast majority of teams) or it means I’ll have to duplicate variables in my playbook environment variables and then again somewhere in my tasks to setup those variables for the user when they login. I was expecting Ansible to load a user’s .bashrc for commands given that I’m logging in specifically to that user’s account with the sudo_user & sudo variables in my playbook; however, I can see why Ansible wouldn’t do that (especially if a user has a different shell potentially rather than bash which Ansible seems to have an expectation for as well).

So that leads to yet another question… Is there a best practice to be able to get Ansible tasks to load my .bashrc if I’m sure it’ll be there before running the shell module or am I likely to have to always do the following?

shell: source {{HOME.stdout}}/.bashrc;

The next question is, is there an alternative approach to storing environment variables / modified $PATH without having to use the .bashrc but that would still be accessible for a user actually logged into the box from a remote terminal? I’ve been playing with server setup / maintenance as a side-duty of all my jobs for about a decade and haven’t come across any, but it’s also not my full-time $dayjob and I’m not an expert by any means. I’d love to hear suggestions or tips from others as to a better approach than putting $STOW and $PATH modifications / exports in the .bashrc of the sysadmin user.

Thanks so much for all of your help and time,

Steven

PS - Is it possible that I could somehow get Ansible to load / source the {{HOME.stdout}}/.bashrc as part of an environment in Ansible and then just pass that environment in? I’m not familiar at all with the environment module. It looks like it’s mostly for setting variables so I’m wondering if maybe I’d at least be best off using an environment variable to store the command to source .bashrc as a way to at least ensure it could be easily modified without changing every subsequent task later, but ideally it’d be awesome to get the shell to load the ~/.bashrc as the first step for anytime a task uses that environment, but I’m not sure if that’s possible…

nope, environment keyword is your friend.

This is a configuration management and deployment system, it’s designed to not trust the user’s configuration on purpose.

Rodger that. Now if I could just get the environment module to work as expected… I’m adding an environment variable to the global_vars/all.yml file where I have other variables set but it’s not picking it up when I add environment: <environment_var_name> for some reason. I’ll keep debugging this and once it’s done I should be pretty much good to go through most of my playbook tasks (hopefully).

Thanks again,

Steven

Environment works for sure, show us what you have now and what ansible version you are using?

– Michael

Thanks very much for your reply and thoughts. This has helped tremendously
(and will help as I write the rest of these playbooks)

Awesome, glad I could help!

So... the problem with putting these variables into my playbook instead of
directly into $PATH is that we do our deployments using Capistrano (I've
used it for 5 years and couldn't be happier and have no desire to use a
provisioning tool for something as simple as deployments). In turn this
means that any environment variables set in Ansible playbooks won't be
accessible to my deployment environments. It also means that I'd either have
to rewrite everything to deploy with Ansible (which isn't the community best
practice in Ruby world, even to use Chef or Puppet wouldn't be ideal for the
vast majority of teams) or it means I'll have to duplicate variables in my
playbook environment variables and then again somewhere in my tasks to setup
those variables for the user when they login.

In cases like these, I usually recommend to decouple variables from
the deployment/configuration tool itself. Have the tools you use all
pull their configuration data from some source using, in Ansible's
case, inventory scripts for example, as these can also set variables
for hosts. If the shared set of variables is small, another option may
be to include them as vars files in Ansible, and have an adapter of
some kind for Capistrano that can read the same YAML files to get the
data there.

This is more work, so admittedly it's a trade off whether or not it's
worth it, but it does bring other advantages with it as well.

So that leads to yet another question... Is there a best practice to be able
to get Ansible tasks to load my .bashrc if I'm sure it'll be there before
running the shell module or am I likely to have to always do the following?

shell: source {{HOME.stdout}}/.bashrc; <cmd>

Like Michael said, environment is your friend. That said, I don't see
a reason why you couldn't do this if you need to rely on an
environment that is set up by bashrc. It's hacky and can be
unpredictable, but if you're aware of the associated risks, I say go
for it. Sometimes doing the hacky thing that just works is preferable
over an ideal-world solution that takes a lot of effort to develop. :slight_smile:

The next question is, is there an alternative approach to storing
environment variables / modified $PATH without having to use the .bashrc but
that would still be accessible for a user actually logged into the box from
a remote terminal? I've been playing with server setup / maintenance as a
side-duty of all my jobs for about a decade and haven't come across any, but
it's also not my full-time $dayjob and I'm not an expert by any means. I'd
love to hear suggestions or tips from others as to a better approach than
putting $STOW and $PATH modifications / exports in the .bashrc of the
sysadmin user.

I'm not aware of any universal way to do so, either.

" So… the problem with putting these variables into my playbook instead of

directly into $PATH is that we do our deployments using Capistrano (I’ve
used it for 5 years and couldn’t be happier and have no desire to use a
provisioning tool for something as simple as deployments). In turn this
means that any environment variables set in Ansible playbooks won’t be
accessible to my deployment environments"

vars:
base_path: {{ lookup(‘env’,‘SOME_ENV_VAR’ }}

or use -e on the command line to pass in a base path.

Michael,

Thanks. I have no doubt that environment works properly, I think I’m just have something setup slightly wrong as newb (sorry).

Here’s my current debug output (-v and -vvv): https://gist.github.com/stevenhaddox/26c8e0bd093887fab2b0

Here’s my current code setup: https://github.com/stevenhaddox/ansible_rails_enterprise/tree/gnu_stow

Specifically I’m trying to set my environment variable(s) in the provisioning/group_vars/all.yml file:
https://github.com/stevenhaddox/ansible_rails_enterprise/blob/gnu_stow/provisioning/group_vars/all.yml

And I’m trying to load the environment in the task Copy Stow src file to remote node in my common role tasks:
https://github.com/stevenhaddox/ansible_rails_enterprise/blob/gnu_stow/provisioning/roles/common/tasks/main.yml

Oh, Ansible on the Mac I’m running this on is currently at 1.3.4.

Hope this helps,

Steven

Sorry, I don’t have time to debug playbooks.

Happy to answer specific questions.

Completely understandable. I’m pretty sure I was expecting environment to set variables that would also be usable in the template / action lines but they only set variables for use within the shell of the command I think is what I’ve gathered between the documents & testing.

Thanks!

Yep, it’s per task.

– Michael