gradle-docker-plugin icon indicating copy to clipboard operation
gradle-docker-plugin copied to clipboard

Encounters error if imageId file exists, and the image it's referring is already built ("repoTags" is null)

Open nvilagos opened this issue 1 year ago • 1 comments

Expected Behavior

DockerBuildImage should be able to successfully finish the build Docker image task.

Current Behavior

DockerBuildImage fails the build Docker image task if the image is already built previously and the following file exists: build/.docker/docker_buildImage-imageId.txt.

The error message is the following:

Cannot invoke "java.util.List.containsAll(java.util.Collection)" because "repoTags" is null

gradle-docker-plugin-evidence-01

gradle-docker-plugin-evidence-02

gradle-docker-plugin-evidence-03

gradle-docker-plugin-evidence-04

gradle-docker-plugin-evidence-05

gradle-docker-plugin-evidence-06

Context

I want to build a Docker image as a Gradle task so I can use it in a later task.

Steps to Reproduce (for bugs)

  1. Run the Docker build for the first time.
  2. Keep intact the generated files and images.
  3. Run the Docker build again to make it fail.

Your Environment

Gradle Docker plugin artifact: com.bmuschko.docker-remote-api:9.4.0 Gradle version: 8.11.1 OS: Windows 11

Possible workaround

Before every Docker image build, delete either the previously built Docker image, or the following file: build/.docker/docker_buildImage-imageId.txt.

nvilagos avatar Nov 29 '24 13:11 nvilagos

Root Cause Analysis

I found the root cause of this issue, which is docker-remote-api packaging some of its dependencies but not renaming the packages of all of them. This leads to very difficult-to-debug behaviour:

  1. docker-remote-api is shading (i.e. including in the .jar and renaming the packages of) jackson-api and jackson-databind, but is including docker-java-api and docker-java-core in the .jar without renaming the packages. Because of this, docker-remote-api puts the classes of those two docker-java dependencies on the classpath (with their original package names!), but all jackson references—for example the @JsonProperty annotations—in those class files link to com.bmuschko.gradle.docker.shaded.com.fasterxml.jackson.….
  2. We added another dependency to our build (testcontainers, specifically) that depends on docker-java-api. This dependency brought the classes of docker-java-api to the classpath, replacing the classes packaged in docker-remote-api. However, the dependency did not replace docker-java-core.
  3. With this setup, docker-remote-api invoked its version of docker-java-core, which, through its com.bmuschko.gradle.docker.shaded.com.fasterxml.jackson.databind.ObjectMapper, searched for com.bmuschko.gradle.docker.shaded.com.fasterxml.jackson.annotation.JsonProperty annotations. However, the normal version of docker-java-api on our classpath had, of course, com.fasterxml.jackson.annotation.JsonProperty annotations. This caused deserialization to fail and some properties to be null. Amazingly, this was only an issue when docker-remote-api tried to check a pre-existing image. Hence, the workaround of removing the build/.docker directory fixed all issues we were facing.

To illustrate the problem, here is the decompiled com.github.dockerjava.api.command.InspectImageResponse, as it is included in the .jar of docker-remote-api. Notice how InspectImageResponse has the normal package of docker-java-api, but references the renamed versions of jackson-api: grafik

Improved Workaround

In our case, adding a dependency on docker-java-core to our buildSrc project fixes the issue. With that dependency on the classpath, all references to the shaded version of Jackson are replaced:

implementation(platform("com.github.docker-java:docker-java-bom:3.4.0"))
implementation("com.github.docker-java:docker-java-core")

However, this effectively disables the shading done in gradle-docker-plugin, which, I assume, exists for a reason.

Proposed fix

gradle-docker-plugin should either shade all its dependencies correctly (i.e. package and rename them), or not shade any of them. Including a dependency with its original package name but different compiled code will always lead to weird dependency errors when another dependency brings in docker-java. At the very least, other dependencies can effectively disable the shading, which defeats its purpose.

jGleitz avatar Dec 01 '24 21:12 jGleitz