Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to expand wildcard in entrypoint runtime classpath #2866

Merged
merged 5 commits into from
Nov 3, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jib-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ All notable changes to this project will be documented in this file.

### Fixed

- Fixed `NullPointerException` when pulling an OCI base image whose manifest does not have `mediaType` information. ([#2819](https://github.com/GoogleContainerTools/jib/pull/2819))
- Fixed `NullPointerException` when pulling an OCI base image whose manifest does not have `mediaType` information. ([#2819](https://github.com/GoogleContainerTools/jib/issues/2819))
- Fixed build failure when using a Docker daemon base image (`docker://...`) that has duplicate layers. ([#2829](https://github.com/GoogleContainerTools/jib/issues/2829))

## 0.16.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
import com.google.cloud.tools.jib.image.Image;
import com.google.cloud.tools.jib.image.LayerPropertyNotFoundException;
import com.google.cloud.tools.jib.image.json.HistoryEntry;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.UnmodifiableIterator;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import javax.annotation.Nullable;
Expand All @@ -35,6 +38,26 @@ class BuildImageStep implements Callable<Image> {

private static final String DESCRIPTION = "Building container configuration";

@VisibleForTesting
static String truncateLongClasspath(ImmutableList<String> imageEntrypoint) {
List<String> truncated = new ArrayList<>();
UnmodifiableIterator<String> iterator = imageEntrypoint.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
truncated.add(element);

if (element.equals("-cp") || element.equals("-classpath")) {
String classpath = iterator.next();
if (classpath.length() > 200) {
truncated.add(classpath.substring(0, 200) + "<... classpath truncated ...>");
} else {
truncated.add(classpath);
}
}
}
return truncated.toString();
}

private final BuildContext buildContext;
private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;
private final Image baseImage;
Expand Down Expand Up @@ -148,10 +171,15 @@ private ImmutableList<String> computeEntrypoint(
shouldInherit ? baseImage.getEntrypoint() : containerConfiguration.getEntrypoint();

if (entrypointToUse != null) {
String logSuffix = shouldInherit ? " (inherited from base image)" : "";
String message = "Container entrypoint set to " + entrypointToUse + logSuffix;
buildContext.getEventHandlers().dispatch(LogEvent.lifecycle(""));
buildContext.getEventHandlers().dispatch(LogEvent.lifecycle(message));
if (shouldInherit) {
String message =
"Container entrypoint set to " + entrypointToUse + " (inherited from base image)";
buildContext.getEventHandlers().dispatch(LogEvent.lifecycle(message));
} else {
String message = "Container entrypoint set to " + truncateLongClasspath(entrypointToUse);
buildContext.getEventHandlers().dispatch(LogEvent.lifecycle(message));
}
}

return entrypointToUse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
public class JibSystemProperties {

public static final String UPSTREAM_CLIENT = "_JIB_UPSTREAM_CLIENT";
private static final String DISABLE_USER_AGENT = "_JIB_DISABLE_USER_AGENT";

@VisibleForTesting public static final String HTTP_TIMEOUT = "jib.httpTimeout";

Expand All @@ -32,8 +33,6 @@ public class JibSystemProperties {
public static final String SEND_CREDENTIALS_OVER_HTTP = "sendCredentialsOverHttp";
public static final String SERIALIZE = "jib.serialize";

private static final String DISABLE_USER_AGENT = "_JIB_DISABLE_USER_AGENT";

@VisibleForTesting public static final String SKIP_EXISTING_IMAGES = "jib.skipExistingImages";

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,4 +370,33 @@ public void test_generateHistoryObjects() {
// Should be exactly 9 total
Assert.assertEquals(9, image.getHistory().size());
}

@Test
public void testTruncateLongClasspath_shortClasspath() {
ImmutableList<String> entrypoint =
ImmutableList.of(
"java", "-Dmy-property=value", "-cp", "/app/classes:/app/libs/*", "com.example.Main");

Assert.assertEquals(
"[java, -Dmy-property=value, -cp, /app/classes:/app/libs/*, com.example.Main]",
BuildImageStep.truncateLongClasspath(entrypoint));
}

@Test
public void testTruncateLongClasspath_longClasspath() {
String classpath =
"/app/resources:/app/classes:/app/libs/spring-boot-starter-web-2.0.3.RELEASE.jar:/app/libs/"
+ "shared-library-0.1.0.jar:/app/libs/spring-boot-starter-json-2.0.3.RELEASE.jar:/app/"
+ "libs/spring-boot-starter-2.0.3.RELEASE.jar:/app/libs/spring-boot-starter-tomcat-2.0."
+ "3.RELEASE.jar";
ImmutableList<String> entrypoint =
ImmutableList.of("java", "-Dmy-property=value", "-cp", classpath, "com.example.Main");

Assert.assertEquals(
"[java, -Dmy-property=value, -cp, /app/resources:/app/classes:/app/libs/spring-boot-starter"
+ "-web-2.0.3.RELEASE.jar:/app/libs/shared-library-0.1.0.jar:/app/libs/spring-boot-"
+ "starter-json-2.0.3.RELEASE.jar:/app/libs/spring-boot-starter-2.<... classpath "
+ "truncated ...>, com.example.Main]",
BuildImageStep.truncateLongClasspath(entrypoint));
}
}
6 changes: 5 additions & 1 deletion jib-gradle-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ All notable changes to this project will be documented in this file.

### Changed

- Preserves the order of loading dependencies as configured in a project by enumerating dependency JARs instead of using a wildcard (`/app/libs/*`) in the Java runtime classpath for an image entrypoint. ([#1871](https://github.com/GoogleContainerTools/jib/issues/1871), [#1907](https://github.com/GoogleContainerTools/jib/issues/1907), [#2228](https://github.com/GoogleContainerTools/jib/issues/2228), [#2733](https://github.com/GoogleContainerTools/jib/issues/2733))
- The feature is also useful for AppCDS. ([#2471](https://github.com/GoogleContainerTools/jib/issues/2471))
- If expanding the wildcard causes an issue, set the system property `-Djib.noClasspathOrderPreserving=true`. However, the temporary property may eventually be removed in future releases.

### Fixed

- Fixed `NullPointerException` when pulling an OCI base image whose manifest does not have `mediaType` information. ([#2819](https://github.com/GoogleContainerTools/jib/pull/2819))
- Fixed `NullPointerException` when pulling an OCI base image whose manifest does not have `mediaType` information. ([#2819](https://github.com/GoogleContainerTools/jib/issues/2819))
- Fixed build failure when using a Docker daemon base image (`docker://...`) that has duplicate layers. ([#2829](https://github.com/GoogleContainerTools/jib/issues/2829))

## 2.6.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Predicate;
Expand Down Expand Up @@ -212,11 +213,7 @@ public JibContainerBuilder createJibContainerBuilder(
projectDependencies.getFiles().stream().map(File::getName).collect(Collectors.toSet()));
}

JavaPluginConvention javaPluginConvention =
project.getConvention().getPlugin(JavaPluginConvention.class);
SourceSet mainSourceSet =
javaPluginConvention.getSourceSets().getByName(MAIN_SOURCE_SET_NAME);

SourceSet mainSourceSet = getMainSourceSet();
FileCollection classesOutputDirectories =
mainSourceSet.getOutput().getClassesDirs().filter(File::exists);
Path resourcesOutputDirectory = mainSourceSet.getOutput().getResourcesDir().toPath();
Expand Down Expand Up @@ -287,18 +284,31 @@ public JibContainerBuilder createJibContainerBuilder(
@Override
public List<Path> getClassFiles() throws IOException {
// TODO: Consolidate with createJibContainerBuilder
JavaPluginConvention javaPluginConvention =
project.getConvention().getPlugin(JavaPluginConvention.class);
SourceSet mainSourceSet = javaPluginConvention.getSourceSets().getByName(MAIN_SOURCE_SET_NAME);
FileCollection classesOutputDirectories =
mainSourceSet.getOutput().getClassesDirs().filter(File::exists);
getMainSourceSet().getOutput().getClassesDirs().filter(File::exists);
List<Path> classFiles = new ArrayList<>();
for (File classesOutputDirectory : classesOutputDirectories) {
classFiles.addAll(new DirectoryWalker(classesOutputDirectory.toPath()).walk().asList());
}
return classFiles;
}

@Override
public List<Path> getDependencies() {
List<Path> dependencies = new ArrayList<>();
FileCollection runtimeClasspath = getMainSourceSet().getRuntimeClasspath();
// To be on the safe side with the order, calling "forEach" first (no filtering operations).
runtimeClasspath.forEach(
file -> {
if (file.exists()
&& file.isFile()
&& file.getName().toLowerCase(Locale.US).endsWith(".jar")) {
dependencies.add(file.toPath());
}
});
return dependencies;
}

@Override
public void waitForLoggingThread() {
singleThreadedExecutor.shutDownAndAwaitTermination(LOGGING_THREAD_SHUTDOWN_TIMEOUT);
Expand Down Expand Up @@ -506,4 +516,10 @@ private JibGradlePluginExtension<?> findConfiguredExtension(
}
return found.get();
}

private SourceSet getMainSourceSet() {
JavaPluginConvention javaPluginConvention =
project.getConvention().getPlugin(JavaPluginConvention.class);
return javaPluginConvention.getSourceSets().getByName(MAIN_SOURCE_SET_NAME);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
Expand Down Expand Up @@ -100,10 +100,12 @@ public class GradleProjectPropertiesTest {
/** Implementation of {@link FileCollection} that just holds a set of {@link File}s. */
private static class TestFileCollection extends AbstractFileCollection {

private final Set<File> files;
private final Set<File> files = new LinkedHashSet<>();

private TestFileCollection(Set<Path> files) {
this.files = files.stream().map(Path::toFile).collect(Collectors.toSet());
for (Path file : files) {
this.files.add(file.toFile());
}
}

@Override
Expand Down Expand Up @@ -237,7 +239,7 @@ public void setUp() throws URISyntaxException, IOException {
FileCollection classesFileCollection = new TestFileCollection(classesFiles);
Path resourcesOutputDir = getResource("gradle/application/resources");

Set<Path> allFiles = new HashSet<>(classesFiles);
Set<Path> allFiles = new LinkedHashSet<>(classesFiles);
allFiles.add(resourcesOutputDir);
allFiles.add(getResource("gradle/application/dependencies/library.jarC.jar"));
allFiles.add(getResource("gradle/application/dependencies/libraryB.jar"));
Expand Down Expand Up @@ -598,6 +600,20 @@ public void testGetWarFilePath_bootWarDisabled() {
Assert.assertEquals("war.war", gradleProjectProperties.getWarFilePath());
}

@Test
public void testGetDependencies() throws URISyntaxException {
Assert.assertEquals(
Arrays.asList(
getResource("gradle/application/dependencies/library.jarC.jar"),
getResource("gradle/application/dependencies/libraryB.jar"),
getResource("gradle/application/dependencies/libraryA.jar"),
getResource("gradle/application/dependencies/dependency-1.0.0.jar"),
getResource("gradle/application/dependencies/more/dependency-1.0.0.jar"),
getResource("gradle/application/dependencies/another/one/dependency-1.0.0.jar"),
getResource("gradle/application/dependencies/dependencyX-1.0.0-SNAPSHOT.jar")),
gradleProjectProperties.getDependencies());
}

private BuildContext setupBuildContext(String appRoot)
throws InvalidImageReferenceException, CacheDirectoryCreationException {
JavaContainerBuilder javaContainerBuilder =
Expand Down
6 changes: 5 additions & 1 deletion jib-maven-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ All notable changes to this project will be documented in this file.

### Changed

- Preserves the order of loading dependencies as configured in a project by enumerating dependency JARs instead of using a wildcard (`/app/libs/*`) in the Java runtime classpath for an image entrypoint. ([#1871](https://github.com/GoogleContainerTools/jib/issues/1871), [#1907](https://github.com/GoogleContainerTools/jib/issues/1907), [#2228](https://github.com/GoogleContainerTools/jib/issues/2228), [#2733](https://github.com/GoogleContainerTools/jib/issues/2733))
- The feature is also useful for AppCDS. ([#2471](https://github.com/GoogleContainerTools/jib/issues/2471))
- If expanding the wildcard causes an issue, set the system property `-Djib.noClasspathOrderPreserving=true`. However, the temporary property may eventually be removed in future releases.

### Fixed

- Fixed `NullPointerException` when pulling an OCI base image whose manifest does not have `mediaType` information. ([#2819](https://github.com/GoogleContainerTools/jib/pull/2819))
- Fixed `NullPointerException` when pulling an OCI base image whose manifest does not have `mediaType` information. ([#2819](https://github.com/GoogleContainerTools/jib/issues/2819))
- Fixed build failure when using a Docker daemon base image (`docker://...`) that has duplicate layers. ([#2829](https://github.com/GoogleContainerTools/jib/issues/2829))

## 2.6.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,15 @@ public List<Path> getClassFiles() throws IOException {
return new DirectoryWalker(Paths.get(project.getBuild().getOutputDirectory())).walk().asList();
}

@Override
public List<Path> getDependencies() {
return project
.getArtifacts()
.stream()
.map(artifact -> artifact.getFile().toPath())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GradleProjectProperties also checks if the dependency is a jar whereas this does not. Is this intentional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I am not sure if all the artifacts from project.getArtifacts() will always be .jar files, but I think usually they will be .jar files for typical Java projects.

I've checked the MavenProjectProperties code gain, and I can tell at least that we always put all the files from project.getArtifacts() into /app/libs. (All of them are added as dependencies and only them.)

Map<LayerType, List<Path>> classifiedDependencies =
classifyDependencies(project.getArtifacts(), getProjectDependencies());
javaContainerBuilder.addDependencies(
Preconditions.checkNotNull(classifiedDependencies.get(LayerType.DEPENDENCIES)));
javaContainerBuilder.addSnapshotDependencies(
Preconditions.checkNotNull(classifiedDependencies.get(LayerType.SNAPSHOT_DEPENDENCIES)));
javaContainerBuilder.addProjectDependencies(
Preconditions.checkNotNull(classifiedDependencies.get(LayerType.PROJECT_DEPENDENCIES)));

(classifyDependencies(...) classifies project.getArtifacts() into project, snapshot, and third-party dependencies.)

So, doing this should match the actual dependencies we put.

Copy link
Member Author

@chanseokoh chanseokoh Nov 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And for Gradle, checking .jar is a bit different behavior to obtain dependencies than we current do in GradleProjectProperties.createJibContainerBuilder(); it doesn't strictly follow the logic there. However, I think this should work.

.collect(Collectors.toList());
}

@Override
public void waitForLoggingThread() {
singleThreadedExecutor.shutDownAndAwaitTermination(LOGGING_THREAD_SHUTDOWN_TIMEOUT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,20 @@ public void testGetWarArtifact_executionIdNotMatched() {
Paths.get("/foo/bar/helloworld-1.war"), mavenProjectProperties.getWarArtifact());
}

@Test
public void testGetDependencies() throws URISyntaxException {
Assert.assertEquals(
Arrays.asList(
getResource("maven/application/dependencies/library.jarC.jar"),
getResource("maven/application/dependencies/libraryB.jar"),
getResource("maven/application/dependencies/libraryA.jar"),
getResource("maven/application/dependencies/more/dependency-1.0.0.jar"),
getResource("maven/application/dependencies/another/one/dependency-1.0.0.jar"),
testRepository.artifactPathOnDisk("com.test", "dependency", "1.0.0"),
testRepository.artifactPathOnDisk("com.test", "dependencyX", "1.0.0-SNAPSHOT")),
mavenProjectProperties.getDependencies());
}

private BuildContext setUpBuildContext(String appRoot, ContainerizingMode containerizingMode)
throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException {
JavaContainerBuilder javaContainerBuilder =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;

Expand Down Expand Up @@ -577,16 +578,27 @@ static List<String> computeEntrypoint(
case EXPLODED:
classpath.add(appRoot.resolve("resources").toString());
classpath.add(appRoot.resolve("classes").toString());
classpath.add(appRoot.resolve("libs/*").toString());
break;
case PACKAGED:
classpath.add(appRoot.resolve("classpath/*").toString());
classpath.add(appRoot.resolve("libs/*").toString());
break;
default:
throw new IllegalStateException("unknown containerizing mode: " + mode);
}

// TODO: remove the switch after no user reports an issue for a sufficient amount of time.
if (Boolean.getBoolean(PropertyNames.NO_CLASSPATH_ORDER_PRESERVING)) {
classpath.add(appRoot.resolve("libs/*").toString());
} else {
List<String> dependencies =
projectProperties
.getDependencies()
.stream()
.map(path -> appRoot.resolve("libs").resolve(path.getFileName()).toString())
.collect(Collectors.toList());
classpath.addAll(dependencies);
}

String classpathString = String.join(":", classpath);
String mainClass =
MainClassResolver.resolveMainClass(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ JibContainerBuilder createJibContainerBuilder(

List<Path> getClassFiles() throws IOException;

List<Path> getDependencies();

Path getDefaultCacheDirectory();

String getJarPluginName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ public class PropertyNames {
public static final String CONTAINER_FILES_MODIFICATION_TIME =
"jib.container.filesModificationTime";
public static final String CONTAINER_CREATION_TIME = "jib.container.creationTime";
public static final String USE_ONLY_PROJECT_CACHE = "jib.useOnlyProjectCache";
public static final String BASE_IMAGE_CACHE = "jib.baseImageCache";
public static final String APPLICATION_CACHE = "jib.applicationCache";
public static final String ALLOW_INSECURE_REGISTRIES = "jib.allowInsecureRegistries";
public static final String EXTRA_DIRECTORIES_PATHS = "jib.extraDirectories.paths";
public static final String EXTRA_DIRECTORIES_PERMISSIONS = "jib.extraDirectories.permissions";
Expand All @@ -59,11 +56,16 @@ public class PropertyNames {
public static final String OUTPUT_PATHS_TAR = "jib.outputPaths.tar";
public static final String CONTAINERIZING_MODE = "jib.containerizingMode";
public static final String SKIP = "jib.skip";
public static final String CONSOLE = "jib.console";

public static final String CONTAINERIZE = "jib.containerize";
public static final String CONSOLE = "jib.console";
public static final String USE_ONLY_PROJECT_CACHE = "jib.useOnlyProjectCache";
public static final String BASE_IMAGE_CACHE = "jib.baseImageCache";
public static final String APPLICATION_CACHE = "jib.applicationCache";
public static final String ALWAYS_CACHE_BASE_IMAGE = "jib.alwaysCacheBaseImage";
public static final String DISABLE_UPDATE_CHECKS = "jib.disableUpdateChecks";
public static final String CONFIG_DIRECTORY = "jib.configDirectory";
public static final String NO_CLASSPATH_ORDER_PRESERVING = "jib.noClasspathOrderPreserving";

private PropertyNames() {}
}
Loading