Ansible command from inside virtualenv?

2019-03-08 08:07发布

问题:

This seems like it should be really simple:

tasks:
- name: install python packages
  pip: name=${item} virtualenv=~/buildbot-env
  with_items: [ buildbot ]
- name: create buildbot master
  command: buildbot create-master ~/buildbot creates=~/buildbot/buildbot.tac

However, the command will not succeed unless the virtualenv's activate script is sourced first, and there doesn't seem to be provision to do that in the Ansible command module.

I've experimented with sourcing the activate script in various of .profile, .bashrc, .bash_login, etc, with no luck. Alternatively, there's the shell command, but it seems like kind of an awkward hack:

- name: create buildbot master
  shell: source ~/buildbot-env/bin/activate && \
         buildbot create-master ~/buildbot \
         creates=~/buildbot/buildbot.tac executable=/bin/bash

Is there a better way?

回答1:

The better way is to use the full path to installed script - it will run in its virtualenv automatically:

tasks:
- name: install python packages
  pip: name={{ item }} virtualenv={{ venv }}
  with_items: [ buildbot ]
- name: create buildbot master
  command: "{{ venv }}/bin/buildbot create-master ~/buildbot
            creates=~/buildbot/buildbot.tac"


回答2:

This is a genericized version of the wrapper method.

venv_exec.j2:

#!/bin/bash
source {{ venv }}/bin/activate
$@

And then the playbook:

tasks:
  - pip: name={{ item }} virtualenv={{ venv }}
    with_items:
      - buildbot
  - template: src=venv_exec.j2 dest={{ venv }}/exec mode=755
  - command: "{{ venv }}/exec buildbot create-master {{ buildbot_master }}"


回答3:

Here's a way to enable the virtualenv for an entire play; this example builds the virtualenv in one play, then starts using it the next.

Not sure how clean it is, but it works. I'm just building a bit on what mikepurvis mentioned here.

---
# Build virtualenv
- hosts: all
vars:
  PROJECT_HOME: "/tmp/my_test_home"
  ansible_python_interpreter: "/usr/local/bin/python"
tasks:
  - name: "Create virtualenv"
    shell: virtualenv "{{ PROJECT_HOME }}/venv"
           creates="{{ PROJECT_HOME }}/venv/bin/activate"

  - name: "Copy virtualenv wrapper file"
    synchronize: src=pyvenv
                 dest="{{ PROJECT_HOME }}/venv/bin/pyvenv"

# Use virtualenv
- hosts: all
vars:
  PROJECT_HOME: "/tmp/my_test_home"
  ansible_python_interpreter: "/tmp/my_test_home/venv/bin/pyvenv"
tasks:
  - name: "Guard code, so we are more certain we are in a virtualenv"
    shell: echo $VIRTUAL_ENV
    register: command_result
    failed_when: command_result.stdout == ""

pyenv wrapper file:

#!/bin/bash
source "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/activate"
python $@


回答4:

Just run the virtualenvs pip in a shell:

shell: ~/buildbot-env/pip install ${item}

Works like a charm. I have no idea what the pip module does with virtualenvs, but it seems pretty useless.



回答5:

As I commented above, I create a script, say it is called buildbot.sh:

source ~/buildbot-env/bin/activate
buildbot create-master [and more stuff]

Then run it on the remote with a task like this:

- name: Create buildbot master
  script: buildbot.sh

To me this still seems unneccessary, but it maybe cleaner than running it in a shell command. Your playbook looks cleaner at the cost of not seeing immediately what the script does.

At least some modules do seem to use virtualenv, as both django_manage and rax_clb already have an inbuilt virtualenv parameter. It may not be such a big step for Ansible to include a command-in-virtenv sort of module.