Search code examples
pythondockerjenkinsjenkins-pipeline

How to dynamically add Jenkins user and permissions inside Docker container


When I am building a declarative Jenkins pipeline for my python project, I get the following error message when using Pip.

WARNING: The directory '/.cache/pip' or its parent directory is not owned or is not writable by the current user.

My Jenkinsfile:

#!groovy

pipeline {
    agent {
        docker {
            image 'python:3.7.5'
        }
    }

    stages {
        stage('Build Dependencies') {
            steps {
                sh "pip install -r requirements.txt"
            }
        }
}
    post {
        always {
            sh "Job finished"
        }
    }
}

How do I solve this?


Solution

  • You have to create a new defined user in the Docker container instead of using the root user. You have to create the new user with the outerscope GID and UID references of the jenkins user on your Jenkins server. To read more about GID and UID inside Docker I recommend you to read this interesting article.

    Let's use a Dockerfile agent instead of a remote image, so we can dynamically assign the needed variables as build arguments.

    First we are going to fetch the outerscope GID, UID and username of the jenkins account:

    Jenkinsfile:

    #!groovy
    
    pipeline {
    
        environment {
                JENKINS_USER_NAME = "${sh(script:'id -un', returnStdout: true).trim()}"
                JENKINS_USER_ID = "${sh(script:'id -u', returnStdout: true).trim()}"
                JENKINS_GROUP_ID = "${sh(script:'id -g', returnStdout: true).trim()}"
        }
    }
    

    We now have assigned the GID, UID and username to various environment variables for the scope of the pipeline. We can now assign them as build arguments for our Dockerfile.

    Jenkinsfile:

    #!groovy
    
    pipeline {
    
        environment {
                JENKINS_USER_NAME = "${sh(script:'id -un', returnStdout: true).trim()}"
                JENKINS_USER_ID = "${sh(script:'id -u', returnStdout: true).trim()}"
                JENKINS_GROUP_ID = "${sh(script:'id -g', returnStdout: true).trim()}"
        }
        agent {
            dockerfile {
                filename 'Dockerfile.build'
                additionalBuildArgs '''\
                --build-arg GID=$JENKINS_GROUP_ID \
                --build-arg UID=$JENKINS_USER_ID \
                --build-arg UNAME=$JENKINS_USER_NAME \
                '''
            }
        }
    
        stages {
            stage('Build Dependencies') {
                steps {
                    sh "pip install -r requirements.txt"
                }
            }
    }
        post {
            always {
                sh "Job finished"
            }
        }
    }
    

    And finally in the Dockerfile we have to create the defined user:

    Dockerfile.build

    FROM python:3.8.3
    
    ARG GID
    ARG UID
    ARG UNAME
    
    ENV GROUP_ID=$GID
    ENV USER_ID=$UID
    ENV USERNAME=$UNAME
    
    RUN mkdir /home/$USERNAME
    
    RUN groupadd -g $GROUP_ID $USERNAME
    RUN useradd -r -u $USER_ID -g $USERNAME -d /home/$USERNAME $USERNAME
    RUN chown $USERNAME:$USERNAME /home/$USERNAME
    
    USER $USERNAME
    WORKDIR /home/$USERNAME
    
    RUN python -m venv testpipe_venv
    
    CMD ["/bin/bash -c 'source testpipe_venv/bin/activate'"]
    

    Note that we have to create a virtual python environment as we are not the root user anymore. In the final command we activate the virtual environmnet for usage in the pipeline.