Skip to content

Commit f78c0c3

Browse files
eddumelendezkiview
andauthored
Add support to build images using Jib (#5623)
[Jib](https://github.com/GoogleContainerTools/jib) is a java library to build container images. `JibImage` is provided in order to be used along with `GenericContainer` and build images using Jib API. Co-authored-by: Kevin Wittek <[email protected]>
1 parent 74a523a commit f78c0c3

File tree

7 files changed

+288
-0
lines changed

7 files changed

+288
-0
lines changed

core/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ configurations.all {
6262
resolutionStrategy {
6363
// use lower Jackson version
6464
force 'com.fasterxml.jackson.core:jackson-databind:2.8.8'
65+
force 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.8'
6566
}
6667
}
6768

@@ -75,6 +76,8 @@ dependencies {
7576
exclude(group: 'org.jetbrains', module: 'annotations')
7677
}
7778

79+
provided 'com.google.cloud.tools:jib-core:0.22.0'
80+
7881
shaded 'org.awaitility:awaitility:4.2.0'
7982

8083
api platform('com.github.docker-java:docker-java-bom:3.3.0')
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package org.testcontainers.jib;
2+
3+
import com.github.dockerjava.api.command.InspectImageResponse;
4+
import com.github.dockerjava.api.command.LoadImageCallback;
5+
import com.google.cloud.tools.jib.api.DockerClient;
6+
import com.google.cloud.tools.jib.api.ImageDetails;
7+
import com.google.cloud.tools.jib.api.ImageReference;
8+
import com.google.cloud.tools.jib.http.NotifyingOutputStream;
9+
import com.google.cloud.tools.jib.image.ImageTarball;
10+
import com.google.common.io.ByteStreams;
11+
import lombok.Cleanup;
12+
import org.testcontainers.DockerClientFactory;
13+
import org.testcontainers.UnstableAPI;
14+
import org.testcontainers.images.RemoteDockerImage;
15+
import org.testcontainers.utility.DockerImageName;
16+
17+
import java.io.BufferedInputStream;
18+
import java.io.BufferedOutputStream;
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.OutputStream;
22+
import java.io.PipedInputStream;
23+
import java.io.PipedOutputStream;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.util.Map;
27+
import java.util.function.Consumer;
28+
29+
@UnstableAPI
30+
class JibDockerClient implements DockerClient {
31+
32+
private static JibDockerClient instance;
33+
34+
private final com.github.dockerjava.api.DockerClient dockerClient = DockerClientFactory.lazyClient();
35+
36+
public static JibDockerClient instance() {
37+
if (instance == null) {
38+
instance = new JibDockerClient();
39+
}
40+
41+
return instance;
42+
}
43+
44+
@Override
45+
public boolean supported(Map<String, String> map) {
46+
return false;
47+
}
48+
49+
@Override
50+
public String load(ImageTarball imageTarball, Consumer<Long> writtenByteCountListener) throws IOException {
51+
@Cleanup
52+
PipedInputStream in = new PipedInputStream();
53+
@Cleanup
54+
PipedOutputStream out = new PipedOutputStream(in);
55+
LoadImageCallback loadImage = this.dockerClient.loadImageAsyncCmd(in).exec(new LoadImageCallback());
56+
57+
try (NotifyingOutputStream stdin = new NotifyingOutputStream(out, writtenByteCountListener)) {
58+
imageTarball.writeTo(stdin);
59+
}
60+
61+
return loadImage.awaitMessage();
62+
}
63+
64+
@Override
65+
public void save(ImageReference imageReference, Path outputPath, Consumer<Long> writtenByteCountListener)
66+
throws IOException {
67+
try (
68+
InputStream inputStream = this.dockerClient.saveImageCmd(imageReference.toString()).exec();
69+
InputStream stdout = new BufferedInputStream(inputStream);
70+
OutputStream fileStream = new BufferedOutputStream(Files.newOutputStream(outputPath));
71+
NotifyingOutputStream notifyingFileStream = new NotifyingOutputStream(fileStream, writtenByteCountListener)
72+
) {
73+
ByteStreams.copy(stdout, notifyingFileStream);
74+
}
75+
}
76+
77+
@Override
78+
public ImageDetails inspect(ImageReference imageReference) {
79+
new RemoteDockerImage(DockerImageName.parse(imageReference.toString())).get();
80+
81+
InspectImageResponse response = this.dockerClient.inspectImageCmd(imageReference.toString()).exec();
82+
return new JibImageDetails(response.getSize(), response.getId(), response.getRootFS().getLayers());
83+
}
84+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.testcontainers.jib;
2+
3+
import com.google.cloud.tools.jib.api.Containerizer;
4+
import com.google.cloud.tools.jib.api.DockerClient;
5+
import com.google.cloud.tools.jib.api.DockerDaemonImage;
6+
import com.google.cloud.tools.jib.api.Jib;
7+
import com.google.cloud.tools.jib.api.JibContainer;
8+
import com.google.cloud.tools.jib.api.JibContainerBuilder;
9+
import lombok.SneakyThrows;
10+
import org.testcontainers.DockerClientFactory;
11+
import org.testcontainers.utility.Base58;
12+
import org.testcontainers.utility.LazyFuture;
13+
import org.testcontainers.utility.ResourceReaper;
14+
15+
import java.util.Map;
16+
import java.util.function.Function;
17+
import java.util.stream.Collectors;
18+
import java.util.stream.Stream;
19+
20+
public class JibImage extends LazyFuture<String> {
21+
22+
private final DockerClient dockerClient = JibDockerClient.instance();
23+
24+
private static final Map<String, String> DEFAULT_LABELS = Stream
25+
.of(
26+
DockerClientFactory.DEFAULT_LABELS.entrySet().stream(),
27+
ResourceReaper.instance().getLabels().entrySet().stream()
28+
)
29+
.flatMap(Function.identity())
30+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
31+
32+
private final String baseImage;
33+
34+
private final Function<JibContainerBuilder, JibContainerBuilder> jibContainerBuilderFn;
35+
36+
public JibImage(String baseImage, Function<JibContainerBuilder, JibContainerBuilder> jibContainerBuilderFn) {
37+
this.baseImage = baseImage;
38+
this.jibContainerBuilderFn = jibContainerBuilderFn;
39+
}
40+
41+
@SneakyThrows
42+
@Override
43+
protected String resolve() {
44+
JibContainerBuilder containerBuilder = Jib.from(this.dockerClient, DockerDaemonImage.named(this.baseImage));
45+
Function<JibContainerBuilder, JibContainerBuilder> applyLabelsFn = jibContainerBuilder -> {
46+
for (Map.Entry<String, String> entry : DEFAULT_LABELS.entrySet()) {
47+
jibContainerBuilder.addLabel(entry.getKey(), entry.getValue());
48+
}
49+
return jibContainerBuilder;
50+
};
51+
JibContainer jibContainer =
52+
this.jibContainerBuilderFn.andThen(applyLabelsFn)
53+
.apply(containerBuilder)
54+
.containerize(
55+
Containerizer.to(this.dockerClient, DockerDaemonImage.named(Base58.randomString(8).toLowerCase()))
56+
);
57+
return jibContainer.getTargetImage().toString();
58+
}
59+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.testcontainers.jib;
2+
3+
import com.google.cloud.tools.jib.api.DescriptorDigest;
4+
import com.google.cloud.tools.jib.api.ImageDetails;
5+
6+
import java.security.DigestException;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
class JibImageDetails implements ImageDetails {
11+
12+
private long size;
13+
14+
private String imageId;
15+
16+
private List<String> layers;
17+
18+
public JibImageDetails(long size, String imageId, List<String> layers) {
19+
this.size = size;
20+
this.imageId = imageId;
21+
this.layers = layers;
22+
}
23+
24+
@Override
25+
public long getSize() {
26+
return this.size;
27+
}
28+
29+
@Override
30+
public DescriptorDigest getImageId() throws DigestException {
31+
return DescriptorDigest.fromDigest(this.imageId);
32+
}
33+
34+
@Override
35+
public List<DescriptorDigest> getDiffIds() throws DigestException {
36+
List<DescriptorDigest> processedDiffIds = new ArrayList<>(this.layers.size());
37+
for (String diffId : this.layers) {
38+
processedDiffIds.add(DescriptorDigest.fromDigest(diffId.trim()));
39+
}
40+
return processedDiffIds;
41+
}
42+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package org.testcontainers.containers;
2+
3+
import com.github.dockerjava.api.command.InspectImageResponse;
4+
import org.junit.Test;
5+
import org.testcontainers.DockerClientFactory;
6+
import org.testcontainers.containers.output.OutputFrame.OutputType;
7+
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
8+
import org.testcontainers.jib.JibImage;
9+
10+
import java.time.Duration;
11+
import java.util.Collections;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
15+
public class JibTest {
16+
17+
@Test
18+
public void buildImage() {
19+
try (
20+
// jibContainerUsage {
21+
GenericContainer<?> busybox = new GenericContainer<>(
22+
new JibImage(
23+
"busybox:1.35",
24+
jibContainerBuilder -> {
25+
return jibContainerBuilder.setEntrypoint("echo", "Hello World");
26+
}
27+
)
28+
)
29+
.withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(3)))
30+
// }
31+
) {
32+
busybox.start();
33+
String logs = busybox.getLogs(OutputType.STDOUT);
34+
assertThat(logs).contains("Hello World");
35+
}
36+
}
37+
38+
@Test
39+
public void standardLabelsAreAddedWhenUsingJibSetLabels() {
40+
try (
41+
GenericContainer<?> busybox = new GenericContainer<>(
42+
new JibImage(
43+
"busybox:1.35",
44+
jibContainerBuilder -> {
45+
return jibContainerBuilder
46+
.setEntrypoint("echo", "Hello World")
47+
.setLabels(Collections.singletonMap("foo", "bar"));
48+
}
49+
)
50+
)
51+
.withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(3)))
52+
) {
53+
busybox.start();
54+
assertImageLabels(busybox);
55+
}
56+
}
57+
58+
@Test
59+
public void standardLabelsAreAddedWhenUsingJibAddLabel() {
60+
try (
61+
GenericContainer<?> busybox = new GenericContainer<>(
62+
new JibImage(
63+
"busybox:1.35",
64+
jibContainerBuilder -> {
65+
return jibContainerBuilder.setEntrypoint("echo", "Hello World").addLabel("foo", "bar");
66+
}
67+
)
68+
)
69+
.withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(3)))
70+
) {
71+
busybox.start();
72+
assertImageLabels(busybox);
73+
}
74+
}
75+
76+
private static void assertImageLabels(GenericContainer<?> busybox) {
77+
String image = busybox.getContainerInfo().getConfig().getImage();
78+
InspectImageResponse imageResponse = DockerClientFactory.lazyClient().inspectImageCmd(image).exec();
79+
assertThat(imageResponse.getConfig().getLabels())
80+
.containsEntry("foo", "bar")
81+
.containsKeys(
82+
"org.testcontainers",
83+
"org.testcontainers.sessionId",
84+
"org.testcontainers.lang",
85+
"org.testcontainers.version"
86+
);
87+
}
88+
}

docs/features/jib.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Using Jib
2+
3+
[Jib](https://github.com/GoogleContainerTools/jib/tree/master/jib-core) is a library for building Docker images.
4+
You can use it as an alternative to Testcontainers default `DockerfileBuilder`.
5+
6+
<!--codeinclude-->
7+
[GenericContainer with JibImage](../../core/src/test/java/org/testcontainers/containers/JibTest.java) inside_block:jibContainerUsage
8+
<!--/codeinclude-->
9+
10+
!!! hint
11+
The Testcontainers library JAR will not automatically add a `jib-core` JAR to your project. Minimum version required is `com.google.cloud.tools:jib-core:0.22.0`.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ nav:
3939
- features/startup_and_waits.md
4040
- features/container_logs.md
4141
- features/creating_images.md
42+
- features/jib.md
4243
- features/configuration.md
4344
- features/image_name_substitution.md
4445
- features/advanced_options.md

0 commit comments

Comments
 (0)