14

I have the following Jenkinsfile:

def notifySlack = { String color, String message ->
    slackSend(color: color, message: "${message}: Job ${env.JOB_NAME} [${env.BUILD_NUMBER}] (${env.BUILD_URL})")
}

node {
    try {
        notifySlack('#FFFF00', 'STARTED')
        stage('Checkout project') {
            checkout scm
        }
        scalaImage = docker.image('<myNexus>/centos-sbt:2.11.8')
        stage('Test project') {
            docker.withRegistry('<myNexus>', 'jenkins-nexus') {
                scalaImage.inside('-v /var/lib/jenkins/.ivy2:/root/.ivy2') { c ->
                    sh 'sbt clean test'
                }
            }
        }
        if (env.BRANCH_NAME == 'master') {
            stage('Release new version') {
                docker.withRegistry('<myNexus>', 'jenkins-nexus') {
                    scalaImage.inside('-v /var/lib/jenkins/.ivy2:/root/.ivy2') { c ->
                        sh 'sbt release'
                    }
                }
            }
        }
        notifySlack('#00FF00', 'SUCCESSFUL')
    } catch (e) {
        currentBuild.result = "FAILED"
        notifySlack('#FF0000', 'FAILED')
        throw e
    }

}

Unfortunately when I reach the sbt clean test line I end up with the following error:

java.lang.IllegalArgumentException: URI has a query component
    at java.io.File.<init>(File.java:427)
    at sbt.IO$.uriToFile(IO.scala:160)
    at sbt.IO$.toFile(IO.scala:135)
    at sbt.Classpaths$.sbt$Classpaths$$bootRepository(Defaults.scala:1942)
    at sbt.Classpaths$$anonfun$appRepositories$1.apply(Defaults.scala:1912)
    at sbt.Classpaths$$anonfun$appRepositories$1.apply(Defaults.scala:1912)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
    at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
    at scala.collection.mutable.WrappedArray.foreach(WrappedArray.scala:34)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
    at scala.collection.AbstractTraversable.map(Traversable.scala:105)
    at sbt.Classpaths$.appRepositories(Defaults.scala:1912)
    at sbt.Classpaths$$anonfun$58.apply(Defaults.scala:1193)
    at sbt.Classpaths$$anonfun$58.apply(Defaults.scala:1190)
    at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
    at sbt.EvaluateSettings$MixedNode.evaluate0(INode.scala:175)
    at sbt.EvaluateSettings$INode.evaluate(INode.scala:135)
    at sbt.EvaluateSettings$$anonfun$sbt$EvaluateSettings$$submitEvaluate$1.apply$mcV$sp(INode.scala:69)
    at sbt.EvaluateSettings.sbt$EvaluateSettings$$run0(INode.scala:78)
    at sbt.EvaluateSettings$$anon$3.run(INode.scala:74)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

If I run the simple docker run ... followed by docker exec I get what I want but I would like to work with the defined Jenkins functionality.

So this seems to be an SBT issue. I use version 0.13.16 inside the docker image. From what I understand the classpath contains a query parameter that SBT:

  • doesn't like
  • doesn't know how to handle
  • is illegal

I put no such query parameters myself so I thought that this .inside method does that. I checked the env in the container and found a single entry RUN_CHANGES_DISPLAY_URL=<my_ip>/job/scheduler/job/fix-jenkins-pipeline/23/display/redirect?page=changes. I tried to unset it but didn't manage to.

I'm out of ideas and am not really sure I'm in the right direction. Any help would be appreciated.

zaxme
  • 1,065
  • 11
  • 29

4 Answers4

15

So after long and tedious searches what finally worked for me is setting explicitly the .sbt and .ivy2 folder like this inside the docker container:

sbt -Dsbt.global.base=.sbt -Dsbt.boot.directory=.sbt -Dsbt.ivy.home=.ivy2 clean test

That somehow prevents sbt from generating the ? folder and directly puts the aforementioned folders in the root of the directory checkout.

zaxme
  • 1,065
  • 11
  • 29
1

I spent a lot of time tracing this down through the code.

It looks like the easiest solution is to just pass -Duser.home=<path> to sbt, or to set it in the SBT_OPTS environment variable; then all the rest of the directories will be built as if the <path> is the user's home directory.

Loki
  • 6,205
  • 4
  • 24
  • 36
0

I fixed this by setting ivys cache directory -> How to override the location of Ivy's Cache?

The problem was that it wasn't set and by default it creates a ? folder which in return can't be handled by sbt itself.

I created a custom Dockerfile to have more control over sbt. Here are the steps I executed to solve the issue:

I created a file called ivysettings.xml with the following contents:

<ivysettings>
    <properties environment="env" />
    <caches defaultCacheDir="/home/jenkins/.ivy2/cache" />
</ivysettings>

and a Dockerfile:

FROM openjdk:8

RUN wget -O- "http://downloads.lightbend.com/scala/2.11.11/scala-2.11.11.tgz" \
    | tar xzf - -C /usr/local --strip-components=1

RUN curl -Ls https://git.io/sbt > /usr/bin/sbt && chmod 0755 /usr/bin/sbt

RUN adduser -u 1000 --disabled-password --gecos "" jenkins

ADD ./files/ivysettings.xml /home/jenkins/.ivy2/ivysettings.xml
RUN chown -R jenkins:jenkins /home/jenkins

USER jenkins

CMD ["sbt"]

I then pushed the image to our private docker repository and our pipeline finally works!

Julian Pieles
  • 3,880
  • 2
  • 23
  • 33
  • I am not succeeding with this. Can you please share with me examples of your `Jenkinsfile`, `build.sbt` and `ivysettings.xml`. I would really appreciate it. – zaxme Nov 17 '17 at 09:11
  • I see that the there is a `?` folder in the git clone done from Jenkins after execution of the `checkout scm` command in the `Jenkinsfile`. It contains `.ivy2` and `.sbt`. What is this folder supposed to be? – zaxme Nov 17 '17 at 10:11
  • Normaly the `?` folder would be the folder sbt uses as cache for ivy2 and sbt itself. Using a default sbt installation this folder would be the home directory (`~` or `$HOME`). Since most docker images uses a custom installation the cache path is not set and needs to be configured before. I will update the question as soon as I get back from work with more information, – Julian Pieles Nov 17 '17 at 10:20
  • @zaxme I updated my answer. I hope it helps. I am pretty sure there has to be a better/more convenient solution but right now this works. – Julian Pieles Nov 17 '17 at 18:26
0

Problem is that jenkins is running a specific user inside the container. But overriding it does the trick.

withDockerContainer(args: "-u root -v ${HOME}/.sbt:/root/.sbt -v ${HOME}/.ivy2:/root/.ivy2 -e HOME=/root",
            image: 'xyz/sbt:v') {
Spangaer
  • 391
  • 3
  • 3