Skip to content

Commit

Permalink
Support jib extra directories feature
Browse files Browse the repository at this point in the history
Resolves: quarkusio#8936
  • Loading branch information
geoand committed Jun 15, 2020
1 parent bfb8845 commit f12e684
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 21 deletions.
5 changes: 5 additions & 0 deletions bom/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,11 @@
<artifactId>quarkus-container-image-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-util</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-docker-deployment</artifactId>
Expand Down
7 changes: 7 additions & 0 deletions docs/src/main/asciidoc/container-image.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ NOTE: In situations where all that is needed to build a container image and no p
it with the Docker daemon. This means that although Docker isn't used to build the image, it is nevertheless necessary. Also note that using this mode, the built container image *will*
show up when executing `docker images`.

==== Including extra files

There are cases when additional files (other than ones produced by the Quarkus build) need to be added to a container image.
To support these cases, Quarkus copies any file under `src/main/jib` into the built container image (which is essentially the same
idea that the Jib Maven and Gradle plugins support).
For example, the presence of `src/main/jib/foo/bar` would result in `/foo/bar` being added into the container filesystem.

[#docker]
=== Docker

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

package io.quarkus.container.image.docker.deployment;

import static io.quarkus.container.util.PathsUtil.findMainSourcesRoot;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -339,23 +341,4 @@ public Path getDockerExecutionPath() {
}
}

/**
* Return a Map.Entry (which is used as a Tuple) containing the main sources root as the key
* and the project root as the value
*/
private static AbstractMap.SimpleEntry<Path, Path> findMainSourcesRoot(Path outputDirectory) {
Path currentPath = outputDirectory;
do {
Path toCheck = currentPath.resolve(Paths.get("src", "main"));
if (toCheck.toFile().exists()) {
return new AbstractMap.SimpleEntry<>(toCheck, currentPath);
}
if (Files.exists(currentPath.getParent())) {
currentPath = currentPath.getParent();
} else {
return null;
}
} while (true);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2018 Google LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.quarkus.container.image.jib.deployment;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiFunction;

import com.google.cloud.tools.jib.api.JavaContainerBuilder;
import com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;
import com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;
import com.google.cloud.tools.jib.api.buildplan.FilePermissions;
import com.google.cloud.tools.jib.filesystem.DirectoryWalker;

/**
* Copied almost verbatim from Jib's {@code com.google.cloud.tools.jib.plugins.common.JavaContainerBuilderHelper}
* because the module that contains it is internal to Jib
*/
final class ContainerBuilderHelper {

private ContainerBuilderHelper() {
}

/**
* Returns a {@link FileEntriesLayer} for adding the extra directory to the container.
*
* @param sourceDirectory the source extra directory path
* @param targetDirectory the root directory on the container to place the files in
* @param extraDirectoryPermissions map from path on container to file permissions
* @param modificationTimeProvider file modification time provider
* @return a {@link FileEntriesLayer} for adding the extra directory to the container
* @throws IOException if walking the extra directory fails
*/
public static FileEntriesLayer extraDirectoryLayerConfiguration(
Path sourceDirectory,
AbsoluteUnixPath targetDirectory,
Map<String, FilePermissions> extraDirectoryPermissions,
BiFunction<Path, AbsoluteUnixPath, Instant> modificationTimeProvider)
throws IOException {
FileEntriesLayer.Builder builder = FileEntriesLayer.builder()
.setName(JavaContainerBuilder.LayerType.EXTRA_FILES.getName());
Map<PathMatcher, FilePermissions> pathMatchers = new LinkedHashMap<>();
for (Map.Entry<String, FilePermissions> entry : extraDirectoryPermissions.entrySet()) {
pathMatchers.put(
FileSystems.getDefault().getPathMatcher("glob:" + entry.getKey()), entry.getValue());
}

new DirectoryWalker(sourceDirectory)
.filterRoot()
.walk(
localPath -> {
AbsoluteUnixPath pathOnContainer = targetDirectory.resolve(sourceDirectory.relativize(localPath));
Instant modificationTime = modificationTimeProvider.apply(localPath, pathOnContainer);
FilePermissions permissions = extraDirectoryPermissions.get(pathOnContainer.toString());
if (permissions == null) {
// Check for matching globs
Path containerPath = Paths.get(pathOnContainer.toString());
for (Map.Entry<PathMatcher, FilePermissions> entry : pathMatchers.entrySet()) {
if (entry.getKey().matches(containerPath)) {
builder.addEntry(
localPath, pathOnContainer, entry.getValue(), modificationTime);
return;
}
}

// Add with default permissions
if (localPath.toFile().canExecute()) {
// make sure we can execute the file in the container
builder.addEntry(localPath, pathOnContainer, FilePermissions.fromOctalString("754"),
modificationTime);
} else {
builder.addEntry(localPath, pathOnContainer, modificationTime);
}
} else {
// Add with explicit permissions
builder.addEntry(localPath, pathOnContainer, permissions, modificationTime);
}
});
return builder.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.container.image.jib.deployment;

import static io.quarkus.container.util.PathsUtil.findMainSourcesRoot;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
Expand All @@ -25,7 +27,6 @@
import com.google.cloud.tools.jib.api.Jib;
import com.google.cloud.tools.jib.api.JibContainer;
import com.google.cloud.tools.jib.api.JibContainerBuilder;
import com.google.cloud.tools.jib.api.LayerConfiguration;
import com.google.cloud.tools.jib.api.LogEvent;
import com.google.cloud.tools.jib.api.RegistryImage;
import com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;
Expand All @@ -40,6 +41,7 @@
import io.quarkus.container.spi.ContainerImageInfoBuildItem;
import io.quarkus.container.spi.ContainerImageLabelBuildItem;
import io.quarkus.container.spi.ContainerImagePushRequestBuildItem;
import io.quarkus.container.util.PathsUtil;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildProducer;
Expand Down Expand Up @@ -97,6 +99,7 @@ public void buildFromJar(ContainerImageConfig containerImageConfig, JibConfig ji
throw new IllegalArgumentException(
"Package type '" + packageType + "' is not supported by the container-image-jib extension");
}
handleExtraFiles(outputTarget, jibContainerBuilder);
containerize(applicationInfo, containerImageConfig, containerImage, jibContainerBuilder,
pushRequest.isPresent());

Expand All @@ -108,6 +111,7 @@ public void buildFromNative(ContainerImageConfig containerImageConfig, JibConfig
ContainerImageInfoBuildItem containerImage,
NativeImageBuildItem nativeImage,
ApplicationInfoBuildItem applicationInfo,
OutputTargetBuildItem outputTarget,
Optional<ContainerImageBuildRequestBuildItem> buildRequest,
Optional<ContainerImagePushRequestBuildItem> pushRequest,
List<ContainerImageLabelBuildItem> containerImageLabels,
Expand All @@ -125,7 +129,7 @@ public void buildFromNative(ContainerImageConfig containerImageConfig, JibConfig

JibContainerBuilder jibContainerBuilder = createContainerBuilderFromNative(containerImageConfig, jibConfig,
nativeImage, containerImageLabels);

handleExtraFiles(outputTarget, jibContainerBuilder);
containerize(applicationInfo, containerImageConfig, containerImage, jibContainerBuilder,
pushRequest.isPresent());

Expand Down Expand Up @@ -338,6 +342,46 @@ private JibContainerBuilder createContainerBuilderFromNative(ContainerImageConfi
}
}

/**
* Allow users to have custom files in {@code src/main/jib} that will be copied into the built container's file system
* in same manner as the Jib Maven and Gradle plugins do.
* For example, {@code src/main/jib/foo/bar} would add {@code /foo/bar} into the container filesystem.
*
* See: https://github.com/GoogleContainerTools/jib/blob/v0.15.0-core/docs/faq.md#can-i-add-a-custom-directory-to-the-image
*/
private void handleExtraFiles(OutputTargetBuildItem outputTarget, JibContainerBuilder jibContainerBuilder) {
Path outputDirectory = outputTarget.getOutputDirectory();
PathsUtil.findMainSourcesRoot(outputTarget.getOutputDirectory());
Map.Entry<Path, Path> mainSourcesRoot = findMainSourcesRoot(outputDirectory);
if (mainSourcesRoot == null) { // this should never happen
return;
}
Path jibFilesRoot = mainSourcesRoot.getKey().resolve("jib");
if (!jibFilesRoot.toFile().exists()) {
return;
}

FileEntriesLayer extraFilesLayer;
try {
extraFilesLayer = ContainerBuilderHelper.extraDirectoryLayerConfiguration(
jibFilesRoot,
AbsoluteUnixPath.get("/"),
Collections.emptyMap(),
(localPath, ignored2) -> {
try {
return Files.getLastModifiedTime(localPath).toInstant();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
jibContainerBuilder.addFileEntriesLayer(
extraFilesLayer);
} catch (IOException e) {
throw new UncheckedIOException(
"Unable to add extra files in '" + jibFilesRoot.toAbsolutePath().toString() + "' to the container", e);
}
}

private Map<String, String> allLabels(JibConfig jibConfig, List<ContainerImageLabelBuildItem> containerImageLabels) {
if (jibConfig.labels.isEmpty() && containerImageLabels.isEmpty()) {
return Collections.emptyMap();
Expand Down
4 changes: 4 additions & 0 deletions extensions/container-image/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-util</artifactId>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
Expand Down
1 change: 1 addition & 0 deletions extensions/container-image/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<modules>
<module>deployment</module>
<module>spi</module>
<module>util</module>
<module>container-image-docker</module>
<module>container-image-jib</module>
<module>container-image-s2i</module>
Expand Down
15 changes: 15 additions & 0 deletions extensions/container-image/util/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-container-image-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-container-image-util</artifactId>
<name>Quarkus - Container - Image - Util</name>
<description>Utility classes common to the container image extensions</description>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.container.util;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.AbstractMap;

public final class PathsUtil {

private PathsUtil() {
}

/**
* Return a Map.Entry (which is used as a Tuple) containing the main sources root as the key
* and the project root as the value
*/
public static AbstractMap.SimpleEntry<Path, Path> findMainSourcesRoot(Path outputDirectory) {
Path currentPath = outputDirectory;
do {
Path toCheck = currentPath.resolve(Paths.get("src", "main"));
if (toCheck.toFile().exists()) {
return new AbstractMap.SimpleEntry<>(toCheck, currentPath);
}
if (Files.exists(currentPath.getParent())) {
currentPath = currentPath.getParent();
} else {
return null;
}
} while (true);
}
}

0 comments on commit f12e684

Please sign in to comment.