From 62c9cab9c83e99c9ecc21a2e9c96a48940dd1656 Mon Sep 17 00:00:00 2001 From: Chanseok Oh Date: Tue, 30 Jul 2019 12:50:26 -0400 Subject: [PATCH] Allow setting file timestamps from plugins (#1818) * Allow setting file timestamps from plugins (#1608) (#1782) * Allow setting file timestamps from plugins (#1608) * Addressed comment related to unnecessary extraDirectories modificationTimes configuration * Addressed remaining comments * Fix integration tests / Clean up * Fix integration test * Handle invalid config value * Refactor * Refactor * Add tests * Refactor and add tests * Take mod time into account when retrieving local application cache * Fix Javadoc * Update error messages * Add Gradle integration tests * Clean up * Remove KEEP_ORIGINAL * Rename methods and variables * Rename things / minor updates * Restore LayerEntriesSelector to consider target modification time too * Fix test * Pass correct argument * format * Fix Javadoc --- jib-core/CHANGELOG.md | 2 +- .../tools/jib/api/JavaContainerBuilder.java | 23 ++++- .../tools/jib/api/LayerConfiguration.java | 52 ++++++++---- .../cloud/tools/jib/api/LayerEntry.java | 17 ++-- .../tools/jib/cache/LayerEntriesSelector.java | 31 ++++--- .../jib/image/ReproducibleLayerBuilder.java | 8 +- .../tools/jib/api/LayerConfigurationTest.java | 2 +- .../cloud/tools/jib/cache/CacheTest.java | 2 +- .../jib/cache/LayerEntriesSelectorTest.java | 34 +++++--- .../image/ReproducibleLayerBuilderTest.java | 14 +-- .../gradle/SingleProjectIntegrationTest.java | 36 ++++++-- ...uild-files-modification-time-custom.gradle | 20 +++++ .../src/main/java/com/test/HelloWorld.java | 8 ++ .../tools/jib/gradle/BuildDockerTask.java | 8 ++ .../tools/jib/gradle/BuildImageTask.java | 8 ++ .../cloud/tools/jib/gradle/BuildTarTask.java | 8 ++ .../tools/jib/gradle/ContainerParameters.java | 14 +++ .../jib/gradle/GradleProjectProperties.java | 11 +-- .../jib/gradle/GradleRawConfiguration.java | 5 ++ .../gradle/GradleProjectPropertiesTest.java | 53 ++++++++---- .../gradle/GradleRawConfigurationTest.java | 2 + .../tools/jib/gradle/JibExtensionTest.java | 8 ++ .../tools/jib/maven/BuildDockerMojo.java | 8 ++ .../cloud/tools/jib/maven/BuildImageMojo.java | 8 ++ .../cloud/tools/jib/maven/BuildTarMojo.java | 8 ++ .../jib/maven/JibPluginConfiguration.java | 25 ++++-- .../jib/maven/MavenProjectProperties.java | 9 +- .../jib/maven/MavenRawConfiguration.java | 5 ++ .../maven/BuildDockerMojoIntegrationTest.java | 6 +- .../maven/BuildImageMojoIntegrationTest.java | 36 ++++++-- .../maven/BuildTarMojoIntegrationTest.java | 2 +- .../jib/maven/JibPluginConfigurationTest.java | 18 ++-- .../jib/maven/MavenProjectPropertiesTest.java | 52 ++++++++---- .../jib/maven/MavenRawConfigurationTest.java | 3 + ...complex-files-modification-time-custom.xml | 76 +++++++++++++++++ .../src/main/java/com/test/HelloWorld.java | 8 ++ ...InvalidFilesModificationTimeException.java | 34 ++++++++ .../common/JavaContainerBuilderHelper.java | 12 ++- .../common/PluginConfigurationProcessor.java | 59 +++++++++++-- .../jib/plugins/common/ProjectProperties.java | 10 +-- .../jib/plugins/common/PropertyNames.java | 2 + .../jib/plugins/common/RawConfiguration.java | 2 + .../JavaContainerBuilderHelperTest.java | 3 +- .../PluginConfigurationProcessorTest.java | 85 ++++++++++++++----- 44 files changed, 660 insertions(+), 177 deletions(-) create mode 100644 jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-files-modification-time-custom.gradle create mode 100644 jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex-files-modification-time-custom.xml create mode 100644 jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InvalidFilesModificationTimeException.java diff --git a/jib-core/CHANGELOG.md b/jib-core/CHANGELOG.md index 0c3c01159e..86f1fc7c45 100644 --- a/jib-core/CHANGELOG.md +++ b/jib-core/CHANGELOG.md @@ -42,7 +42,7 @@ All notable changes to this project will be documented in this file. ### Added -- Overloads for `LayerConfiguration#addEntryRecursive` that take providers allowing for setting file permissions/file modification timestamps on a per-file basis ([#1607](https://github.com/GoogleContainerTools/jib/issues/1607)) +- Overloads for `LayerConfiguration#addEntryRecursive` that take providers to set file permissions and file modification time on a per-file basis ([#1607](https://github.com/GoogleContainerTools/jib/issues/1607)) ### Changed diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/JavaContainerBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JavaContainerBuilder.java index e161b30378..de99f39a9c 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/JavaContainerBuilder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JavaContainerBuilder.java @@ -27,6 +27,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.NotDirectoryException; import java.nio.file.Path; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; @@ -34,6 +35,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -175,6 +177,8 @@ public static JavaContainerBuilder from(RegistryImage registryImage) { private RelativeUnixPath dependenciesDestination = RelativeUnixPath.get("libs"); private RelativeUnixPath othersDestination = RelativeUnixPath.get("classpath"); @Nullable private String mainClass; + private BiFunction modificationTimeProvider = + LayerConfiguration.DEFAULT_MODIFICATION_TIME_PROVIDER; private JavaContainerBuilder(JibContainerBuilder jibContainerBuilder) { this.jibContainerBuilder = jibContainerBuilder; @@ -484,6 +488,19 @@ public JavaContainerBuilder setMainClass(String mainClass) { return this; } + /** + * Sets the modification time provider for container files. + * + * @param modificationTimeProvider a provider that takes a source path and destination path on the + * container and returns the file modification time that should be set for that path + * @return this + */ + public JavaContainerBuilder setModificationTimeProvider( + BiFunction modificationTimeProvider) { + this.modificationTimeProvider = modificationTimeProvider; + return this; + } + /** * Returns a new {@link JibContainerBuilder} using the parameters specified on the {@link * JavaContainerBuilder}. @@ -643,7 +660,8 @@ private void addFileToLayer( if (!layerBuilders.containsKey(layerType)) { layerBuilders.put(layerType, LayerConfiguration.builder()); } - layerBuilders.get(layerType).addEntry(sourceFile, pathInContainer); + Instant modificationTime = modificationTimeProvider.apply(sourceFile, pathInContainer); + layerBuilders.get(layerType).addEntry(sourceFile, pathInContainer, modificationTime); } private void addDirectoryContentsToLayer( @@ -665,7 +683,8 @@ private void addDirectoryContentsToLayer( path -> { AbsoluteUnixPath pathOnContainer = basePathInContainer.resolve(sourceRoot.relativize(path)); - builder.addEntry(path, pathOnContainer); + Instant modificationTime = modificationTimeProvider.apply(path, pathOnContainer); + builder.addEntry(path, pathOnContainer, modificationTime); }); } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/LayerConfiguration.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/LayerConfiguration.java index 55da591bf1..f3899794cb 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/LayerConfiguration.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/LayerConfiguration.java @@ -99,23 +99,40 @@ public Builder addEntry(Path sourceFile, AbsoluteUnixPath pathInContainer) { */ public Builder addEntry( Path sourceFile, AbsoluteUnixPath pathInContainer, FilePermissions permissions) { + return addEntry(sourceFile, pathInContainer, permissions, DEFAULT_MODIFICATION_TIME); + } + + /** + * Adds an entry to the layer with the given file modification time. Only adds the single source + * file to the exact path in the container file system. See {@link Builder#addEntry(Path, + * AbsoluteUnixPath)} for more information. + * + * @param sourceFile the source file to add to the layer + * @param pathInContainer the path in the container file system corresponding to the {@code + * sourceFile} + * @param modificationTime the file modification time + * @return this + * @see Builder#addEntry(Path, AbsoluteUnixPath) + */ + public Builder addEntry( + Path sourceFile, AbsoluteUnixPath pathInContainer, Instant modificationTime) { return addEntry( sourceFile, pathInContainer, - permissions, - DEFAULT_MODIFIED_TIME_PROVIDER.apply(sourceFile, pathInContainer)); + DEFAULT_FILE_PERMISSIONS_PROVIDER.apply(sourceFile, pathInContainer), + modificationTime); } /** - * Adds an entry to the layer with the given permissions. Only adds the single source file to - * the exact path in the container file system. See {@link Builder#addEntry(Path, - * AbsoluteUnixPath)} for more information. + * Adds an entry to the layer with the given permissions and file modification time. Only adds + * the single source file to the exact path in the container file system. See {@link + * Builder#addEntry(Path, AbsoluteUnixPath)} for more information. * * @param sourceFile the source file to add to the layer * @param pathInContainer the path in the container file system corresponding to the {@code * sourceFile} * @param permissions the file permissions on the container - * @param lastModifiedTime the file modification timestamp + * @param modificationTime the file modification time * @return this * @see Builder#addEntry(Path, AbsoluteUnixPath) * @see FilePermissions#DEFAULT_FILE_PERMISSIONS @@ -125,8 +142,8 @@ public Builder addEntry( Path sourceFile, AbsoluteUnixPath pathInContainer, FilePermissions permissions, - Instant lastModifiedTime) { - return addEntry(new LayerEntry(sourceFile, pathInContainer, permissions, lastModifiedTime)); + Instant modificationTime) { + return addEntry(new LayerEntry(sourceFile, pathInContainer, permissions, modificationTime)); } /** @@ -167,7 +184,7 @@ public Builder addEntryRecursive( BiFunction filePermissionProvider) throws IOException { return addEntryRecursive( - sourceFile, pathInContainer, filePermissionProvider, DEFAULT_MODIFIED_TIME_PROVIDER); + sourceFile, pathInContainer, filePermissionProvider, DEFAULT_MODIFICATION_TIME_PROVIDER); } /** @@ -179,7 +196,7 @@ public Builder addEntryRecursive( * sourceFile} * @param filePermissionProvider a provider that takes a source path and destination path on the * container and returns the file permissions that should be set for that path - * @param lastModifiedTimeProvider a provider that takes a source path and destination path on + * @param modificationTimeProvider a provider that takes a source path and destination path on * the container and returns the file modification time that should be set for that path * @return this * @throws IOException if an exception occurred when recursively listing the directory @@ -188,11 +205,11 @@ public Builder addEntryRecursive( Path sourceFile, AbsoluteUnixPath pathInContainer, BiFunction filePermissionProvider, - BiFunction lastModifiedTimeProvider) + BiFunction modificationTimeProvider) throws IOException { FilePermissions permissions = filePermissionProvider.apply(sourceFile, pathInContainer); - Instant modifiedTime = lastModifiedTimeProvider.apply(sourceFile, pathInContainer); - addEntry(sourceFile, pathInContainer, permissions, modifiedTime); + Instant modificationTime = modificationTimeProvider.apply(sourceFile, pathInContainer); + addEntry(sourceFile, pathInContainer, permissions, modificationTime); if (!Files.isDirectory(sourceFile)) { return this; } @@ -202,7 +219,7 @@ public Builder addEntryRecursive( file, pathInContainer.resolve(file.getFileName()), filePermissionProvider, - lastModifiedTimeProvider); + modificationTimeProvider); } } return this; @@ -227,11 +244,12 @@ public LayerConfiguration build() { : FilePermissions.DEFAULT_FILE_PERMISSIONS; /** Default file modification time (EPOCH + 1 second). */ - public static final Instant DEFAULT_MODIFIED_TIME = Instant.ofEpochSecond(1); + public static final Instant DEFAULT_MODIFICATION_TIME = Instant.ofEpochSecond(1); /** Provider that returns default file modification time (EPOCH + 1 second). */ - public static final BiFunction DEFAULT_MODIFIED_TIME_PROVIDER = - (sourcePath, destinationPath) -> DEFAULT_MODIFIED_TIME; + public static final BiFunction + DEFAULT_MODIFICATION_TIME_PROVIDER = + (sourcePath, destinationPath) -> DEFAULT_MODIFICATION_TIME; /** * Gets a new {@link Builder} for {@link LayerConfiguration}. diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/LayerEntry.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/LayerEntry.java index b17ebda94f..d1de767b80 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/LayerEntry.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/LayerEntry.java @@ -29,7 +29,7 @@ public class LayerEntry { private final Path sourceFile; private final AbsoluteUnixPath extractionPath; private final FilePermissions permissions; - private final Instant lastModifiedTime; + private final Instant modificationTime; /** * Instantiates with a source file and the path to place the source file in the container file @@ -55,18 +55,17 @@ public class LayerEntry { * @param extractionPath the path in the container file system corresponding to the {@code * sourceFile} * @param permissions the file permissions on the container - * @param lastModifiedTime the file modification time, default to 1 second since the epoch - * (https://github.com/GoogleContainerTools/jib/issues/1079) + * @param modificationTime the file modification time */ public LayerEntry( Path sourceFile, AbsoluteUnixPath extractionPath, FilePermissions permissions, - Instant lastModifiedTime) { + Instant modificationTime) { this.sourceFile = sourceFile; this.extractionPath = extractionPath; this.permissions = permissions; - this.lastModifiedTime = lastModifiedTime; + this.modificationTime = modificationTime; } /** @@ -74,8 +73,8 @@ public LayerEntry( * * @return the modification time */ - public Instant getLastModifiedTime() { - return lastModifiedTime; + public Instant getModificationTime() { + return modificationTime; } /** @@ -119,11 +118,11 @@ public boolean equals(Object other) { return sourceFile.equals(otherLayerEntry.sourceFile) && extractionPath.equals(otherLayerEntry.extractionPath) && Objects.equals(permissions, otherLayerEntry.permissions) - && Objects.equals(lastModifiedTime, otherLayerEntry.lastModifiedTime); + && Objects.equals(modificationTime, otherLayerEntry.modificationTime); } @Override public int hashCode() { - return Objects.hash(sourceFile, extractionPath, permissions, lastModifiedTime); + return Objects.hash(sourceFile, extractionPath, permissions, modificationTime); } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/cache/LayerEntriesSelector.java b/jib-core/src/main/java/com/google/cloud/tools/jib/cache/LayerEntriesSelector.java index d43a93cd58..b81185b77f 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/cache/LayerEntriesSelector.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/cache/LayerEntriesSelector.java @@ -41,13 +41,15 @@ * { * "sourceFile": "source/file/for/layer/entry/1", * "extractionPath": "/extraction/path/for/layer/entry/1" - * "lastModifiedTime": "2018-10-03T15:48:32.416152Z" + * "sourceModificationTime": "2018-10-03T15:48:32.416152Z" + * "targetModificationTime": "1970-01-01T00:00:01Z", * "permissions": "777" * }, * { * "sourceFile": "source/file/for/layer/entry/2", * "extractionPath": "/extraction/path/for/layer/entry/2" - * "lastModifiedTime": "2018-10-03T15:48:32.416152Z" + * "sourceModificationTime": "2018-10-03T15:48:32.416152Z" + * "targetModificationTime": "1970-01-01T00:00:01Z", * "permissions": "777" * } * ] @@ -61,14 +63,16 @@ static class LayerEntryTemplate implements JsonTemplate, Comparable 0 ? args[0] : "")); + Path worldFilePath = Paths.get("/app/resources/world"); + if (worldFilePath.toFile().exists()) { + System.out.println(Files.getLastModifiedTime(worldFilePath).toString()); + } // Prints the contents of the extra files. if (Files.exists(Paths.get("/foo"))) { @@ -52,11 +57,14 @@ public static void main(String[] args) throws IOException, URISyntaxException { new String(Files.readAllBytes(Paths.get("/foo")), StandardCharsets.UTF_8)); System.out.println( new String(Files.readAllBytes(Paths.get("/bar/cat")), StandardCharsets.UTF_8)); + System.out.println(Files.getLastModifiedTime(Paths.get("/foo")).toString()); + System.out.println(Files.getLastModifiedTime(Paths.get("/bar/cat")).toString()); } // Prints the contents of the files in the second extra directory. if (Files.exists(Paths.get("/baz"))) { System.out.println( new String(Files.readAllBytes(Paths.get("/baz")), StandardCharsets.UTF_8)); + System.out.println(Files.getLastModifiedTime(Paths.get("/baz")).toString()); } // Prints jvm flags diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildDockerTask.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildDockerTask.java index bc636f872c..b131b9df19 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildDockerTask.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildDockerTask.java @@ -26,6 +26,7 @@ import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException; +import com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException; import com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException; import com.google.cloud.tools.jib.plugins.common.JibBuildRunner; import com.google.cloud.tools.jib.plugins.common.MainClassInferenceException; @@ -164,6 +165,13 @@ public void buildDocker() throw new GradleException( "container.volumes is not an absolute Unix-style path: " + ex.getInvalidVolume(), ex); + } catch (InvalidFilesModificationTimeException ex) { + throw new GradleException( + "container.filesModificationTime should be an ISO 8601 date-time (see " + + "DateTimeFormatter.ISO_DATE_TIME) or special keyword \"EPOCH_PLUS_SECOND\": " + + ex.getInvalidFilesModificationTime(), + ex); + } catch (IncompatibleBaseImageJavaVersionException ex) { throw new GradleException( HelpfulSuggestions.forIncompatibleBaseImageJavaVesionForGradle( diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildImageTask.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildImageTask.java index 0a31bd08f7..ef55b10451 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildImageTask.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildImageTask.java @@ -25,6 +25,7 @@ import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException; +import com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException; import com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException; import com.google.cloud.tools.jib.plugins.common.JibBuildRunner; import com.google.cloud.tools.jib.plugins.common.MainClassInferenceException; @@ -144,6 +145,13 @@ public void buildImage() throw new GradleException( "container.volumes is not an absolute Unix-style path: " + ex.getInvalidVolume(), ex); + } catch (InvalidFilesModificationTimeException ex) { + throw new GradleException( + "container.filesModificationTime should be an ISO 8601 date-time (see " + + "DateTimeFormatter.ISO_DATE_TIME) or special keyword \"EPOCH_PLUS_SECOND\": " + + ex.getInvalidFilesModificationTime(), + ex); + } catch (IncompatibleBaseImageJavaVersionException ex) { throw new GradleException( HelpfulSuggestions.forIncompatibleBaseImageJavaVesionForGradle( diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildTarTask.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildTarTask.java index 5cfeaeac80..179b93d21f 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildTarTask.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildTarTask.java @@ -24,6 +24,7 @@ import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException; +import com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException; import com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException; import com.google.cloud.tools.jib.plugins.common.JibBuildRunner; import com.google.cloud.tools.jib.plugins.common.MainClassInferenceException; @@ -162,6 +163,13 @@ public void buildTar() throw new GradleException( "container.volumes is not an absolute Unix-style path: " + ex.getInvalidVolume(), ex); + } catch (InvalidFilesModificationTimeException ex) { + throw new GradleException( + "container.filesModificationTime should be an ISO 8601 date-time (see " + + "DateTimeFormatter.ISO_DATE_TIME) or special keyword \"EPOCH_PLUS_SECOND\": " + + ex.getInvalidFilesModificationTime(), + ex); + } catch (IncompatibleBaseImageJavaVersionException ex) { throw new GradleException( HelpfulSuggestions.forIncompatibleBaseImageJavaVesionForGradle( diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ContainerParameters.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ContainerParameters.java index f84d308649..d9db45e31e 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ContainerParameters.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ContainerParameters.java @@ -47,6 +47,7 @@ public class ContainerParameters { private String appRoot = ""; @Nullable private String user; @Nullable private String workingDirectory; + private String filesModificationTime = "EPOCH_PLUS_SECOND"; @Input @Optional @@ -247,4 +248,17 @@ public String getWorkingDirectory() { public void setWorkingDirectory(String workingDirectory) { this.workingDirectory = workingDirectory; } + + @Input + @Optional + public String getFilesModificationTime() { + if (System.getProperty(PropertyNames.CONTAINER_FILES_MODIFICATION_TIME) != null) { + return System.getProperty(PropertyNames.CONTAINER_FILES_MODIFICATION_TIME); + } + return filesModificationTime; + } + + public void setFilesModificationTime(String filesModificationTime) { + this.filesModificationTime = filesModificationTime; + } } diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java index b9479d47dd..3ca008e507 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java @@ -16,12 +16,10 @@ package com.google.cloud.tools.jib.gradle; -import com.google.cloud.tools.jib.api.AbsoluteUnixPath; import com.google.cloud.tools.jib.api.Containerizer; import com.google.cloud.tools.jib.api.JavaContainerBuilder; import com.google.cloud.tools.jib.api.JibContainerBuilder; import com.google.cloud.tools.jib.api.LogEvent; -import com.google.cloud.tools.jib.api.RegistryImage; import com.google.cloud.tools.jib.event.events.ProgressEvent; import com.google.cloud.tools.jib.event.events.TimerEvent; import com.google.cloud.tools.jib.event.progress.ProgressEventHandler; @@ -133,11 +131,8 @@ private static boolean isProgressFooterEnabled(Project project) { } @Override - public JibContainerBuilder createContainerBuilder( - RegistryImage baseImage, AbsoluteUnixPath appRoot, ContainerizingMode containerizingMode) { - JavaContainerBuilder javaContainerBuilder = - JavaContainerBuilder.from(baseImage).setAppRoot(appRoot); - + public JibContainerBuilder createJibContainerBuilder( + JavaContainerBuilder javaContainerBuilder, ContainerizingMode containerizingMode) { try { if (isWarProject()) { logger.info("WAR project identified, creating WAR image: " + project.getDisplayName()); @@ -233,7 +228,7 @@ public JibContainerBuilder createContainerBuilder( @Override public List getClassFiles() throws IOException { - // TODO: Consolidate with createContainerBuilder + // TODO: Consolidate with createJibContainerBuilder JavaPluginConvention javaPluginConvention = project.getConvention().getPlugin(JavaPluginConvention.class); SourceSet mainSourceSet = javaPluginConvention.getSourceSets().getByName(MAIN_SOURCE_SET_NAME); diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java index 4f7900eb24..c7ae362e26 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java @@ -150,6 +150,11 @@ public Optional getProperty(String propertyName) { return Optional.ofNullable(System.getProperty(propertyName)); } + @Override + public String getFilesModificationTime() { + return jibExtension.getContainer().getFilesModificationTime(); + } + @Override public List getExtraDirectories() { return jibExtension.getExtraDirectories().getPaths(); diff --git a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleProjectPropertiesTest.java b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleProjectPropertiesTest.java index 59f5b11b38..dc2ef244f7 100644 --- a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleProjectPropertiesTest.java +++ b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleProjectPropertiesTest.java @@ -41,6 +41,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -90,6 +91,7 @@ public class GradleProjectPropertiesTest { private static final ContainerizingMode DEFAULT_CONTAINERIZING_MODE = ContainerizingMode.EXPLODED; + private static final Instant SAMPLE_FILE_MODIFICATION_TIME = Instant.ofEpochSecond(32); /** Implementation of {@link FileCollection} that just holds a set of {@link File}s. */ private static class TestFileCollection extends AbstractFileCollection { @@ -163,6 +165,15 @@ private static void assertExtractionPathsUnordered( expectedPaths, entries, layerEntry -> layerEntry.getExtractionPath().toString()); } + private static void assertModificationTime(Instant instant, List layers) { + for (LayerConfiguration layer : layers) { + for (LayerEntry entry : layer.getLayerEntries()) { + String message = "wrong time: " + entry.getSourceFile() + "-->" + entry.getExtractionPath(); + Assert.assertEquals(message, instant, entry.getModificationTime()); + } + } + } + private static Path getResource(String path) throws URISyntaxException { return Paths.get(Resources.getResource(path).toURI()); } @@ -338,7 +349,7 @@ public void testGetMajorJavaVersion_jvm11() { public void testCreateContainerBuilder_correctFiles() throws URISyntaxException, IOException, InvalidImageReferenceException, CacheDirectoryCreationException { - BuildConfiguration configuration = setupBuildConfiguration("/app"); + BuildConfiguration configuration = setupBuildConfiguration("/app", DEFAULT_CONTAINERIZING_MODE); ContainerBuilderLayers layers = new ContainerBuilderLayers(configuration); Path applicationDirectory = getResource("gradle/application"); @@ -366,6 +377,11 @@ public void testCreateContainerBuilder_correctFiles() applicationDirectory.resolve("classes/HelloWorld.class"), applicationDirectory.resolve("classes/some.class")), layers.classesLayerEntries.get(0).getLayerEntries()); + + assertModificationTime(SAMPLE_FILE_MODIFICATION_TIME, layers.snapshotsLayerEntries); + assertModificationTime(SAMPLE_FILE_MODIFICATION_TIME, layers.dependenciesLayerEntries); + assertModificationTime(SAMPLE_FILE_MODIFICATION_TIME, layers.resourcesLayerEntries); + assertModificationTime(SAMPLE_FILE_MODIFICATION_TIME, layers.classesLayerEntries); } @Test @@ -373,17 +389,16 @@ public void testCreateContainerBuilder_noClassesFiles() throws InvalidImageRefer Path nonexistentFile = Paths.get("/nonexistent/file"); Mockito.when(mockMainSourceSetOutput.getClassesDirs()) .thenReturn(new TestFileCollection(ImmutableSet.of(nonexistentFile))); - gradleProjectProperties.createContainerBuilder( - RegistryImage.named("base"), - AbsoluteUnixPath.get("/anything"), - DEFAULT_CONTAINERIZING_MODE); + gradleProjectProperties.createJibContainerBuilder( + JavaContainerBuilder.from(RegistryImage.named("base")), DEFAULT_CONTAINERIZING_MODE); Mockito.verify(mockLogger).warn("No classes files were found - did you compile your project?"); } @Test public void testCreateContainerBuilder_nonDefaultAppRoot() throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException { - BuildConfiguration configuration = setupBuildConfiguration("/my/app"); + BuildConfiguration configuration = + setupBuildConfiguration("/my/app", DEFAULT_CONTAINERIZING_MODE); ContainerBuilderLayers layers = new ContainerBuilderLayers(configuration); assertExtractionPathsUnordered( @@ -413,7 +428,7 @@ public void testCreateContainerBuilder_nonDefaultAppRoot() public void testCreateContainerBuilder_defaultAppRoot() throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException { BuildConfiguration configuration = - setupBuildConfiguration(JavaContainerBuilder.DEFAULT_APP_ROOT); + setupBuildConfiguration(JavaContainerBuilder.DEFAULT_APP_ROOT, DEFAULT_CONTAINERIZING_MODE); ContainerBuilderLayers layers = new ContainerBuilderLayers(configuration); assertExtractionPathsUnordered( Arrays.asList( @@ -443,7 +458,8 @@ public void testCreateContainerBuilder_war() Path webAppDirectory = getResource("gradle/webapp"); setUpWarProject(webAppDirectory); - BuildConfiguration configuration = setupBuildConfiguration("/my/app"); + BuildConfiguration configuration = + setupBuildConfiguration("/my/app", DEFAULT_CONTAINERIZING_MODE); ContainerBuilderLayers layers = new ContainerBuilderLayers(configuration); assertSourcePathsUnordered( ImmutableList.of( @@ -509,7 +525,8 @@ public void testCreateContainerBuilder_defaultWebAppRoot() setUpWarProject(getResource("gradle/webapp")); BuildConfiguration configuration = - setupBuildConfiguration(JavaContainerBuilder.DEFAULT_WEB_APP_ROOT); + setupBuildConfiguration( + JavaContainerBuilder.DEFAULT_WEB_APP_ROOT, DEFAULT_CONTAINERIZING_MODE); ContainerBuilderLayers layers = new ContainerBuilderLayers(configuration); assertExtractionPathsUnordered( Collections.singletonList("/jetty/webapps/ROOT/WEB-INF/lib/dependency-1.0.0.jar"), @@ -544,7 +561,7 @@ public void testCreateContainerBuilder_noErrorIfWebInfClassesDoesNotExist() throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException { temporaryFolder.newFolder("jib-exploded-war", "WEB-INF", "lib"); setUpWarProject(temporaryFolder.getRoot().toPath()); - setupBuildConfiguration(JavaContainerBuilder.DEFAULT_WEB_APP_ROOT); // should pass + setupBuildConfiguration("/anything", DEFAULT_CONTAINERIZING_MODE); // should pass } @Test @@ -552,7 +569,7 @@ public void testCreateContainerBuilder_noErrorIfWebInfLibDoesNotExist() throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException { temporaryFolder.newFolder("jib-exploded-war", "WEB-INF", "classes"); setUpWarProject(temporaryFolder.getRoot().toPath()); - setupBuildConfiguration(JavaContainerBuilder.DEFAULT_WEB_APP_ROOT); // should pass + setupBuildConfiguration("/anything", DEFAULT_CONTAINERIZING_MODE); // should pass } @Test @@ -560,17 +577,19 @@ public void testCreateContainerBuilder_noErrorIfWebInfDoesNotExist() throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException { temporaryFolder.newFolder("jib-exploded-war"); setUpWarProject(temporaryFolder.getRoot().toPath()); - setupBuildConfiguration(JavaContainerBuilder.DEFAULT_WEB_APP_ROOT); // should pass + setupBuildConfiguration("/anything", DEFAULT_CONTAINERIZING_MODE); // should pass } - private BuildConfiguration setupBuildConfiguration(String appRoot) + private BuildConfiguration setupBuildConfiguration( + String appRoot, ContainerizingMode containerizingMode) throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException { + JavaContainerBuilder javaContainerBuilder = + JavaContainerBuilder.from(RegistryImage.named("base")) + .setAppRoot(AbsoluteUnixPath.get(appRoot)) + .setModificationTimeProvider((ignored1, ignored2) -> SAMPLE_FILE_MODIFICATION_TIME); JibContainerBuilder jibContainerBuilder = new GradleProjectProperties(mockProject, mockLogger) - .createContainerBuilder( - RegistryImage.named("base"), - AbsoluteUnixPath.get(appRoot), - DEFAULT_CONTAINERIZING_MODE); + .createJibContainerBuilder(javaContainerBuilder, containerizingMode); return JibContainerBuilderTestHelper.toBuildConfiguration( jibContainerBuilder, Containerizer.to(RegistryImage.named("to")) diff --git a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java index 083ebde5c6..5fcab8e405 100644 --- a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java +++ b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java @@ -68,6 +68,7 @@ public void testGetters() { Mockito.when(containerParameters.getPorts()).thenReturn(Arrays.asList("80/tcp", "0")); Mockito.when(containerParameters.getUseCurrentTimestamp()).thenReturn(true); Mockito.when(containerParameters.getUser()).thenReturn("admin:wheel"); + Mockito.when(containerParameters.getFilesModificationTime()).thenReturn("2011-12-03T22:42:05Z"); GradleRawConfiguration rawConfiguration = new GradleRawConfiguration(jibExtension); @@ -96,5 +97,6 @@ public void testGetters() { Sets.newHashSet(rawConfiguration.getToTags())); Assert.assertTrue(rawConfiguration.getUseCurrentTimestamp()); Assert.assertEquals("admin:wheel", rawConfiguration.getUser().get()); + Assert.assertEquals("2011-12-03T22:42:05Z", rawConfiguration.getFilesModificationTime()); } } diff --git a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibExtensionTest.java b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibExtensionTest.java index 2a12108c88..851271c620 100644 --- a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibExtensionTest.java +++ b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibExtensionTest.java @@ -54,6 +54,7 @@ private static void clearProperties() { System.clearProperty("jib.container.ports"); System.clearProperty("jib.container.useCurrentTimestamp"); System.clearProperty("jib.container.user"); + System.clearProperty("jib.container.filesModificationTime"); System.clearProperty("jib.containerizingMode"); System.clearProperty("jib.extraDirectory.path"); System.clearProperty("jib.extraDirectory.permissions"); @@ -124,6 +125,8 @@ public void testContainer() { Assert.assertEquals(Collections.emptyList(), testJibExtension.getContainer().getPorts()); Assert.assertEquals(Collections.emptyMap(), testJibExtension.getContainer().getLabels()); Assert.assertEquals("", testJibExtension.getContainer().getAppRoot()); + Assert.assertEquals( + "EPOCH_PLUS_SECOND", testJibExtension.getContainer().getFilesModificationTime()); testJibExtension.container( container -> { @@ -137,6 +140,7 @@ public void testContainer() { container.setLabels(ImmutableMap.of("label1", "value1", "label2", "value2")); container.setFormat(ImageFormat.OCI); container.setAppRoot("some invalid appRoot value"); + container.setFilesModificationTime("some invalid time value"); }); ContainerParameters container = testJibExtension.getContainer(); Assert.assertEquals(Arrays.asList("foo", "bar", "baz"), container.getEntrypoint()); @@ -151,6 +155,7 @@ public void testContainer() { ImmutableMap.of("label1", "value1", "label2", "value2"), container.getLabels()); Assert.assertSame(ImageFormat.OCI, container.getFormat()); Assert.assertEquals("some invalid appRoot value", container.getAppRoot()); + Assert.assertEquals("some invalid time value", container.getFilesModificationTime()); } @Test @@ -294,6 +299,9 @@ public void testProperties() { Assert.assertTrue(testJibExtension.getContainer().getUseCurrentTimestamp()); System.setProperty("jib.container.user", "myUser"); Assert.assertEquals("myUser", testJibExtension.getContainer().getUser()); + System.setProperty("jib.container.filesModificationTime", "2011-12-03T22:42:05Z"); + Assert.assertEquals( + "2011-12-03T22:42:05Z", testJibExtension.getContainer().getFilesModificationTime()); System.setProperty("jib.containerizingMode", "packaged"); Assert.assertEquals("packaged", testJibExtension.getContainerizingMode()); System.setProperty("jib.extraDirectories.paths", "/foo,/bar/baz"); diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildDockerMojo.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildDockerMojo.java index fbe015dfc9..ee8ee77276 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildDockerMojo.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildDockerMojo.java @@ -27,6 +27,7 @@ import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException; +import com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException; import com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException; import com.google.cloud.tools.jib.plugins.common.JibBuildRunner; import com.google.cloud.tools.jib.plugins.common.MainClassInferenceException; @@ -162,6 +163,13 @@ public void execute() throws MojoExecutionException, MojoFailureException { throw new MojoExecutionException( " is not an absolute Unix-style path: " + ex.getInvalidVolume(), ex); + } catch (InvalidFilesModificationTimeException ex) { + throw new MojoExecutionException( + " should be an ISO 8601 date-time (see " + + "DateTimeFormatter.ISO_DATE_TIME) or special keyword \"EPOCH_PLUS_SECOND\": " + + ex.getInvalidFilesModificationTime(), + ex); + } catch (IncompatibleBaseImageJavaVersionException ex) { throw new MojoExecutionException( HelpfulSuggestions.forIncompatibleBaseImageJavaVesionForMaven( diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java index 9db4d08608..2f0271a6c9 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java @@ -26,6 +26,7 @@ import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException; +import com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException; import com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException; import com.google.cloud.tools.jib.plugins.common.JibBuildRunner; import com.google.cloud.tools.jib.plugins.common.MainClassInferenceException; @@ -156,6 +157,13 @@ public void execute() throws MojoExecutionException, MojoFailureException { throw new MojoExecutionException( " is not an absolute Unix-style path: " + ex.getInvalidVolume(), ex); + } catch (InvalidFilesModificationTimeException ex) { + throw new MojoExecutionException( + " should be an ISO 8601 date-time (see " + + "DateTimeFormatter.ISO_DATE_TIME) or special keyword \"EPOCH_PLUS_SECOND\": " + + ex.getInvalidFilesModificationTime(), + ex); + } catch (IncompatibleBaseImageJavaVersionException ex) { throw new MojoExecutionException( HelpfulSuggestions.forIncompatibleBaseImageJavaVesionForMaven( diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildTarMojo.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildTarMojo.java index 7502f820e1..a1998e11f3 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildTarMojo.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildTarMojo.java @@ -24,6 +24,7 @@ import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException; import com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException; +import com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException; import com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException; import com.google.cloud.tools.jib.plugins.common.JibBuildRunner; import com.google.cloud.tools.jib.plugins.common.MainClassInferenceException; @@ -134,6 +135,13 @@ public void execute() throws MojoExecutionException, MojoFailureException { throw new MojoExecutionException( " is not an absolute Unix-style path: " + ex.getInvalidVolume(), ex); + } catch (InvalidFilesModificationTimeException ex) { + throw new MojoExecutionException( + " should be an ISO 8601 date-time (see " + + "DateTimeFormatter.ISO_DATE_TIME) or special keyword \"EPOCH_PLUS_SECOND\": " + + ex.getInvalidFilesModificationTime(), + ex); + } catch (IncompatibleBaseImageJavaVersionException ex) { throw new MojoExecutionException( HelpfulSuggestions.forIncompatibleBaseImageJavaVesionForMaven( diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java index bd4c7bcc9d..a02d311cef 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java @@ -129,9 +129,7 @@ Optional getMode() { /** Configuration for {@code from} parameter, */ public static class FromConfiguration { - @Nullable - @Parameter(required = true) - private String image; + @Nullable @Parameter private String image; @Nullable @Parameter private String credHelper; @@ -186,6 +184,8 @@ public static class ContainerParameters { @Nullable @Parameter private String user; @Nullable @Parameter private String workingDirectory; + + @Parameter private String filesModificationTime = "EPOCH_PLUS_SECOND"; } /** Configuration for the {@code extraDirectories} parameter. */ @@ -281,7 +281,7 @@ String getBaseImage() { if (property != null) { return property; } - return Preconditions.checkNotNull(from).image; + return from.image; } /** @@ -295,7 +295,7 @@ String getBaseImageCredentialHelperName() { if (property != null) { return property; } - return Preconditions.checkNotNull(from).credHelper; + return from.credHelper; } AuthConfiguration getBaseImageAuth() { @@ -348,7 +348,7 @@ String getTargetImageCredentialHelperName() { if (property != null) { return property; } - return Preconditions.checkNotNull(to).credHelper; + return to.credHelper; } AuthConfiguration getTargetImageAuth() { @@ -543,6 +543,19 @@ String getFormat() { return container.format; } + /** + * Gets the configured files modification time value. + * + * @return the configured files modification time value + */ + String getFilesModificationTime() { + String property = getProperty(PropertyNames.CONTAINER_FILES_MODIFICATION_TIME); + if (property != null) { + return property; + } + return container.filesModificationTime; + } + /** * Gets the list of configured extra directory paths. * diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenProjectProperties.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenProjectProperties.java index d643039da0..c5f1019856 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenProjectProperties.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenProjectProperties.java @@ -16,13 +16,11 @@ package com.google.cloud.tools.jib.maven; -import com.google.cloud.tools.jib.api.AbsoluteUnixPath; import com.google.cloud.tools.jib.api.Containerizer; import com.google.cloud.tools.jib.api.JavaContainerBuilder; import com.google.cloud.tools.jib.api.JavaContainerBuilder.LayerType; import com.google.cloud.tools.jib.api.JibContainerBuilder; import com.google.cloud.tools.jib.api.LogEvent; -import com.google.cloud.tools.jib.api.RegistryImage; import com.google.cloud.tools.jib.event.events.ProgressEvent; import com.google.cloud.tools.jib.event.events.TimerEvent; import com.google.cloud.tools.jib.event.progress.ProgressEventHandler; @@ -184,12 +182,9 @@ static int getVersionFromString(String versionString) { } @Override - public JibContainerBuilder createContainerBuilder( - RegistryImage baseImage, AbsoluteUnixPath appRoot, ContainerizingMode containerizingMode) + public JibContainerBuilder createJibContainerBuilder( + JavaContainerBuilder javaContainerBuilder, ContainerizingMode containerizingMode) throws IOException { - JavaContainerBuilder javaContainerBuilder = - JavaContainerBuilder.from(baseImage).setAppRoot(appRoot); - try { if (isWarProject()) { Path explodedWarPath = diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java index ecb17b52c7..f7c69cc162 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java @@ -155,6 +155,11 @@ public Optional getProperty(String propertyName) { return Optional.ofNullable(jibPluginConfiguration.getProperty(propertyName)); } + @Override + public String getFilesModificationTime() { + return jibPluginConfiguration.getFilesModificationTime(); + } + @Override public List getExtraDirectories() { return MojoCommon.getExtraDirectories(jibPluginConfiguration); diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java index 782f9ac3dc..06a3515bd0 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java @@ -103,7 +103,8 @@ public void testExecute_simple() Instant before = Instant.now(); Assert.assertEquals( - "Hello, world. An argument.\nrw-r--r--\nrw-r--r--\nfoo\ncat\n", + "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" + + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n", buildToDockerDaemonAndRun(simpleTestProject.getProjectRoot(), targetImage)); Instant buildTime = Instant.parse( @@ -191,7 +192,8 @@ public void testExecute_noToImageAndInvalidProjectName() buildToDockerDaemon( simpleTestProject.getProjectRoot(), "image reference ignored", "pom-no-to-image.xml"); Assert.assertEquals( - "Hello, world. \n", new Command("docker", "run", "--rm", "my-artifact-id:1").run()); + "Hello, world. \n1970-01-01T00:00:01Z\n", + new Command("docker", "run", "--rm", "my-artifact-id:1").run()); } @Test diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildImageMojoIntegrationTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildImageMojoIntegrationTest.java index 603d2dfe02..e2bf745290 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildImageMojoIntegrationTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildImageMojoIntegrationTest.java @@ -365,7 +365,10 @@ public void testExecute_simple() before.toString().getBytes(StandardCharsets.UTF_8)); Assert.assertEquals( - "Hello, " + before + ". An argument.\nrw-r--r--\nrw-r--r--\nfoo\ncat\n", + "Hello, " + + before + + ". An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" + + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n", buildAndRun(simpleTestProject.getProjectRoot(), targetImage, "pom.xml", true)); Instant buildTime = @@ -403,7 +406,7 @@ public void testExecute_simpleOnJava11() String targetImage = getTestImageReference("simpleimage:maven"); Assert.assertEquals( - "Hello, world. An argument.\n", + "Hello, world. An argument.\n1970-01-01T00:00:01Z\n", buildAndRun(simpleTestProject.getProjectRoot(), targetImage, "pom-java11.xml", false)); } @@ -453,7 +456,8 @@ public void testExecute_multipleExtraDirectories() throws DigestException, VerificationException, IOException, InterruptedException { String targetImage = getTestImageReference("simpleimage:maven"); Assert.assertEquals( - "Hello, world. An argument.\nrw-r--r--\nrw-r--r--\nfoo\ncat\nbaz\n", + "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" + + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\nbaz\n1970-01-01T00:00:01Z\n", buildAndRun(simpleTestProject.getProjectRoot(), targetImage, "pom-extra-dirs.xml", false)); assertLayerSize(9, targetImage); // one more than usual } @@ -505,21 +509,37 @@ public void testExecute_complex() throws IOException, InterruptedException, VerificationException, DigestException { String targetImage = "localhost:6000/compleximage:maven" + System.nanoTime(); Assert.assertEquals( - "Hello, world. An argument.\nrwxr-xr-x\nrwxrwxrwx\nfoo\ncat\n-Xms512m\n-Xdebug\nenvvalue1\nenvvalue2\n", + "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrwxr-xr-x\nrwxrwxrwx\nfoo\ncat\n" + + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n" + + "-Xms512m\n-Xdebug\nenvvalue1\nenvvalue2\n", buildAndRunComplex( targetImage, "testuser2", "testpassword2", localRegistry2, "pom-complex.xml")); assertWorkingDirectory("", targetImage); assertEntrypoint( - "[java -Xms512m -Xdebug -cp /other:/app/resources:/app/classes:/app/libs/* com.test.HelloWorld]", + "[java -Xms512m -Xdebug -cp /other:/app/resources:/app/classes:/app/libs/* " + + "com.test.HelloWorld]", targetImage); } + @Test + public void testExecute_filesModificationTimeCustom() + throws IOException, InterruptedException, VerificationException, DigestException { + String targetImage = "localhost:6000/simpleimage:maven" + System.nanoTime(); + String pom = "pom-complex-files-modification-time-custom.xml"; + Assert.assertEquals( + "Hello, world. \n2019-06-17T16:30:00Z\nrw-r--r--\nrw-r--r--\n" + + "foo\ncat\n2019-06-17T16:30:00Z\n2019-06-17T16:30:00Z\n", + buildAndRunComplex(targetImage, "testuser2", "testpassword2", localRegistry2, pom)); + } + @Test public void testExecute_complex_sameFromAndToRegistry() throws IOException, InterruptedException, VerificationException, DigestException { String targetImage = "localhost:5000/compleximage:maven" + System.nanoTime(); Assert.assertEquals( - "Hello, world. An argument.\nrwxr-xr-x\nrwxrwxrwx\nfoo\ncat\n-Xms512m\n-Xdebug\nenvvalue1\nenvvalue2\n", + "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrwxr-xr-x\nrwxrwxrwx\nfoo\ncat\n" + + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n" + + "-Xms512m\n-Xdebug\nenvvalue1\nenvvalue2\n", buildAndRunComplex( targetImage, "testuser", "testpassword", localRegistry1, "pom-complex.xml")); assertWorkingDirectory("", targetImage); @@ -530,7 +550,9 @@ public void testExecute_complexProperties() throws InterruptedException, DigestException, VerificationException, IOException { String targetImage = "localhost:6000/compleximage:maven" + System.nanoTime(); Assert.assertEquals( - "Hello, world. An argument.\nrwxr-xr-x\nrwxrwxrwx\nfoo\ncat\n-Xms512m\n-Xdebug\nenvvalue1\nenvvalue2\n", + "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrwxr-xr-x\nrwxrwxrwx\nfoo\ncat\n" + + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n" + + "-Xms512m\n-Xdebug\nenvvalue1\nenvvalue2\n", buildAndRunComplex( targetImage, "testuser2", diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildTarMojoIntegrationTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildTarMojoIntegrationTest.java index 12e5616a5e..19f231e98f 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildTarMojoIntegrationTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildTarMojoIntegrationTest.java @@ -72,7 +72,7 @@ public void testExecute_simple() .toString()) .run(); Assert.assertEquals( - "Hello, world. An argument.\nrw-r--r--\nrw-r--r--\nfoo\ncat\n", + "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n", new Command("docker", "run", "--rm", targetImage).run()); Instant buildTime = diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java index 4ac459b4be..1457b52e85 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java @@ -69,6 +69,7 @@ public void testDefaults() { Assert.assertNull(testPluginConfiguration.getWorkingDirectory()); Assert.assertTrue(testPluginConfiguration.getExtraClasspath().isEmpty()); Assert.assertEquals("exploded", testPluginConfiguration.getContainerizingMode()); + Assert.assertEquals("EPOCH_PLUS_SECOND", testPluginConfiguration.getFilesModificationTime()); } @Test @@ -118,8 +119,10 @@ public void testSystemProperties() { Assert.assertTrue(testPluginConfiguration.getUseCurrentTimestamp()); sessionProperties.put("jib.container.user", "myUser"); Assert.assertEquals("myUser", testPluginConfiguration.getUser()); - sessionProperties.put("jib.container.workingDirectory", "working directory"); - Assert.assertEquals("working directory", testPluginConfiguration.getWorkingDirectory()); + sessionProperties.put("jib.container.workingDirectory", "/working/directory"); + Assert.assertEquals("/working/directory", testPluginConfiguration.getWorkingDirectory()); + sessionProperties.put("jib.container.filesModificationTime", "2011-12-03T22:42:05Z"); + Assert.assertEquals("2011-12-03T22:42:05Z", testPluginConfiguration.getFilesModificationTime()); sessionProperties.put("jib.container.extraClasspath", "/foo,/bar"); Assert.assertEquals( ImmutableList.of("/foo", "/bar"), testPluginConfiguration.getExtraClasspath()); @@ -185,8 +188,12 @@ public void testPomProperties() { Assert.assertTrue(testPluginConfiguration.getUseCurrentTimestamp()); project.getProperties().setProperty("jib.container.user", "myUser"); Assert.assertEquals("myUser", testPluginConfiguration.getUser()); - project.getProperties().setProperty("jib.container.workingDirectory", "working directory"); - Assert.assertEquals("working directory", testPluginConfiguration.getWorkingDirectory()); + project.getProperties().setProperty("jib.container.workingDirectory", "/working/directory"); + Assert.assertEquals("/working/directory", testPluginConfiguration.getWorkingDirectory()); + project + .getProperties() + .setProperty("jib.container.filesModificationTime", "2011-12-03T22:42:05Z"); + Assert.assertEquals("2011-12-03T22:42:05Z", testPluginConfiguration.getFilesModificationTime()); project.getProperties().setProperty("jib.container.extraClasspath", "/foo,/bar"); Assert.assertEquals( ImmutableList.of("/foo", "/bar"), testPluginConfiguration.getExtraClasspath()); @@ -210,8 +217,7 @@ public void testPomProperties() { @Test public void testEmptyOrNullTags() { // https://github.com/GoogleContainerTools/jib/issues/1534 - // Maven turns empty tags into null entries, and its possible - // to have empty tags in jib.to.tags + // Maven turns empty tags into null entries, and its possible to have empty tags in jib.to.tags sessionProperties.put("jib.to.tags", "a,,b"); try { testPluginConfiguration.getTargetImageAdditionalTags(); diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenProjectPropertiesTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenProjectPropertiesTest.java index 7efabee3fc..e1acf1f5ae 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenProjectPropertiesTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenProjectPropertiesTest.java @@ -20,6 +20,7 @@ import com.google.cloud.tools.jib.api.CacheDirectoryCreationException; import com.google.cloud.tools.jib.api.Containerizer; import com.google.cloud.tools.jib.api.InvalidImageReferenceException; +import com.google.cloud.tools.jib.api.JavaContainerBuilder; import com.google.cloud.tools.jib.api.JavaContainerBuilder.LayerType; import com.google.cloud.tools.jib.api.JibContainerBuilder; import com.google.cloud.tools.jib.api.JibContainerBuilderTestHelper; @@ -38,6 +39,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -70,6 +72,7 @@ public class MavenProjectPropertiesTest { private static final ContainerizingMode DEFAULT_CONTAINERIZING_MODE = ContainerizingMode.EXPLODED; + private static final Instant SAMPLE_FILE_MODIFICATION_TIME = Instant.ofEpochSecond(32); /** Helper for reading back layers in a {@code BuildConfiguration}. */ private static class ContainerBuilderLayers { @@ -119,6 +122,15 @@ private static void assertExtractionPathsUnordered( expectedPaths, entries, layerEntry -> layerEntry.getExtractionPath().toString()); } + private static void assertModificationTime(Instant instant, List layers) { + for (LayerConfiguration layer : layers) { + for (LayerEntry entry : layer.getLayerEntries()) { + String message = "wrong time: " + entry.getSourceFile() + "-->" + entry.getExtractionPath(); + Assert.assertEquals(message, instant, entry.getModificationTime()); + } + } + } + private static void assertNonDefaultAppRoot(BuildConfiguration configuration) { ContainerBuilderLayers layers = new ContainerBuilderLayers(configuration); assertExtractionPathsUnordered( @@ -403,6 +415,11 @@ public void testCreateContainerBuilder_correctFiles() applicationDirectory.resolve("output/package/some.class"), applicationDirectory.resolve("output/some.class")), layers.classesLayers.get(0).getLayerEntries()); + + assertModificationTime(SAMPLE_FILE_MODIFICATION_TIME, layers.dependenciesLayers); + assertModificationTime(SAMPLE_FILE_MODIFICATION_TIME, layers.snapshotsLayers); + assertModificationTime(SAMPLE_FILE_MODIFICATION_TIME, layers.resourcesLayers); + assertModificationTime(SAMPLE_FILE_MODIFICATION_TIME, layers.classesLayers); } @Test @@ -551,7 +568,7 @@ public void testCreateContainerBuilder_noErrorIfWebInfDoesNotExist() Mockito.when(mockBuild.getDirectory()).thenReturn(temporaryFolder.getRoot().toString()); Mockito.when(mockBuild.getFinalName()).thenReturn("final-name"); - setupBuildConfiguration("/my/app", DEFAULT_CONTAINERIZING_MODE); // should pass + setupBuildConfiguration("/anything", DEFAULT_CONTAINERIZING_MODE); // should pass } @Test @@ -562,7 +579,7 @@ public void testCreateContainerBuilder_noErrorIfWebInfLibDoesNotExist() Mockito.when(mockBuild.getDirectory()).thenReturn(temporaryFolder.getRoot().toString()); Mockito.when(mockBuild.getFinalName()).thenReturn("final-name"); - setupBuildConfiguration("/my/app", DEFAULT_CONTAINERIZING_MODE); // should pass + setupBuildConfiguration("/anything", DEFAULT_CONTAINERIZING_MODE); // should pass } @Test @@ -573,7 +590,7 @@ public void testCreateContainerBuilder_noErrorIfWebInfClassesDoesNotExist() Mockito.when(mockBuild.getDirectory()).thenReturn(temporaryFolder.getRoot().toString()); Mockito.when(mockBuild.getFinalName()).thenReturn("final-name"); - setupBuildConfiguration("/my/app", DEFAULT_CONTAINERIZING_MODE); // should pass + setupBuildConfiguration("/anything", DEFAULT_CONTAINERIZING_MODE); // should pass } @Test @@ -610,19 +627,6 @@ public void testIsWarProject_GwtLibPackagingIsNotWar() { Assert.assertFalse(mavenProjectProperties.isWarProject()); } - private BuildConfiguration setupBuildConfiguration( - String appRoot, ContainerizingMode containerizingMode) - throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException { - JibContainerBuilder JibContainerBuilder = - new MavenProjectProperties(mockMavenProject, mockMavenSession, mockLog) - .createContainerBuilder( - RegistryImage.named("base"), AbsoluteUnixPath.get(appRoot), containerizingMode); - return JibContainerBuilderTestHelper.toBuildConfiguration( - JibContainerBuilder, - Containerizer.to(RegistryImage.named("to")) - .setExecutorService(MoreExecutors.newDirectExecutorService())); - } - @Test public void testClassifyDependencies() { Set artifacts = @@ -665,6 +669,22 @@ public void testClassifyDependencies() { newArtifact("com.test", "projectC", "3.0").getFile().toPath())); } + private BuildConfiguration setupBuildConfiguration( + String appRoot, ContainerizingMode containerizingMode) + throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException { + JavaContainerBuilder javaContainerBuilder = + JavaContainerBuilder.from(RegistryImage.named("base")) + .setAppRoot(AbsoluteUnixPath.get(appRoot)) + .setModificationTimeProvider((ignored1, ignored2) -> SAMPLE_FILE_MODIFICATION_TIME); + JibContainerBuilder JibContainerBuilder = + new MavenProjectProperties(mockMavenProject, mockMavenSession, mockLog) + .createJibContainerBuilder(javaContainerBuilder, containerizingMode); + return JibContainerBuilderTestHelper.toBuildConfiguration( + JibContainerBuilder, + Containerizer.to(RegistryImage.named("to")) + .setExecutorService(MoreExecutors.newDirectExecutorService())); + } + private Artifact newArtifact(String group, String artifactId, String version) { Artifact artifact = new DefaultArtifact(group, artifactId, version, null, "jar", "", null); artifact.setFile(new File("/tmp/" + group + artifactId + version)); diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java index 0ec69cc561..a796fcc415 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java @@ -75,6 +75,8 @@ public void testGetters() { .thenReturn(new HashSet<>(Arrays.asList("additional", "tags"))); Mockito.when(jibPluginConfiguration.getUseCurrentTimestamp()).thenReturn(true); Mockito.when(jibPluginConfiguration.getUser()).thenReturn("admin:wheel"); + Mockito.when(jibPluginConfiguration.getFilesModificationTime()) + .thenReturn("2011-12-03T22:42:05Z"); MavenRawConfiguration rawConfiguration = new MavenRawConfiguration(jibPluginConfiguration); @@ -102,6 +104,7 @@ public void testGetters() { new HashSet<>(Arrays.asList("additional", "tags")), rawConfiguration.getToTags()); Assert.assertTrue(rawConfiguration.getUseCurrentTimestamp()); Assert.assertEquals("admin:wheel", rawConfiguration.getUser().get()); + Assert.assertEquals("2011-12-03T22:42:05Z", rawConfiguration.getFilesModificationTime()); Mockito.verifyNoMoreInteractions(eventHandlers); } diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex-files-modification-time-custom.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex-files-modification-time-custom.xml new file mode 100644 index 0000000000..02acd783bd --- /dev/null +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex-files-modification-time-custom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + com.test + hello-world + 1 + + + UTF-8 + UTF-8 + @@PluginVersion@@ + + + + + com.test + dependency + 1.0.0 + system + ${project.basedir}/libs/dependency-1.0.0.jar + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + + + localhost:5000/distroless/java + + testuser + testpassword + + + + ${_TARGET_IMAGE} + + ${_TARGET_USERNAME} + ${_TARGET_PASSWORD} + + + + true + + 1000/tcp + 2000-2003/udp + + + value1 + value2 + + 2019-06-17T16:30:00Z + + + src/main/jib-custom + + true + + + + + diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/java/com/test/HelloWorld.java b/jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/java/com/test/HelloWorld.java index bc41a9f3f2..cb492a0904 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/java/com/test/HelloWorld.java +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/java/com/test/HelloWorld.java @@ -24,6 +24,7 @@ import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermissions; @@ -41,6 +42,10 @@ public static void main(String[] args) throws IOException, URISyntaxException { HelloWorld.class.getResourceAsStream("/world"), StandardCharsets.UTF_8))) { String world = reader.readLine(); System.out.println(greeting + ", " + world + ". " + (args.length > 0 ? args[0] : "")); + Path worldFilePath = Paths.get("/app/resources/world"); + if (worldFilePath.toFile().exists()) { + System.out.println(Files.getLastModifiedTime(worldFilePath).toString()); + } // Prints the contents of the extra files. if (Files.exists(Paths.get("/foo"))) { @@ -52,11 +57,14 @@ public static void main(String[] args) throws IOException, URISyntaxException { new String(Files.readAllBytes(Paths.get("/foo")), StandardCharsets.UTF_8)); System.out.println( new String(Files.readAllBytes(Paths.get("/bar/cat")), StandardCharsets.UTF_8)); + System.out.println(Files.getLastModifiedTime(Paths.get("/foo")).toString()); + System.out.println(Files.getLastModifiedTime(Paths.get("/bar/cat")).toString()); } // Prints the contents of the files in the second extra directory. if (Files.exists(Paths.get("/baz"))) { System.out.println( new String(Files.readAllBytes(Paths.get("/baz")), StandardCharsets.UTF_8)); + System.out.println(Files.getLastModifiedTime(Paths.get("/baz")).toString()); } // Prints jvm flags diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InvalidFilesModificationTimeException.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InvalidFilesModificationTimeException.java new file mode 100644 index 0000000000..421f328eb0 --- /dev/null +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InvalidFilesModificationTimeException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 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 com.google.cloud.tools.jib.plugins.common; + +import java.time.format.DateTimeParseException; + +public class InvalidFilesModificationTimeException extends Exception { + + private final String invalidValue; + + public InvalidFilesModificationTimeException( + String message, String invalidValue, DateTimeParseException ex) { + super(message, ex); + this.invalidValue = invalidValue; + } + + public String getInvalidFilesModificationTime() { + return invalidValue; + } +} diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JavaContainerBuilderHelper.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JavaContainerBuilderHelper.java index 38958cfc33..fa2bc1f267 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JavaContainerBuilderHelper.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JavaContainerBuilderHelper.java @@ -27,7 +27,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Instant; import java.util.Map; +import java.util.function.BiFunction; import java.util.function.Predicate; /** Helper for constructing {@link JavaContainerBuilder}-based {@link JibContainerBuilder}s. */ @@ -38,11 +40,14 @@ public class JavaContainerBuilderHelper { * * @param extraDirectory the source extra directory path * @param extraDirectoryPermissions map from path on container to file permissions + * @param modificationTimeProvider file modification time provider * @return a {@link LayerConfiguration} for adding the extra directory to the container * @throws IOException if walking the extra directory fails */ public static LayerConfiguration extraDirectoryLayerConfiguration( - Path extraDirectory, Map extraDirectoryPermissions) + Path extraDirectory, + Map extraDirectoryPermissions, + BiFunction modificationTimeProvider) throws IOException { LayerConfiguration.Builder builder = LayerConfiguration.builder().setName(LayerType.EXTRA_FILES.getName()); @@ -52,11 +57,12 @@ public static LayerConfiguration extraDirectoryLayerConfiguration( localPath -> { AbsoluteUnixPath pathOnContainer = AbsoluteUnixPath.get("/").resolve(extraDirectory.relativize(localPath)); + Instant modificationTime = modificationTimeProvider.apply(localPath, pathOnContainer); FilePermissions permissions = extraDirectoryPermissions.get(pathOnContainer); if (permissions == null) { - builder.addEntry(localPath, pathOnContainer); + builder.addEntry(localPath, pathOnContainer, modificationTime); } else { - builder.addEntry(localPath, pathOnContainer, permissions); + builder.addEntry(localPath, pathOnContainer, permissions, modificationTime); } }); return builder.build(); diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java index 568799202f..17e7889a93 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java @@ -39,6 +39,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -46,6 +48,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.BiFunction; import javax.annotation.Nullable; /** @@ -64,7 +67,7 @@ public static PluginConfigurationProcessor processCommonConfigurationForDockerDa throws InvalidImageReferenceException, MainClassInferenceException, InvalidAppRootException, IOException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, - InvalidContainerizingModeException { + InvalidContainerizingModeException, InvalidFilesModificationTimeException { ImageReference targetImageReference = getGeneratedTargetDockerTag(rawConfiguration, projectProperties, helpfulSuggestions); DockerDaemonImage targetImage = DockerDaemonImage.named(targetImageReference); @@ -94,7 +97,7 @@ public static PluginConfigurationProcessor processCommonConfigurationForTarImage throws InvalidImageReferenceException, MainClassInferenceException, InvalidAppRootException, IOException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, - InvalidContainerizingModeException { + InvalidContainerizingModeException, InvalidFilesModificationTimeException { ImageReference targetImageReference = getGeneratedTargetDockerTag(rawConfiguration, projectProperties, helpfulSuggestions); TarImage targetImage = TarImage.named(targetImageReference).saveTo(tarImagePath); @@ -116,7 +119,7 @@ public static PluginConfigurationProcessor processCommonConfigurationForRegistry throws InvalidImageReferenceException, MainClassInferenceException, InvalidAppRootException, IOException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, - InvalidContainerizingModeException { + InvalidContainerizingModeException, InvalidFilesModificationTimeException { Preconditions.checkArgument(rawConfiguration.getToImage().isPresent()); ImageReference targetImageReference = ImageReference.parse(rawConfiguration.getToImage().get()); @@ -157,7 +160,7 @@ static PluginConfigurationProcessor processCommonConfiguration( throws InvalidImageReferenceException, MainClassInferenceException, InvalidAppRootException, IOException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, - InvalidContainerizingModeException { + InvalidContainerizingModeException, InvalidFilesModificationTimeException { JibSystemProperties.checkHttpTimeoutProperty(); JibSystemProperties.checkProxyPortProperty(); @@ -184,11 +187,16 @@ static PluginConfigurationProcessor processCommonConfiguration( inferredAuthProvider, rawConfiguration.getFromCredHelper().orElse(null)); + BiFunction modificationTimeProvider = + createModificationTimeProvider(rawConfiguration.getFilesModificationTime()); + JavaContainerBuilder javaContainerBuilder = + JavaContainerBuilder.from(baseImage) + .setAppRoot(getAppRootChecked(rawConfiguration, projectProperties)) + .setModificationTimeProvider(modificationTimeProvider); JibContainerBuilder jibContainerBuilder = projectProperties - .createContainerBuilder( - baseImage, - getAppRootChecked(rawConfiguration, projectProperties), + .createJibContainerBuilder( + javaContainerBuilder, getContainerizingModeChecked(rawConfiguration, projectProperties)) .setEntrypoint(computeEntrypoint(rawConfiguration, projectProperties)) .setProgramArguments(rawConfiguration.getProgramArguments().orElse(null)) @@ -211,7 +219,9 @@ static PluginConfigurationProcessor processCommonConfiguration( if (Files.exists(directory)) { jibContainerBuilder.addLayer( JavaContainerBuilderHelper.extraDirectoryLayerConfiguration( - directory, rawConfiguration.getExtraDirectoryPermissions())); + directory, + rawConfiguration.getExtraDirectoryPermissions(), + modificationTimeProvider)); } } @@ -431,6 +441,39 @@ static Optional getWorkingDirectoryChecked(RawConfiguration ra } } + /** + * Creates a modification time provider based on the config value. The value can be: + * + *
    + *
  1. {@code EPOCH_PLUS_SECOND} to create a provider which trims file modification time to + * EPOCH + 1 second + *
  2. date in ISO 8601 format + *
+ * + * @param modificationTime modification time config value + * @return corresponding modification time provider + * @throws InvalidFilesModificationTimeException if the config value is not in ISO 8601 format + */ + @VisibleForTesting + static BiFunction createModificationTimeProvider( + String modificationTime) throws InvalidFilesModificationTimeException { + try { + switch (modificationTime) { + case "EPOCH_PLUS_SECOND": + Instant epochPlusSecond = Instant.ofEpochSecond(1); + return (ignored1, ignored2) -> epochPlusSecond; + + default: + Instant timestamp = + DateTimeFormatter.ISO_DATE_TIME.parse(modificationTime, Instant::from); + return (ignored1, ignored2) -> timestamp; + } + + } catch (DateTimeParseException ex) { + throw new InvalidFilesModificationTimeException(modificationTime, modificationTime, ex); + } + } + // TODO: find a way to reduce the number of arguments. private static boolean configureCredentialRetrievers( RawConfiguration rawConfiguration, diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ProjectProperties.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ProjectProperties.java index 3b716a59be..46aaad508d 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ProjectProperties.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ProjectProperties.java @@ -16,11 +16,10 @@ package com.google.cloud.tools.jib.plugins.common; -import com.google.cloud.tools.jib.api.AbsoluteUnixPath; import com.google.cloud.tools.jib.api.Containerizer; +import com.google.cloud.tools.jib.api.JavaContainerBuilder; import com.google.cloud.tools.jib.api.JibContainerBuilder; import com.google.cloud.tools.jib.api.LogEvent; -import com.google.cloud.tools.jib.api.RegistryImage; import java.io.IOException; import java.nio.file.Path; import java.util.List; @@ -58,14 +57,13 @@ public interface ProjectProperties { /** * Starts the containerization process. * - * @param baseImage the base image - * @param appRoot root directory in the image where the app will be placed + * @param javaContainerBuilder Java container builder to start with * @param containerizingMode mode to containerize the app * @return a {@link JibContainerBuilder} with classes, resources, and dependencies added to it * @throws IOException if there is a problem walking the project files */ - JibContainerBuilder createContainerBuilder( - RegistryImage baseImage, AbsoluteUnixPath appRoot, ContainerizingMode containerizingMode) + JibContainerBuilder createJibContainerBuilder( + JavaContainerBuilder javaContainerBuilder, ContainerizingMode containerizingMode) throws IOException; List getClassFiles() throws IOException; diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java index 33ebab924a..9fe63a276d 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java @@ -43,6 +43,8 @@ public class PropertyNames { public static final String CONTAINER_VOLUMES = "jib.container.volumes"; public static final String CONTAINER_PORTS = "jib.container.ports"; public static final String CONTAINER_USE_CURRENT_TIMESTAMP = "jib.container.useCurrentTimestamp"; + public static final String CONTAINER_FILES_MODIFICATION_TIME = + "jib.container.filesModificationTime"; 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"; diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java index 93cbf06e52..c2fa1ea6c3 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java @@ -76,6 +76,8 @@ public interface RawConfiguration { Optional getProperty(String propertyName); + String getFilesModificationTime(); + List getExtraDirectories(); Map getExtraDirectoryPermissions(); diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JavaContainerBuilderHelperTest.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JavaContainerBuilderHelperTest.java index 516a14bc3b..5cf1c06d49 100644 --- a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JavaContainerBuilderHelperTest.java +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JavaContainerBuilderHelperTest.java @@ -37,6 +37,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -84,7 +85,7 @@ public void testExtraDirectoryLayerConfiguration() throws URISyntaxException, IO Path extraFilesDirectory = Paths.get(Resources.getResource("core/layer").toURI()); LayerConfiguration layerConfiguration = JavaContainerBuilderHelper.extraDirectoryLayerConfiguration( - extraFilesDirectory, Collections.emptyMap()); + extraFilesDirectory, Collections.emptyMap(), (ignored1, ignored2) -> Instant.EPOCH); assertSourcePathsUnordered( Arrays.asList( extraFilesDirectory.resolve("a"), diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessorTest.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessorTest.java index 85e087e2eb..9870c2d886 100644 --- a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessorTest.java +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessorTest.java @@ -22,6 +22,7 @@ import com.google.cloud.tools.jib.api.FilePermissions; import com.google.cloud.tools.jib.api.ImageReference; import com.google.cloud.tools.jib.api.InvalidImageReferenceException; +import com.google.cloud.tools.jib.api.JavaContainerBuilder; import com.google.cloud.tools.jib.api.Jib; import com.google.cloud.tools.jib.api.JibContainerBuilder; import com.google.cloud.tools.jib.api.JibContainerBuilderTestHelper; @@ -36,10 +37,13 @@ import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Instant; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -92,6 +96,7 @@ public void setUp() throws IOException, InvalidImageReferenceException { Mockito.when(rawConfiguration.getFromAuth()).thenReturn(authProperty); Mockito.when(rawConfiguration.getEntrypoint()).thenReturn(Optional.empty()); Mockito.when(rawConfiguration.getAppRoot()).thenReturn("/app"); + Mockito.when(rawConfiguration.getFilesModificationTime()).thenReturn("EPOCH_PLUS_SECOND"); Mockito.when(rawConfiguration.getExtraDirectories()) .thenReturn(Arrays.asList(Paths.get("nonexistent/path"))); Mockito.when(rawConfiguration.getContainerizingMode()).thenReturn("exploded"); @@ -100,10 +105,8 @@ public void setUp() throws IOException, InvalidImageReferenceException { Mockito.when(projectProperties.getMainClassFromJar()).thenReturn("java.lang.Object"); Mockito.when(projectProperties.getDefaultCacheDirectory()).thenReturn(Paths.get("cache")); Mockito.when( - projectProperties.createContainerBuilder( - Mockito.any(RegistryImage.class), - Mockito.any(AbsoluteUnixPath.class), - Mockito.any(ContainerizingMode.class))) + projectProperties.createJibContainerBuilder( + Mockito.any(JavaContainerBuilder.class), Mockito.any(ContainerizingMode.class))) .thenReturn(Jib.from("base")); Mockito.when(projectProperties.isOffline()).thenReturn(false); @@ -122,7 +125,8 @@ public void testPluginConfigurationProcessor_defaults() throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, - NumberFormatException, InvalidContainerizingModeException { + NumberFormatException, InvalidContainerizingModeException, + InvalidFilesModificationTimeException { PluginConfigurationProcessor processor = createPluginConfigurationProcessor(); BuildConfiguration buildConfiguration = getBuildConfiguration(processor.getJibContainerBuilder()); @@ -146,7 +150,7 @@ public void testPluginConfigurationProcessor_extraDirectory() InvalidAppRootException, IOException, IncompatibleBaseImageJavaVersionException, InvalidWorkingDirectoryException, InvalidImageReferenceException, CacheDirectoryCreationException, NumberFormatException, - InvalidContainerizingModeException { + InvalidContainerizingModeException, InvalidFilesModificationTimeException { Path extraDirectory = Paths.get(Resources.getResource("core/layer").toURI()); Mockito.when(rawConfiguration.getExtraDirectories()).thenReturn(Arrays.asList(extraDirectory)); Mockito.when(rawConfiguration.getExtraDirectoryPermissions()) @@ -192,7 +196,7 @@ public void testPluginConfigurationProcessor_cacheDirectorySystemProperties() throws InvalidContainerVolumeException, MainClassInferenceException, InvalidAppRootException, IOException, InvalidWorkingDirectoryException, InvalidImageReferenceException, IncompatibleBaseImageJavaVersionException, NumberFormatException, - InvalidContainerizingModeException { + InvalidContainerizingModeException, InvalidFilesModificationTimeException { System.setProperty(PropertyNames.BASE_IMAGE_CACHE, "new/base/cache"); System.setProperty(PropertyNames.APPLICATION_CACHE, "/new/application/cache"); @@ -210,7 +214,7 @@ public void testPluginConfigurationProcessor_warProjectBaseImage() throws InvalidImageReferenceException, MainClassInferenceException, InvalidAppRootException, IOException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, - InvalidContainerizingModeException { + InvalidContainerizingModeException, InvalidFilesModificationTimeException { Mockito.when(projectProperties.isWarProject()).thenReturn(true); PluginConfigurationProcessor processor = createPluginConfigurationProcessor(); @@ -226,7 +230,8 @@ public void testEntrypoint() throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, - NumberFormatException, InvalidContainerizingModeException { + NumberFormatException, InvalidContainerizingModeException, + InvalidFilesModificationTimeException { Mockito.when(rawConfiguration.getEntrypoint()) .thenReturn(Optional.of(Arrays.asList("custom", "entrypoint"))); @@ -287,7 +292,8 @@ public void testEntrypoint_defaultWarPackaging() throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, - NumberFormatException, InvalidContainerizingModeException { + NumberFormatException, InvalidContainerizingModeException, + InvalidFilesModificationTimeException { Mockito.when(rawConfiguration.getEntrypoint()).thenReturn(Optional.empty()); Mockito.when(projectProperties.isWarProject()).thenReturn(true); @@ -305,7 +311,8 @@ public void testEntrypoint_defaultNonWarPackaging() throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, - NumberFormatException, InvalidContainerizingModeException { + NumberFormatException, InvalidContainerizingModeException, + InvalidFilesModificationTimeException { Mockito.when(rawConfiguration.getEntrypoint()).thenReturn(Optional.empty()); Mockito.when(projectProperties.isWarProject()).thenReturn(false); @@ -327,7 +334,8 @@ public void testEntrypoint_extraClasspathNonWarPackaging() throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, - NumberFormatException, InvalidContainerizingModeException { + NumberFormatException, InvalidContainerizingModeException, + InvalidFilesModificationTimeException { Mockito.when(rawConfiguration.getEntrypoint()).thenReturn(Optional.empty()); Mockito.when(rawConfiguration.getExtraClasspath()) .thenReturn(Collections.singletonList("/foo")); @@ -352,7 +360,8 @@ public void testUser() throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, - NumberFormatException, InvalidContainerizingModeException { + NumberFormatException, InvalidContainerizingModeException, + InvalidFilesModificationTimeException { Mockito.when(rawConfiguration.getUser()).thenReturn(Optional.of("customUser")); PluginConfigurationProcessor processor = createPluginConfigurationProcessor(); @@ -368,7 +377,8 @@ public void testUser_null() throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, - NumberFormatException, InvalidContainerizingModeException { + NumberFormatException, InvalidContainerizingModeException, + InvalidFilesModificationTimeException { PluginConfigurationProcessor processor = createPluginConfigurationProcessor(); BuildConfiguration buildConfiguration = getBuildConfiguration(processor.getJibContainerBuilder()); @@ -382,7 +392,8 @@ public void testEntrypoint_warningOnJvmFlags() throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, - NumberFormatException, InvalidContainerizingModeException { + NumberFormatException, InvalidContainerizingModeException, + InvalidFilesModificationTimeException { Mockito.when(rawConfiguration.getEntrypoint()) .thenReturn(Optional.of(Arrays.asList("custom", "entrypoint"))); Mockito.when(rawConfiguration.getJvmFlags()).thenReturn(Collections.singletonList("jvmFlag")); @@ -406,7 +417,8 @@ public void testEntrypoint_warningOnMainclass() throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, - NumberFormatException, InvalidContainerizingModeException { + NumberFormatException, InvalidContainerizingModeException, + InvalidFilesModificationTimeException { Mockito.when(rawConfiguration.getEntrypoint()) .thenReturn(Optional.of(Arrays.asList("custom", "entrypoint"))); Mockito.when(rawConfiguration.getMainClass()).thenReturn(Optional.of("java.util.Object")); @@ -430,7 +442,8 @@ public void testEntrypointClasspath_nonDefaultAppRoot() throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, - NumberFormatException, InvalidContainerizingModeException { + NumberFormatException, InvalidContainerizingModeException, + InvalidFilesModificationTimeException { Mockito.when(rawConfiguration.getAppRoot()).thenReturn("/my/app"); PluginConfigurationProcessor processor = createPluginConfigurationProcessor(); @@ -453,7 +466,8 @@ public void testWebAppEntrypoint_inheritedFromBaseImage() throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, - NumberFormatException, InvalidContainerizingModeException { + NumberFormatException, InvalidContainerizingModeException, + InvalidFilesModificationTimeException { Mockito.when(projectProperties.isWarProject()).thenReturn(true); PluginConfigurationProcessor processor = createPluginConfigurationProcessor(); @@ -837,11 +851,44 @@ public void testGetInvalidVolumesList() { } } + @Test + public void createModificationTimeProvider_epochPlusSecond() + throws InvalidFilesModificationTimeException { + BiFunction timeProvider = + PluginConfigurationProcessor.createModificationTimeProvider("EPOCH_PLUS_SECOND"); + Assert.assertEquals( + Instant.ofEpochSecond(1), + timeProvider.apply(Paths.get("foo"), AbsoluteUnixPath.get("/bar"))); + } + + @Test + public void createModificationTimeProvider_isoDateTimeValue() + throws InvalidFilesModificationTimeException { + BiFunction timeProvider = + PluginConfigurationProcessor.createModificationTimeProvider("2011-12-03T10:15:30+09:00"); + Instant expected = DateTimeFormatter.ISO_DATE_TIME.parse("2011-12-03T01:15:30Z", Instant::from); + Assert.assertEquals( + expected, timeProvider.apply(Paths.get("foo"), AbsoluteUnixPath.get("/bar"))); + } + + @Test + public void createModificationTimeProvider_invalidValue() { + try { + BiFunction timeProvider = + PluginConfigurationProcessor.createModificationTimeProvider("invalid format"); + timeProvider.apply(Paths.get("foo"), AbsoluteUnixPath.get("/bar")); + Assert.fail(); + } catch (InvalidFilesModificationTimeException ex) { + Assert.assertEquals("invalid format", ex.getMessage()); + Assert.assertEquals("invalid format", ex.getInvalidFilesModificationTime()); + } + } + private PluginConfigurationProcessor createPluginConfigurationProcessor() throws InvalidImageReferenceException, MainClassInferenceException, InvalidAppRootException, IOException, InvalidWorkingDirectoryException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, - InvalidContainerizingModeException { + InvalidContainerizingModeException, InvalidFilesModificationTimeException { return PluginConfigurationProcessor.processCommonConfiguration( rawConfiguration, ignored -> Optional.empty(),