exec $SHELL executes from SSH, won't execute i

2019-08-11 07:07发布

问题:

I'm trying to provision Ruby + Rails on a guest VM. Here's what I have in my playbook.yml:

---
- hosts: all
  sudo: true
  tasks:
    - apt: update_cache=yes
    - apt: name={{ item }} state=present
      with_items:
        - build-essential
        - git-core
        - zlib1g-dev
        - libssl-dev
        - libreadline-dev
        - libmysqlclient-dev
        - libyaml-dev
        - libxml2-dev
        - libxslt1-dev
        - libcurl4-openssl-dev
        - python-software-properties
        - libffi-dev
        - curl
    - command: git clone git://github.com/sstephenson/rbenv.git .rbenv
    - shell: echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
    - shell: echo 'eval "$(rbenv init -)"' >> ~/.bashrc
    - shell: exec $SHELL
    - command: git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
    - shell: echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
    - shell: exec $SHELL
    - command: git clone https://github.com/sstephenson/rbenv-gem-rehash.git ~/.rbenv/plugins/rbenv-gem-rehash
    - command: rbenv install 2.2.3
    - command: rbenv global 2.2.3
    - shell: echo 'gem{{":"}} --no-ri --no-rdoc' >> ~/.gemrc
    - command: gem install bundler
    - command: add-apt-repository -y ppa:chris-lea/node.js
    - apt: update_cache=yes
    - apt: name=nodejs state=present
    - command: gem install rails -v 4.2.2
    - command: rbenv rehash

When provisioning, Ansible always hangs at task - shell: exec $SHELL and doesn't return. But when I SSH into the machine and run exec $SHELL, it executes and returns. When I use - command: exec $SHELL instead in my playbook, Ansible prints this error:

failed: [default] => {"cmd": "exec /bin/bash", "failed": true, "rc": 2}
msg: [Errno 2] No such file or directory

I can confirm that /bin/bash is present.

What's going on? I've been struggling with this for half a day now. Please advise.

回答1:

Well first of all I wouldn't recommend that you do things like this:

- shell: echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
- shell: echo 'eval "$(rbenv init -)"' >> ~/.bashrc

If you happen to run this playbook against a host multiple times then you'll end up with multiple instances of each of these commands in your .bashrc file, which you don't want. You should consider templating your .bashrc file, or at the very least using something like lineinfile to make sure these entries are added only once.

As far as this task goes:

- shell: exec $SHELL

This is something you simply should never do. This certainly won't have the result you may be expecting, and it indicates that you don't fully understand how tasks (and in particular the shell task) work. exec replaces the exiting shell with the specified command. If you didn't get an error when attempting this then what you would effectively be doing is replacing the shell launched by Ansible to run this command. What would theoretically happen is that the shell Ansible launches gets replaced with another shell, and Ansible would then simply hang because its waiting for the shell to terminate, but the shell you exec'd isn't doing anything so it will never terminate.

Once this task executed your playbook wouldn't continue running. You would either need to ctrl+c to terminate ansible-playbook, or you'd need to log into the remote host and kill the shell ansible is waiting on, at which point the playbook would terminate due to the task failing.

Switching this to -command: shell $SHELL also fails because exec is a built-in shell command, but as the Ansible documentation for the command module states:

The given command ... will not be processed through the shell

Since the command module is executing the command directly and not through a shell then shell built-in's like exec won't be recognized. This is why you get the error message that you indicated above.

Perhaps you should provide a detailed explanation of what you're trying to do and why you think you need to be invoking exec $SHELL in this manner.

You should also consider changing tasks like these:

 - command: gem install bundler

to use the Ansible gem module instead. In general you want to use the built-in Ansible modules wherever possible, and only use shell/command, etc. as a last resort. The built-in modules provide better error handling, parameter validation, etc. among other things.