Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Caching Maven parent in offline build

Tags:

TL/DR: How can I cache the pom.xml file's <parent> so my build can be run in offline mode?

I'm using Docker to build a maven project. My goal is to add two steps to the build: one to download all of the dependencies, and another to build the project. Here's what my Dockerfile looks like so far:

FROM maven:3.3-jdk-8

# Download the project dependencies (so they can be cached by Docker)
ADD pom.xml /runtime/
WORKDIR /runtime
RUN mvn dependency:go-offline
RUN mvn dependency:resolve-plugins

# Mount the local repository
ADD . /runtime

# Build the service
RUN mvn clean package -o -DskipTests

This seems to work fine for the plugins. I checked the /root/.m2/repository and everything seems to be in order.

Edit: When double checking for the /root/.m2/repository directory, it's no longer there. For some reason, Maven isn't saving any of the dependencies to this location.

Edit 2: After building the Docker image, there's no /root/.m2/repository directory. However, if I run mvn dependency:go-offline from within a shell inside the Docker container, the directory is created without a problem.

When I attempt build my application, I get the following error:

[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[FATAL] Non-resolvable parent POM for com.example:service:1.2: Cannot access central (http://jcenter.bintray.com) in offline mode and the artifact org.springframework.boot:spring-boot-starter-parent:pom:1.4.0.M3 has not been downloaded from it before. and 'parent.relativePath' points at wrong local POM @ line 14, column 13
 @
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]
[ERROR]   The project com.sample:service:1.2 (/runtime/pom.xml) has 1 error
[ERROR]     Non-resolvable parent POM for com.oe:graph-service:1.2: Cannot access central (http://jcenter.bintray.com) in offline mode and the artifact org.springframework.boot:spring-boot-starter-parent:pom:1.4.0.M3 has not been downloaded from it before. and 'parent.relativePath' points at wrong local POM @ line 14, column 13 -> [Help 2]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
[ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/UnresolvableModelException

The problem seems to be that mvn dependency:go-offline isn't resolving the parent. When I run the build in offline mode, it breaks.

Here are the relevant portions of my pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    ...

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.M3</version>
    </parent>

    ...

    <repositories>
        <repository>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <id>central</id>
            <name>bintray</name>
            <url>http://jcenter.bintray.com</url>
        </repository>

        <repository>
            <id>repository.springsource.snapshot</id>
            <name>SpringSource Snapshot Repository</name>
            <url>http://repo.springsource.org/snapshot</url>
        </repository>

        <repository>
            <id>spring-milestones</id>
            <url>http://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <dependencies>
       ...

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.scala</groupId>
            <artifactId>spring-scala_2.11</artifactId>
            <version>1.0.0.BUILD-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- disabling Spring cloud AWS until proper testing harnesses can be set up -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>LATEST</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-aws-autoconfigure</artifactId>
            <version>1.1.0.RELEASE</version>
        </dependency>

        ...
    </dependencies>

    ...
</project>
like image 468
LandonSchropp Avatar asked Nov 30 '16 21:11

LandonSchropp


People also ask

What is Maven cache?

the local repository is a directory on the computer where Maven runs. It caches remote downloads and contains temporary build artifacts that you have not yet released.

How Maven download dependencies?

When you run a Maven build, then Maven automatically downloads all the dependency jars into the local repository. It helps to avoid references to dependencies stored on remote machine every time a project is build. Maven local repository by default get created by Maven in %USER_HOME% directory.


2 Answers

If you customize maven's settings.xml file you can save your repository files on image.

Create a custom version of settings.xml, with the localRepository setting modified, like this:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <!-- localRepository
   | The path to the local repository maven will use to store artifacts.
   |
   | Default: ${user.home}/.m2/repository -->
  <localRepository>/usr/share/maven/repo</localRepository>
...

Then override the default configuration when building your image:

FROM maven:3-jdk-8

COPY settings.xml /usr/share/maven/conf/settings.xml

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

ADD . /usr/src/app

RUN mvn dependency:go-offline

RUN mvn clean package

Now your repository is stored in /usr/share/maven/repo.

Using onbuild

You can also create a base image using ONBUILD, this will allow to have custom configured images for every maven project.

Like this:

FROM maven:3-jdk-8

COPY settings.xml /usr/share/maven/conf/settings.xml

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

ONBUILD ADD . /usr/src/app

ONBUILD RUN mvn dependency:go-offline

ONBUILD RUN mvn clean package

Then build the image:

docker build -t mvn_bldr .

This will create a template for other maven images. Then you can create your custom downstream image with:

FROM mvn_bldr

If you want to customize your image further you can add more instructions, every instruction of the template will be triggered after the FROM mvn_bldr command, as in the docs:

The trigger will be executed in the context of the downstream build, as if it had been inserted immediately after the FROM instruction in the downstream Dockerfile.

like image 104
Marcs Avatar answered Sep 24 '22 16:09

Marcs


It may seem like using RUN with Docker is like executing commands in a shell script, but it's not. Every instance of RUN gets applied to a new container that results from the changes created by the previous command. So each command is executing inside of a new container context.

Dockerifles can contain a VOLUME reference, which mounts an external directory inside the Docker container. If there were any files inside the volume, those files are wiped out. If you don't explicitly specify a volume, Docker is happy to instead create an empty folder.

While my Dockerfile doesn't contain an explicit reference to a VOLUME, its parent does. So, even though my mvn dependency:go-offline command was running, those files were being wiped out in the next step by the VOLUME specified in the docker-maven Dockerfile.

In the end, I couldn't find a good way to make the maven Docker image work, so I switched to the openjdk image and installed maven via apt-get instead.

like image 33
LandonSchropp Avatar answered Sep 24 '22 16:09

LandonSchropp