skip to Main Content

We have multiple Jenkins ssh agents, multiple build containers with different jdk versions and containers use java to publish stuff (sonar results etc.) to other servers.

The containers need to trust the same servers as the host machine (Jenkins ssh agent). So java cacerts inside the container’s jdk needs to match the host machine cacerts. For this I add args:

agent {
  docker {
    registryUrl "https://private.registry"
    registryCredentialsId "private-registry-creds"
    image "build_container_jdk17"
    args "-v /host/cacerts:/usr/share/jdk-17.0.6+10/lib/security/cacerts:ro"
  }
}

when starting the container from a Jenkinsfile.

Problem is the jdk path changes between containers. jdk path inside the container is also stored in an environment variable $JAVA_HOME.

What is the simplest way to get the JAVA_HOME environment variable from inside the container to use on the volume command line argument?

agent {
  docker {
    registryUrl "https://private.registry"
    registryCredentialsId "private-registry-creds"
    image "build_container_jdk17"
    args "-v /host/cacerts:${CONTAINER_JAVA_HOME}/lib/security/cacerts:ro"
  }
}

I tried using:

args '-v /host/cacerts:${JAVA_HOME}/lib/security/cacerts:ro'

Hopeful that the env variable on the right side of the colon would be evaluated from inside the container. This resulted in "invalid volume specification".

I think there’s a way to start the container in one stage, set the variable and restart in the next stage using the variable. I figured I’d ask before trying this since it seems overly complicated and there might be some obvious solution I’m missing.

2

Answers


  1. Chosen as BEST ANSWER

    In his answer mbwmd gave two options to fetch the environment variable from inside a docker container. These are both valid and it depends on your use case which one you would choose.

    I wanted to expand on this answer and give the details from Jenkins pipeline perspective as that was part of my question.

    When you use a pipeline where the pipeline agent is also the host for the containers. The "docker inspect" option can be preferable:

    pipeline {
      agent { label 'jenkins_ssh_agent' }
      environment {
        CONTAINER_JAVA_HOME = get_container_java_home()
        DOCKER_ARGS = "-v /host/cacerts:${CONTAINER_JAVA_HOME}/lib/security/cacerts:ro"
      }
      stages {
        stage ('Build') {
          agent {
            docker {
              label 'jenkins_ssh_agent'
              registryUrl 'https://private.registry'
              registryCredentialsId 'private-registry-creds'
              image 'build_container_jdk17:1'
              args "${DOCKER_ARGS}"
              reuseNode true
            }
          }
          steps {
            script {
              // java should now trusts trusted.private.server like the host does.
              sh(script: 'java -jar connect.jar https://trusted.private.server/config.xml')
            }
          }
        }
      }
    }
    get_container_java_home() {
      return sh(returnStdout: true, script: 'docker inspect --format ' {{ range .Config.Env }}{{println .}}{{end}}' private.registry/build_container_jdk17:1 | grep JAVA_HOME |cut -d '=' -f 2').trim();
    }
    

    The docker inspect command was derived from the answer by Vonc here. You could also use jq like mbwmd suggested in his answer if you prefer that. I did not have jq available and wanted to avoid the dependency.

    Advantage of this option is you do not need to start the container to get the env variable.

    Disadvantage might be that you have to run a docker command from your pipeline. Depending on the pipeline setup this might cause issues in some situations.

    Option #2 avoids using docker commands by using a stage to save the environment variable:

    pipeline {
      agent none
      stages {
        stage ('Checkout') {
          agent {
            docker {
              registryUrl 'https://private.registry'
              registryCredentialsId 'private-registry-creds'
              image 'build_container_jdk17:1'
            }
          }
          steps {
            script {
              // this command does not need java to trust any servers.
              checkout(/*configure checkout*/)
              env.CONTAINER_JAVA_HOME=sh(script: 'echo $JAVA_HOME', returnStdout: true).trim()
            }
          }
        }
        stage('Build') {
          agent {
            docker {
              registryUrl 'https://private.registry'
              registryCredentialsId 'private-registry-creds'
              image 'build_container_jdk17:1'
              args "-v /host/cacerts:${CONTAINER_JAVA_HOME}/lib/security/cacerts:ro"
            }
          }
          steps {
            script {
              // java should now trusts trusted.private.server like the host does.
              sh(script: 'java -jar connect.jar https://trusted.private.server/config.xml')
            }
          }
        }
      }
    }
    

    This is a good option if you are running multiple stages anyway and do not need the args to be set the first time you run up the container.


  2. you have two options:

    1. inspect the image beforehand. The environment variable will most likely be set in the image already:
      example: docker inspect jenkins/jenkins:lts | jq -r '.[].Config.Env[] | select(contains("JAVA_HOME")) | split("=")[1]'
    2. start the image and echo the environment variable
      example: docker run --rm --entrypoint='printenv' jenkins/jenkins:lts JAVA_HOME

    Both options will output /opt/java/openjdk, which in turn could be passed into the local evaluation of your volume definition

    # opt 1 
    docker run --rm 
      -v "/mnt/mypath/myfile:`docker inspect jenkins/jenkins:lts | jq -r '.[].Config.Env[] | select(contains("JAVA_HOME")) | split("=")[1]'`/mypath/myfile" 
      jenkins/jenkins:lts
    # or opt 2
    docker run --rm 
      -v "/mnt/mypath/myfile:`docker run --rm --entrypoint='printenv' jenkins/jenkins:lts JAVA_HOME`/mypath/myfile" 
      jenkins/jenkins:lts
    

    Option 1 should be faster but requires jq to parse the json out put of inspect.
    Respect quotes and backticks.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search