From 1874fd2ea279a5037e07ffca6f549e32a11e01b0 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Wed, 27 Sep 2023 20:57:02 +0530 Subject: [PATCH] fix (jkube-kit) : lastModified timestamps should be preserved while preparing assembly We don't seem to be preserving lastModified timestamps of files that are being copied while preparing the assembly for image. Make sure we're copying all the metadata of files/directories as well while copying them. Signed-off-by: Rohan Kumar --- .../common/archive/AssemblyFileSetUtils.java | 2 + .../jkube/kit/common/util/FileUtil.java | 10 +++- ...ileSetUtilsProcessAssemblyFileSetTest.java | 31 +++++++++++- .../jkube/kit/common/util/FileUtilTest.java | 47 +++++++++++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/archive/AssemblyFileSetUtils.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/archive/AssemblyFileSetUtils.java index 9b16a415d4..13605b79c6 100644 --- a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/archive/AssemblyFileSetUtils.java +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/archive/AssemblyFileSetUtils.java @@ -169,8 +169,10 @@ private static List copy(Path sourceDirectory, File source, F for (File sourceChild : Optional.ofNullable(source.listFiles()).orElse(new File[0])) { copy(sourceDirectory, sourceChild, new File(target, sourceChild.getName()), assemblyFileSet); } + FileUtil.updateLastModifiedTimestamp(target, source.lastModified()); } else { FileUtil.copy(source, target); + FileUtil.updateLastModifiedTimestamp(target.getParentFile(), source.getParentFile().lastModified()); } return calculateFilePermissions(source, target, assemblyFileSet); } diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/FileUtil.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/FileUtil.java index 66e3141909..650a42229b 100644 --- a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/FileUtil.java +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/FileUtil.java @@ -36,6 +36,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; /** @@ -201,7 +202,7 @@ public static void copy(File sourceFile, File targetFile) throws IOException { } public static void copy(Path sourcePath, Path targetPath) throws IOException { - Files.copy(sourcePath, targetPath, REPLACE_EXISTING); + Files.copy(sourcePath, targetPath, REPLACE_EXISTING, COPY_ATTRIBUTES); } public static void copyDirectoryIfNotExists(File sourceDir, File targetDir) throws IOException { @@ -252,5 +253,12 @@ public static void createDirectory(File directory) throws IOException { throw new IOException("Failed to create directory: " + directory.getAbsolutePath()); } } + + public static void updateLastModifiedTimestamp(File file, long timestamp) { + boolean parentLastModifiedUpdated = file.setLastModified(timestamp); + if (!parentLastModifiedUpdated) { + throw new IllegalStateException("Not able to update last modified timestamp of " + file.getPath()); + } + } } diff --git a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/archive/AssemblyFileSetUtilsProcessAssemblyFileSetTest.java b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/archive/AssemblyFileSetUtilsProcessAssemblyFileSetTest.java index b4bc046189..d6f435efe9 100644 --- a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/archive/AssemblyFileSetUtilsProcessAssemblyFileSetTest.java +++ b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/archive/AssemblyFileSetUtilsProcessAssemblyFileSetTest.java @@ -16,6 +16,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.Date; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -47,18 +48,22 @@ class AssemblyFileSetUtilsProcessAssemblyFileSetTest { private File baseDirectory; private File sourceDirectory; private File outputDirectory; + private static long sourceFilesTimestamp; @BeforeEach public void setUp() throws Exception { assumeFalse(isWindows()); baseDirectory = new File(temp, "base"); + sourceFilesTimestamp = new Date().getTime() - 10; sourceDirectory = new File(baseDirectory, "source-directory"); final List sourceSubdirectories = Stream.of("one", "two", "three") .map(s -> new File(sourceDirectory, s)).collect(Collectors.toList()); for (File directory : Stream.concat(Stream.of(baseDirectory, sourceDirectory), sourceSubdirectories.stream()).collect(Collectors.toList())) { FileUtils.forceMkdir(directory); populateSampleFiles(directory); + assertThat(directory.setLastModified(sourceFilesTimestamp)).isTrue(); } + assertThat(sourceDirectory.setLastModified(sourceFilesTimestamp)).isTrue(); outputDirectory = new File(temp, "output"); } @@ -71,7 +76,9 @@ public void tearDown() { private static void populateSampleFiles(File baseDirectory) throws IOException { for (String fileName : new String[]{"1.txt", "3.other", "37"}) { - FileUtils.touch(new File(baseDirectory, fileName)); + File file = new File(baseDirectory, fileName); + FileUtils.touch(file); + assertThat(file.setLastModified(sourceFilesTimestamp)).isTrue(); } } @@ -566,4 +573,26 @@ void withRelativeSourceAndDirectoryExcludes() throws Exception { ); } + @Test + void timestampsPreservedWhilePreparingAssembly() throws IOException { + // Given + final AssemblyFileSet afs = AssemblyFileSet.builder() + .directory(sourceDirectory) + .build(); + final Assembly layer = new Assembly(); + final AssemblyConfiguration ac = AssemblyConfiguration.builder() + .targetDir("deployments") + .build(); + // When + final List result = processAssemblyFileSet(baseDirectory, outputDirectory, afs, layer, ac); + // Then + assertThat(result).hasSize(16); + File assemblyFileSetOutput = new File(outputDirectory, "deployments"); + FileAssertions.assertThat(assemblyFileSetOutput) + .exists() + .fileTree() + .filteredOn(p -> new File(assemblyFileSetOutput, p).lastModified() != sourceFilesTimestamp) + .isEmpty(); + } + } diff --git a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/FileUtilTest.java b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/FileUtilTest.java index 71388ce406..31ca01b435 100644 --- a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/FileUtilTest.java +++ b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/FileUtilTest.java @@ -15,7 +15,10 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Date; import java.util.List; import org.junit.jupiter.api.Test; @@ -25,6 +28,7 @@ import org.junit.jupiter.api.condition.OS; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.eclipse.jkube.kit.common.util.FileUtil.getRelativeFilePath; import static org.eclipse.jkube.kit.common.util.FileUtil.getRelativePath; @@ -151,6 +155,49 @@ void testGetRelativePath() throws IOException { assertThat(relativeFile.getPath()).isEqualTo("foo" + File.separator + "fileInFoo1"); } + @Test + void copy_whenFileCopied_shouldPreserveLastModifiedTimestamp() throws IOException { + // Given + File sourceFile = new File(folder, "source"); + Files.write(sourceFile.toPath(), "testdata".getBytes(StandardCharsets.UTF_8)); + long originalTimestamp = new Date().getTime() - 10; + assertThat(sourceFile.setLastModified(originalTimestamp)).isTrue(); + Path targetFilePath = folder.toPath().resolve("target"); + + // When + FileUtil.copy(sourceFile.toPath(), targetFilePath); + + // Then + assertThat(targetFilePath.toFile()).hasSameTextualContentAs(sourceFile); + assertThat(targetFilePath.toFile().lastModified()).isEqualTo(originalTimestamp); + } + + @Test + void updateLastModifiedTimestamp_whenInvokedOnValidFile_shouldUpdateLastModifiedAttribute() throws IOException { + // Given + File sourceFile = new File(folder, "source"); + Files.createFile(sourceFile.toPath()); + long timestamp = new Date().getTime() - 10; + + // When + FileUtil.updateLastModifiedTimestamp(sourceFile, timestamp); + + // Then + assertThat(sourceFile.lastModified()).isEqualTo(timestamp); + } + + @Test + void updateLastModifiedTimestamp_whenInvokedOnNonExistentFile_shouldThrowException() { + // Given + File sourceFile = new File(folder, "source"); + long timestamp = new Date().getTime() - 10; + + // When + Then + assertThatIllegalStateException() + .isThrownBy(() -> FileUtil.updateLastModifiedTimestamp(sourceFile, timestamp)) + .withMessage("Not able to update last modified timestamp of " + sourceFile.getPath()); + } + private void prepareDirectory() throws IOException { final File dir1 = Files.createDirectories(folder.toPath().resolve("foo")).toFile(); assertThat(new File(dir1, "fileInFoo1").createNewFile()).isTrue();