Jib: Option to containerize JAR so that java applications can read MANIFEST.MF

Created on 30 Aug 2018  ยท  22Comments  ยท  Source: GoogleContainerTools/jib

Description of the issue:

Feature-Request: I have a spring boot application that exposes some metadata about the currently running application.
i collect these metadata using the following snippet:

````java
@Configuration
public class MetricsConfig {

@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
    final Package pack = getClass().getPackage();
    return registry -> registry.config()
            .commonTags(
            // @formatter:off

"implementation-title", Objects.toString(pack.getImplementationTitle(), "DEVault"),
"implementation-version", Objects.toString(pack.getImplementationVersion(), "DEV")
// @formatter:on
);
}

}
````

If i start the application using the fat jar or as a library of another project this code works fine, however
if i launch it using the generated docker image it only uses the fallback values. I assume this is caused by the missing META-INF/MANIFEST.mf file. Currently there is no way to specify these attributes and the plugin does not pull them by itself. (Similar to #159 )

These metadata are also used by Log4j2 (and other Logging frameworks) to include a version number with the stacktrace entries.

Expected behavior:

I would expect that the metadata would be present also in the docker image.
/
It should be possible to specify these metadata in the plugin's configuration.

Steps to reproduce:

  • Write a main class that prints: Main.class.getPackage().getImplementationVersion()
  • Add build metadata to the jar plugin config (see below)
  • Build the jar
  • Run the jar
  • It will show the version number of the project
  • Build the docker image
  • Run the docker image
  • It will show null

xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries> </manifest> <manifestEntries> <Build-Time>${maven.build.timestamp}</Build-Time> </manifestEntries> </archive> </configuration> </plugin>

Environment:

  • Maven 3.5
  • Java 8
  • Windows 10

(but I assume this is a general issue)

Possible Workaround

Move docker packaging to its own project that uses the original project as (jar) dependency.

discuss

Most helpful comment

I'd vote for opt-out from JAR packaging via configuration setting rather than for opt-in: jar packaging and manifest are so common and standard way to build the apps, that it naturally leads to unpleasant surprise for many developers that passing the build details like implementation version via manifest does not work.

The benefits related to incremental .class overlays (saving probably less than 100Kb) and reduced build time (by few seconds) for many would be insignificant.

All 22 comments

This seems tricky, because basically the Jib image doesn't respect the JAR spec. It doesn't package a JAR but rather directly executes a Main class with classpath set to point to regular .class files and dependency JARs. So the concept of the JAR Manifest doesn't really apply here.

I was testing Main.class.getPackage().getImplementationVersion(), and it returns the package version when executed inside a JAR. However, unpacking the JAR and executing the method outside the JAR returns null, even if META-INF/MANIFEST.MF is on the classpath. See below.

$ java -jar target/_MavenJavaTest-0.0.1-SNAPSHOT.jar
0.0.1-SNAPSHOT  <-- prints a value

$ unzip target/_MavenJavaTest-0.0.1-SNAPSHOT.jar -d target/tmp
$ tree target/tmp/
target/tmp/
โ”œโ”€โ”€ _MavenJavaTest
โ”‚ย ย  โ””โ”€โ”€ MavenJavaTestMain.class
โ””โ”€โ”€ META-INF
    โ”œโ”€โ”€ MANIFEST.MF
    โ””โ”€โ”€ maven
        โ””โ”€โ”€ _MavenJavaTest
            โ””โ”€โ”€ _MavenJavaTest
                โ”œโ”€โ”€ pom.properties
                โ””โ”€โ”€ pom.xml

5 directories, 5 files
$ java -cp target/tmp/ _MavenJavaTest.MavenJavaTestMain
null  <-- now null

Then could we add a mode that packages the classes inside a jar just like any other dependency? Or just use the jar that was actually build in the jar goal?

Then could we add a mode that packages the classes inside a jar just like any other dependency?

I'm thinking this might be possible... like packaging the main .class file into a JAR ourselves, but not sure what it would look like or what it may imply.

Or just use the jar that was actually build in the jar goal?

For now, we deliberately don't use a final JAR built by Maven or Gradle. (I won't list all the reasons and rationales for the decision; I'll just say one reason is an optimization of dividing things into multiple image layers instead of placing a huge single fat JAR.)

Then could we add a mode that packages the classes inside a jar just like any other dependency?

I'm thinking this might be possible... like packaging the main .class file into a JAR ourselves, but not sure what it would look like or what it may imply.

I' not sure that it is sufficient to pacakge only the main class, IMO you have to package all classes.

Or just use the jar that was actually build in the jar goal?

For now, we deliberately don't use a final JAR built by Maven or Gradle. (I won't list all the reasons and rationales for the decision; I'll just say one reason is an optimization of dividing things into multiple image layers instead of placing a huge single fat JAR.)

Not the/a fat jar. Just the normal jar that would be build by the plain old maven-jar-plugin. That way you could still use the layers from the dependencies/resources.

As a workaround you can run jib from an empty project that pulls in your other jar project.

That's the workaround I currently use (see the issue description).

The only downside to that is, that I have to create another project. Which I then have to convert to a multi module project to combine the builds and make consistent version numbers. Which is a PITA with maven.

I think most Java code would assume that they were packaged in a jar file. Especially when your build packaging is a jar packaging.

It doesn't seem unreasonable to support something like a <package>classes</package> or <package>jar</package> option.

@briandealwis +1. Am I right that we can do this and still layer more or less the same way as we do now?

I guess we would lose the ability to eventually do incremental .class overlays for users using jar packaging mode.

It's an XOR situation. For classes that assume they're in a jar, we can't do incremental class overlays as the overlaid class won't be in the jar. So we either bundle in the jar, or we do the class overlay. If they want a jar, they need to mvn package jib:build instead of mvn compile jib:build. Both approaches still benefit from the snapshot vs release overlays though, which is the biggest part of the win.

If they want a jar, they need to mvn package jib:build instead of mvn compile jib:build

I'm thinking mvn jar:jar jib:build, because some projects are configured to build a fat JAR with mvn package (Spring Boot is such one) at the same location where a normal JAR would be.

I think if we were to support packaging the classes files as a JAR and adding that as a layer, we should have the user explicitly enable that functionality, since packaging the JAR does add a significant portion to the build time.

$ ./gradlew clean jib
BUILD SUCCESSFUL in 4s
$ ./gradlew clean jar jib
BUILD SUCCESSFUL in 7s
$ ./mvnw clean compile jib:build
[INFO] Total time: 5.240 s
$ ./mvnw clean compile jar:jar jib:build
[INFO] Total time: 7.204 s

2 seconds... that isn't really significant (for me). I loose way more than two seconds on bad days due to network speed. Or unit/integration tests. Which might even take minutes.
But I agree that it should be an extra option.

Spring boot has a config option to add a classifier to their jar, same as the maven shade plugin.
Maybe we can let the user specify which classifier they want to include.

I'd vote for opt-out from JAR packaging via configuration setting rather than for opt-in: jar packaging and manifest are so common and standard way to build the apps, that it naturally leads to unpleasant surprise for many developers that passing the build details like implementation version via manifest does not work.

The benefits related to incremental .class overlays (saving probably less than 100Kb) and reduced build time (by few seconds) for many would be insignificant.

Need to update FAQ when JAR containerization is implemented.

1746 and #1760 added <containerizingMode>packaged, which will containerize a "main" JAR artifact instead of putting individual .class and resource files.

v1.4.0 has been released with JAR support!

Hi,

We have exactly the same need. In order to achieve that, we are going to generate a manifest during the compile phase using the maven-bundle-plugin (see https://stackoverflow.com/questions/34674073/how-to-generate-manifest-mf-file-during-compile-phase/62123534#62123534). This way we keep the default settings for generating the image with JIB.

Regards

@adelinor Hi! The issue is fixed a long time ago. You can set <containerizingMode>packaged to put a packaged JAR into the image, and your application should be able to read values from META-INF/MANIFEST.MF.

Hi @chanseokoh ,

Yes I tried that option. Unfortunately the manifest entries Implementation-Version and Build-Time set with the maven-jar-plugin, are not the ones that the app reads when running in the container:

  • The version is the version of the JDK used to do the build
  • The build time entry is missing

So it seems that another MANIFEST.MF gets created in the process :(

Hi @adelinor,

Jib takes the very JAR that maven-jar-plugin generates as-is (when <containerizingMode>packaged is given); Jib neither tempers with the contents of the JAR nor ever attempts to look into the JAR. If you believe your MANIFEST.MF content is not what you expect, then someone else must be modifying it or maybe your app isn't working properly. Note, Jib takes the original thin JAR that maven-jar-plugin generates; in other words, Jib does not take other potential JAR files generated by other plugins (such as the Spring Boot Maven plugin).

If you still think Jib is getting in the way of maven-jar-plugin's assembling a correct JAR, please file a new issue and we'll look into it. It's best if you can provide us with a reproducible sample.

Hi @chanseokoh,

You are correct: the cause might be an obstruction of MANIFEST.MF files. I investigated and the issue was caused by the fact that the wrong MANIFEST.MF was read. The sample application used for the investigation runs successfully from an image created by Jib :) . See:

https://github.com/adelinor/spring-boot-jib-hello

Many thanks for this excellent product and excellent support.

Regards

Was this page helpful?
0 / 5 - 0 ratings