Kenneth D.
Kenneth D.

Reputation: 845

How does one create a Python virtualenv in Jenkins?

I am using a Makefile to provide consistent single commands for setting up a virtualenv, running tests, etc. I have configured my Jenkins instance to pull from a mercurial repo and then run "make virtualenv", which does this:

virtualenv --python=/usr/bin/python2.7 --no-site-packages . && . ./bin/activate && pip install -r requirements.txt

But for some reason it insists on using the system-installed pip and trying to install my package dependencies in the system site-packages rather than the virtualenv:

error: could not create '/usr/local/lib/python2.7/dist-packages/flask': Permission denied

If I add some debugging commands and explicitly point to the pip in my virtualenv, things get even more confusing:

virtualenv --python=/usr/bin/python2.7 --no-site-packages . && . ./bin/activate && ls -l bin && which pip && pwd && ./bin/pip install -r requirements.txt

Which generates the following output:

New python executable in ./bin/python2.7
Not overwriting existing python script ./bin/python (you must use ./bin/python2.7)
Installing setuptools, pip...done.
Running virtualenv with interpreter /usr/bin/python2.7

It appears Jenkins doesn't rebuild the environment from scratch for each build, which strikes me as an odd choice, but shouldn't effect my immediate issue

The output from the "ls -l bin" shows pip to be installed in the virtualenv and executable:

-rw-r--r-- 1 jenkins jenkins    2248 Apr  9 21:14 activate
-rw-r--r-- 1 jenkins jenkins    1304 Apr  9 21:14 activate.csh
-rw-r--r-- 1 jenkins jenkins    2517 Apr  9 21:14 activate.fish
-rw-r--r-- 1 jenkins jenkins    1129 Apr  9 21:14 activate_this.py
-rwxr-xr-x 1 jenkins jenkins     278 Apr  9 21:14 easy_install
-rwxr-xr-x 1 jenkins jenkins     278 Apr  9 21:14 easy_install-2.7
-rwxr-xr-x 1 jenkins jenkins     250 Apr  9 21:14 pip
-rwxr-xr-x 1 jenkins jenkins     250 Apr  9 21:14 pip2
-rwxr-xr-x 1 jenkins jenkins     250 Apr  9 21:14 pip2.7
lrwxrwxrwx 1 jenkins jenkins       9 Apr 10 19:31 python -> python2.7
lrwxrwxrwx 1 jenkins jenkins       9 Apr 10 19:31 python2 -> python2.7
-rwxr-xr-x 1 jenkins jenkins 3349512 Apr 10 19:31 python2.7

The output of "which pip" seems to want to use the correct one:

/var/lib/jenkins/jobs/Run Tests/workspace/bin/pip

My current working directory is what I expect it to be:

/var/lib/jenkins/jobs/Run Tests/workspace

But... wtf?

/bin/sh: 1: ./bin/pip: Permission denied
make: *** [virtualenv] Error 126
Build step 'Execute shell' marked build as failure
Finished: FAILURE

Upvotes: 33

Views: 67397

Answers (7)

sorin
sorin

Reputation: 170430

I have been using python virtualenvs with Jenkins every day in the last two years, at multiple companies and for small side projects and cannot say I found "THE" answer. Still, I hope that sharing my experience will help others save time. Hopefully I will get further feedback in order to make the decision easier.

  • Avoid ShiningPanda - it's not well maintained, incompatible with Jenkins2 pipelines and prevents execution of jobs in parallel. Also it has the bad habit of leaving orphan environments on disk.
  • DIY via bash and virtualenv is my current favourite. Create it inside $WORKSPACE and, if not always cleaning, run relocatable before activating them. This is because jenkins workspace folder disk location can change between executions of job N and N+1.

If you use multiple builders that do need the same virtualenv, the easiest way is to dump your environment to a file and source it at the beginning of the new builder.

To ease the maintenance I am planning to investigate these:

  • direnvm
  • virtualenv-wrapper (mkvirtualenv)
  • pyenv

If you hit the shebang command line limits the best thing to do is to change your jenkins home directory to just /j.

Upvotes: 15

Wolfgang Fahl
Wolfgang Fahl

Reputation: 15769

@hardbyte's answer

The default shell that Jenkins uses is /bin/sh - this is configurable in Manage Jenkins -> Configure System -> Shell -> Shell executable. Setting this to /bin/bash will make source work.

plus:

got me working

sudo apt install python3.10-venv

and then in jenkins in the execute shell step:

python3.10 -m venv .venv
source .venv/bin/activate
...

Upvotes: 0

VAD
VAD

Reputation: 23

There are some issues with venv-python plugin with different OS environments.

Here is how I call python method manually. Not best practice but it work.

// Put this stage on top of pipeline
stage('Prepare venv') {
    steps {
        script {
            if (isUnix()) {
                env.ISUNIX = "TRUE" // cache isUnix() function to prevent blueocean show too many duplicate step (Checks if running on a Unix-like node) in python function below
                sh 'python3 -m venv pyenv'
                PYTHON_PATH =  sh(script: 'echo ${WORKSPACE}/pyenv/bin/', returnStdout: true).trim()                        
            }
            else {
                env.ISUNIX = "FALSE"
                powershell(script:"py -3 -m venv pyenv") // windows not allow call python3.exe with venv. https://github.com/msys2/MINGW-packages/issues/5001
                PYTHON_PATH =  sh(script: 'echo ${WORKSPACE}/pyenv/Scripts/', returnStdout: true).trim()
            }

            try  {
                // Sometime agent with older pip version can cause error due to non compatible plugin.
                Python("-m pip install --upgrade pip")
            } 
            catch (ignore) { } // update pip always return false when already lastest version
            // After this you can call Python() anywhere from pipeline
            Python("-m pip install -r requirements.txt")
        }                
    }
}

// Several plugins like WithPyenv is not working perfectly accross platform when using Virtual Env.
// Put this method outside pipeline
def Python(String command) {
    if (env.ISUNIX == "TRUE") {
        sh script:"source ${WORKSPACE}/pyenv/bin/activate && python ${command}", label: "python ${command}"
    }
    else {
        powershell script:"${WORKSPACE}\\pyenv\\Scripts\\Activate.ps1 ; python ${command}", label: "python ${command}"
    }
}

Upvotes: 1

Attila Viniczai
Attila Viniczai

Reputation: 693

After acticating the virtualenv, try to run pip as a module:

python -m pip install ...

python -m pip vs pip

  • python -m pip: executes python interpreter binary that reads module pip.py from site packages directory
  • pip: executes pip binary / script picked up from $PATH

I have found that using python -m pip solved most of the pip permission problems encountered.

Upvotes: 0

Tautvydas
Tautvydas

Reputation: 2067

I'd recommend avoiding ShiningPanda.

I set up my virtual environments with Anaconda/Miniconda. When installing conda make sure you're running as jenkins user.

your_user@$ sudo -u jenkins sh
jenkins@$ wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
jenkins@$ bash Miniconda3-latest-Linux-x86_64.sh

Since Jenkins runs sh rather than bash, I added conda path to /etc/profile:

export PATH="/var/lib/jenkins/miniconda3/bin:$PATH"

Then in Jenkinsfile you can create and delete conda environments. Here's an example that creates a new environment for each build:

pipeline {
    agent any
    stages {
        stage('Unit tests') {
            steps {
            sh '''
                conda create --yes -n ${BUILD_TAG} python
                source activate ${BUILD_TAG}
                // example of unit test with nose2
                pip install nose2
                nose2
            '''
            }
        }
    }
    post {
        always {
            sh 'conda remove --yes -n ${BUILD_TAG} --all'
        }
    }
}

Upvotes: 6

Hardbyte
Hardbyte

Reputation: 1639

Jenkins pipelines can be made to run with virtual environments but there are multiple things to consider.

  • The default shell that Jenkins uses is /bin/sh - this is configurable in Manage Jenkins -> Configure System -> Shell -> Shell executable. Setting this to /bin/bash will make source work.
  • An activated venv simply changes environment variables, and environment variables do not persist between stages in jenkins. See withEnv
  • If you are using version controlled multibranch pipelines jenkins creates a workspace with the branch name and a commit hash in the path - which can be quite long. venv scripts (e.g. pip) all start with a hashbang line which includes the full path to the python interpreter in the venv (the python interpreter itself is a symlink). E.g.,

    ~/workspace/ink_feature-use-jenkinsfile-VGRPYD53GGGDDSBIJDLSUDYPJ34QR63ITGMC5VJNB56W6ID244AA/env/bin$ cat pip
    #!/var/jenkins_home/workspace/ink_feature-use-jenkinsfile-VGRPYD53GGGDDSBIJDLSUDYPJ34QR63ITGMC5VJNB56W6ID244AA/env/bin/python3.5
    

    Bash only reads the first N characters of any executable file - which I found did not quite include the full venv path:

    bash: ./pip: /var/jenkins_home/workspace/ink_feature-use-jenkinsfile-VGRPYD53GGGDDSBIJDLSU: bad interpreter: No such file or directory
    

    This particular problem can be avoided by executing the script with Python instead. E.g. python3.5 ./pip

Upvotes: 15

Talkerbox
Talkerbox

Reputation: 385

I've got same problem. As I can see - you project named 'Run Tests'. So, this name contain space. That was the problem for me. I just renamed project, like RunTests - and venv working now! Attention - jenkins ask you about confirmation renaming project.

Upvotes: 2

Related Questions