From 6d78fc4f900415b4e33db1653a8be64ee62a6ee6 Mon Sep 17 00:00:00 2001
From: Ian Wienand <iwienand@redhat.com>
Date: Wed, 6 May 2020 10:19:23 +1000
Subject: [PATCH] ensure-tox: use venv to install

This currently installs with pip --user which cases problems if you
try to run this version of tox as another user.  This is done in
system-config, for example, where we run tox with "become: yes" to run
testinfra.

By installing tox into a venv, we can call it as another user and it
just works because it's all encapsulated in the venv.  We use the
virtualenv commands exported by ensure-pip to create this.

I think the original motivation for installing tox like this was to
ensure it is done without sudo permissions.  This also doesn't require
permissions, but ensures the resulting tox_executable is able to be
executed in more contexts.

Needed-By: https://review.opendev.org/712819
Change-Id: Iebee8cb72cce7944c537fdb91b6c98ed51878661
---
 roles/ensure-tox/README.rst         |  4 +--
 roles/ensure-tox/defaults/main.yaml |  1 +
 roles/ensure-tox/tasks/main.yaml    | 48 ++++++++++++-----------------
 test-playbooks/ensure-tox.yaml      |  2 +-
 4 files changed, 24 insertions(+), 31 deletions(-)

diff --git a/roles/ensure-tox/README.rst b/roles/ensure-tox/README.rst
index b81aef3d1..937c0f847 100644
--- a/roles/ensure-tox/README.rst
+++ b/roles/ensure-tox/README.rst
@@ -1,7 +1,7 @@
 Ensure tox is installed
 
-Look for ``tox``, and if not found, install it via ``pip`` in the user
-install directory (i.e., ``pip install --user``).
+Look for ``tox``, and if not found, install it via ``pip`` into a
+virtual environment for the current user.
 
 **Role Variables**
 
diff --git a/roles/ensure-tox/defaults/main.yaml b/roles/ensure-tox/defaults/main.yaml
index 2e2266d2b..7479455e6 100644
--- a/roles/ensure-tox/defaults/main.yaml
+++ b/roles/ensure-tox/defaults/main.yaml
@@ -1,2 +1,3 @@
 tox_executable: tox
+tox_venv_path: '{{ ansible_user_dir }}/.local/tox'
 tox_prefer_python2: false
diff --git a/roles/ensure-tox/tasks/main.yaml b/roles/ensure-tox/tasks/main.yaml
index 39b8402bb..8c8fa68e4 100644
--- a/roles/ensure-tox/tasks/main.yaml
+++ b/roles/ensure-tox/tasks/main.yaml
@@ -4,41 +4,33 @@
   vars:
     ensure_pip_from_packages_with_python2: '{{ tox_prefer_python2 }}'
 
-- name: Ensure tox is installed
+- name: Check if tox is installed
   shell: |
-    set -euo pipefail
-
-    {% if tox_prefer_python2 %}
-    if command -v pip; then
-      PIP=pip
-    elif command -v pip3; then
-      PIP=pip3
-    fi
-    {% else %}
-    if command -v pip3; then
-      PIP=pip3
-    elif command -v pip; then
-      PIP=pip
-    fi
-    {% endif %}
-
-    type {{ tox_executable }} || $PIP install --user tox
+    command -v {{ tox_executable }} || exit 1
   args:
     executable: /bin/bash
-  register: result
-  changed_when: "'Successfully installed' in result.stdout"
+  register: tox_preinstalled
+  failed_when: false
 
-- name: Set tox_executable fact to pip installed
+- name: Export preinstalled tox_exectuable
   set_fact:
-    tox_executable: "{{ ansible_user_dir }}/.local/bin/tox"
+    tox_executable: '{{ tox_executable }}'
     cacheable: true
-  when: result is changed
+  when: tox_preinstalled.rc == 0
 
-- name: Set tox_exectuable fact to found tox
-  set_fact:
-    tox_executable: "{{ tox_executable }}"
-    cacheable: true
-  when: result is not changed
+- name: Install tox to local env
+  when: tox_preinstalled.rc != 0
+  block:
+    - name: Install tox to local venv
+      pip:
+        name: tox
+        virtualenv_command: '{{ ensure_pip_virtualenv_command }}'
+        virtualenv: '{{ tox_venv_path }}'
+
+    - name: Export installed tox_executable path
+      set_fact:
+        tox_executable: '{{ tox_venv_path }}/bin/tox'
+        cacheable: true
 
 - name: Output tox version
   command: "{{ tox_executable }} --version"
diff --git a/test-playbooks/ensure-tox.yaml b/test-playbooks/ensure-tox.yaml
index 67a0001c1..854b37b39 100644
--- a/test-playbooks/ensure-tox.yaml
+++ b/test-playbooks/ensure-tox.yaml
@@ -27,7 +27,7 @@
     - name: Verify tox_executable is set
       assert:
         that:
-          - tox_executable == "{{ ansible_user_dir }}/.local/bin/tox"
+          - tox_executable == "{{ ansible_user_dir }}/.local/tox/bin/tox"
     - name: Verify tox is installed
       command: "{{ tox_executable }} --version"
       register: result