From 095343c13f4707d2d7cf0e523855709196c0c7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Bugge=20Grathwohl?= Date: Fri, 17 Nov 2023 20:53:15 +0100 Subject: [PATCH 1/8] Set access, creation, and last-modified time to zero --- .../cloud/tools/jib/image/ReproducibleLayerBuilder.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java index c258eb2b5f..0bd77bca21 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java @@ -100,6 +100,10 @@ private static void setUserAndGroup(TarArchiveEntry entry, FileEntry layerEntry) entry.setGroupId(0); entry.setUserName(""); entry.setGroupName(""); + entry.addPaxHeader("mtime", "0"); + entry.addPaxHeader("atime", "0"); + entry.addPaxHeader("ctime", "0"); + entry.addPaxHeader("LIBARCHIVE.creationtime", "0"); if (!layerEntry.getOwnership().isEmpty()) { // Parse ":" string. From 6376c32b39fcbfa08a0d34dba9ee8729bdbb819c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Bugge=20Grathwohl?= Date: Fri, 17 Nov 2023 21:47:40 +0100 Subject: [PATCH 2/8] Add small test that ensures that modtime doesn't change layer --- .../tools/jib/image/ReproducibleLayerBuilder.java | 12 ++++++++---- .../jib/image/ReproducibleLayerBuilderTest.java | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java index 0bd77bca21..2a8b2a2abd 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java @@ -95,15 +95,18 @@ private List getSortedEntries() { } } + private static void clearTimeHeaders(TarArchiveEntry entry) { + entry.addPaxHeader("mtime", "0"); + entry.addPaxHeader("atime", "0"); + entry.addPaxHeader("ctime", "0"); + entry.addPaxHeader("LIBARCHIVE.creationtime", "0"); + } + private static void setUserAndGroup(TarArchiveEntry entry, FileEntry layerEntry) { entry.setUserId(0); entry.setGroupId(0); entry.setUserName(""); entry.setGroupName(""); - entry.addPaxHeader("mtime", "0"); - entry.addPaxHeader("atime", "0"); - entry.addPaxHeader("ctime", "0"); - entry.addPaxHeader("LIBARCHIVE.creationtime", "0"); if (!layerEntry.getOwnership().isEmpty()) { // Parse ":" string. @@ -162,6 +165,7 @@ public Blob build() throws IOException { entry.setMode((entry.getMode() & ~0777) | layerEntry.getPermissions().getPermissionBits()); entry.setModTime(layerEntry.getModificationTime().toEpochMilli()); setUserAndGroup(entry, layerEntry); + clearTimeHeaders(entry); uniqueTarArchiveEntries.add(entry); } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java index d3860184ae..9d33811ae7 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java @@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; import com.google.common.io.Resources; + import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -84,12 +85,12 @@ private static void verifyNextTarArchiveEntryIsDirectory( assertThat(extractionPathEntry.getMode()).isEqualTo(TarArchiveEntry.DEFAULT_DIR_MODE); } - private static FileEntry defaultLayerEntry(Path source, AbsoluteUnixPath destination) { + private static FileEntry defaultLayerEntry(Path source, AbsoluteUnixPath destination) throws IOException { return new FileEntry( source, destination, FileEntriesLayer.DEFAULT_FILE_PERMISSIONS_PROVIDER.get(source, destination), - FileEntriesLayer.DEFAULT_MODIFICATION_TIME); + Files.getLastModifiedTime(source).toInstant()); } @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); From 05bd7f8ff0441fbf1dd29487adaf113764ddfe6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Bugge=20Grathwohl?= Date: Fri, 17 Nov 2023 22:00:28 +0100 Subject: [PATCH 3/8] Also clear time-related PAX headers on parent-dir entries --- .../google/cloud/tools/jib/image/ReproducibleLayerBuilder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java index 2a8b2a2abd..8c34461f06 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java @@ -81,6 +81,7 @@ private void add(TarArchiveEntry tarArchiveEntry) throws IOException { dir.setGroupId(0); dir.setUserName(""); dir.setGroupName(""); + clearTimeHeaders(dir); add(dir); } From 9f048d13746be419891a8238bf20a3c0fac94c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Bugge=20Grathwohl?= Date: Wed, 24 Jan 2024 22:01:51 +0100 Subject: [PATCH 4/8] Add blank line for checkstyle Co-authored-by: Mridula <66699525+mpeddada1@users.noreply.github.com> --- .../cloud/tools/jib/image/ReproducibleLayerBuilderTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java index 9d33811ae7..1cb31ce547 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java @@ -27,7 +27,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; import com.google.common.io.Resources; - import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; From 24162ddeca531992fdbf51fdbc6dabb7b44825ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Bugge=20Grathwohl?= Date: Thu, 25 Jan 2024 09:04:02 +0100 Subject: [PATCH 5/8] Fix the modification-time tests --- .../image/ReproducibleLayerBuilderTest.java | 140 +++++++++--------- 1 file changed, 74 insertions(+), 66 deletions(-) diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java index 1cb31ce547..e8d26e0513 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java @@ -38,6 +38,7 @@ import java.nio.file.StandardOpenOption; import java.nio.file.attribute.FileTime; import java.time.Instant; +import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.junit.Rule; @@ -84,12 +85,33 @@ private static void verifyNextTarArchiveEntryIsDirectory( assertThat(extractionPathEntry.getMode()).isEqualTo(TarArchiveEntry.DEFAULT_DIR_MODE); } - private static FileEntry defaultLayerEntry(Path source, AbsoluteUnixPath destination) throws IOException { + /** + * Verifies that the modification time has been reset in the PAX headers. + * + * @param entry The archive entry in the layer. + */ + private static void verifyThatModificationTimeIsReset(ArchiveEntry entry) { + assertThat(entry.getLastModifiedDate().toInstant()).isEqualTo(Instant.EPOCH); + } + + private static FileEntry layerEntry(Path source, AbsoluteUnixPath destination, FilePermissions permissions, String ownership) throws IOException { return new FileEntry( source, destination, - FileEntriesLayer.DEFAULT_FILE_PERMISSIONS_PROVIDER.get(source, destination), - Files.getLastModifiedTime(source).toInstant()); + permissions, + // Here we make sure to use the actual modification-time here because that's what would happen in + // regular use when copying the file from disk into the layer. + Files.getLastModifiedTime(source).toInstant(), + ownership + ); + } + + private static FileEntry layerEntry(Path source, AbsoluteUnixPath destination, FilePermissions permissions) throws IOException { + return layerEntry(source, destination, permissions, ""); + } + + private static FileEntry layerEntry(Path source, AbsoluteUnixPath destination) throws IOException { + return layerEntry(source, destination, FileEntriesLayer.DEFAULT_FILE_PERMISSIONS_PROVIDER.get(source, destination)); } @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -169,14 +191,14 @@ public void testToBlob_reproducibility() throws IOException { Blob layer = new ReproducibleLayerBuilder( ImmutableList.of( - defaultLayerEntry(fileA1, AbsoluteUnixPath.get("/somewhere/fileA")), - defaultLayerEntry(fileB1, AbsoluteUnixPath.get("/somewhere/fileB")))) + layerEntry(fileA1, AbsoluteUnixPath.get("/somewhere/fileA")), + layerEntry(fileB1, AbsoluteUnixPath.get("/somewhere/fileB")))) .build(); Blob reproduced = new ReproducibleLayerBuilder( ImmutableList.of( - defaultLayerEntry(fileB2, AbsoluteUnixPath.get("/somewhere/fileB")), - defaultLayerEntry(fileA2, AbsoluteUnixPath.get("/somewhere/fileA")))) + layerEntry(fileB2, AbsoluteUnixPath.get("/somewhere/fileB")), + layerEntry(fileA2, AbsoluteUnixPath.get("/somewhere/fileA")))) .build(); byte[] layerContent = Blobs.writeToByteArray(layer); @@ -195,7 +217,7 @@ public void testBuild_parentDirBehavior() throws IOException { Path ignoredParent = Files.createDirectories(testRoot.resolve("dirB-ignored")); Path fileB = Files.createFile(ignoredParent.resolve("fileB")); Path fileC = - Files.createFile(Files.createDirectories(testRoot.resolve("dirC-absent")).resolve("fileC")); + Files.createFile(Files.createDirectories(testRoot.resolve("dirC-absent")).resolve("fileC")); Blob layer = new ReproducibleLayerBuilder( @@ -236,7 +258,7 @@ public void testBuild_parentDirBehavior() throws IOException { // root (default folder permissions) TarArchiveEntry root = in.getNextTarEntry(); assertThat(root.getMode()).isEqualTo(040755); - assertThat(root.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(1)); + verifyThatModificationTimeIsReset(root); assertThat(root.getLongUserId()).isEqualTo(0); assertThat(root.getLongGroupId()).isEqualTo(0); assertThat(root.getUserName()).isEmpty(); @@ -245,7 +267,7 @@ public void testBuild_parentDirBehavior() throws IOException { // parentAAA (custom permissions, custom timestamp) TarArchiveEntry rootParentA = in.getNextTarEntry(); assertThat(rootParentA.getMode()).isEqualTo(040111); - assertThat(rootParentA.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(10)); + verifyThatModificationTimeIsReset(root); assertThat(rootParentA.getLongUserId()).isEqualTo(0); assertThat(rootParentA.getLongGroupId()).isEqualTo(0); assertThat(rootParentA.getUserName()).isEmpty(); @@ -258,8 +280,7 @@ public void testBuild_parentDirBehavior() throws IOException { TarArchiveEntry rootParentB = in.getNextTarEntry(); // TODO (#1650): we want 040444 here. assertThat(rootParentB.getMode()).isEqualTo(040755); - // TODO (#1650): we want Instant.ofEpochSecond(40) here. - assertThat(rootParentB.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(1)); + verifyThatModificationTimeIsReset(root); assertThat(rootParentB.getLongUserId()).isEqualTo(0); assertThat(rootParentB.getLongGroupId()).isEqualTo(0); assertThat(rootParentB.getUserName()).isEmpty(); @@ -271,7 +292,7 @@ public void testBuild_parentDirBehavior() throws IOException { // parentCCC (default permissions - no entry provided) TarArchiveEntry rootParentC = in.getNextTarEntry(); assertThat(rootParentC.getMode()).isEqualTo(040755); - assertThat(rootParentC.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(1)); + verifyThatModificationTimeIsReset(root); assertThat(rootParentC.getLongUserId()).isEqualTo(0); assertThat(rootParentC.getLongGroupId()).isEqualTo(0); assertThat(rootParentC.getUserName()).isEmpty(); @@ -287,7 +308,7 @@ public void testBuild_timestampDefault() throws IOException { Blob blob = new ReproducibleLayerBuilder( - ImmutableList.of(defaultLayerEntry(file, AbsoluteUnixPath.get("/fileA")))) + ImmutableList.of(layerEntry(file, AbsoluteUnixPath.get("/fileA")))) .build(); Path tarFile = temporaryFolder.newFile().toPath(); @@ -297,35 +318,29 @@ public void testBuild_timestampDefault() throws IOException { // Reads the file back. try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(tarFile))) { - assertThat(in.getNextEntry().getLastModifiedDate().toInstant()) - .isEqualTo(Instant.EPOCH.plusSeconds(1)); + verifyThatModificationTimeIsReset(in.getNextEntry()); } } @Test public void testBuild_timestampNonDefault() throws IOException { - Path file = createFile(temporaryFolder.getRoot().toPath(), "fileA", "some content", 54321); - - Blob blob = - new ReproducibleLayerBuilder( - ImmutableList.of( - new FileEntry( - file, - AbsoluteUnixPath.get("/fileA"), - FilePermissions.DEFAULT_FILE_PERMISSIONS, - Instant.ofEpochSecond(123)))) - .build(); - - Path tarFile = temporaryFolder.newFile().toPath(); - try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(tarFile))) { - blob.writeTo(out); - } - - // Reads the file back. - try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(tarFile))) { - assertThat(in.getNextEntry().getLastModifiedDate().toInstant()) - .isEqualTo(Instant.EPOCH.plusSeconds(123)); - } + Path file = createFile(temporaryFolder.getRoot().toPath(), "fileA", "some content", 54321); + + Blob blob = + new ReproducibleLayerBuilder( + ImmutableList.of( + layerEntry(file, AbsoluteUnixPath.get("/fileA")))) + .build(); + + Path tarFile = temporaryFolder.newFile().toPath(); + try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(tarFile))) { + blob.writeTo(out); + } + + // Reads the file back. + try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(tarFile))) { + verifyThatModificationTimeIsReset(in.getNextTarEntry()); + } } @Test @@ -338,17 +353,9 @@ public void testBuild_permissions() throws IOException { Blob blob = new ReproducibleLayerBuilder( ImmutableList.of( - defaultLayerEntry(fileA, AbsoluteUnixPath.get("/somewhere/fileA")), - new FileEntry( - fileB, - AbsoluteUnixPath.get("/somewhere/fileB"), - FilePermissions.fromOctalString("123"), - FileEntriesLayer.DEFAULT_MODIFICATION_TIME), - new FileEntry( - folder, - AbsoluteUnixPath.get("/somewhere/folder"), - FilePermissions.fromOctalString("456"), - FileEntriesLayer.DEFAULT_MODIFICATION_TIME))) + layerEntry(fileA, AbsoluteUnixPath.get("/somewhere/fileA")), + layerEntry(fileB, AbsoluteUnixPath.get("/somewhere/fileB"), FilePermissions.fromOctalString("123")), + layerEntry(folder, AbsoluteUnixPath.get("/somewhere/folder"), FilePermissions.fromOctalString("456")))) .build(); Path tarFile = temporaryFolder.newFile().toPath(); @@ -376,54 +383,46 @@ public void testBuild_ownership() throws IOException { Blob blob = new ReproducibleLayerBuilder( ImmutableList.of( - defaultLayerEntry(someFile, AbsoluteUnixPath.get("/file1")), - new FileEntry( + layerEntry(someFile, AbsoluteUnixPath.get("/file1")), + layerEntry( someFile, AbsoluteUnixPath.get("/file2"), FilePermissions.fromOctalString("123"), - Instant.EPOCH, ""), - new FileEntry( + layerEntry( someFile, AbsoluteUnixPath.get("/file3"), FilePermissions.fromOctalString("123"), - Instant.EPOCH, ":"), - new FileEntry( + layerEntry( someFile, AbsoluteUnixPath.get("/file4"), FilePermissions.fromOctalString("123"), - Instant.EPOCH, "333:"), - new FileEntry( + layerEntry( someFile, AbsoluteUnixPath.get("/file5"), FilePermissions.fromOctalString("123"), - Instant.EPOCH, ":555"), - new FileEntry( + layerEntry( someFile, AbsoluteUnixPath.get("/file6"), FilePermissions.fromOctalString("123"), - Instant.EPOCH, "333:555"), - new FileEntry( + layerEntry( someFile, AbsoluteUnixPath.get("/file7"), FilePermissions.fromOctalString("123"), - Instant.EPOCH, "user:"), - new FileEntry( + layerEntry( someFile, AbsoluteUnixPath.get("/file8"), FilePermissions.fromOctalString("123"), - Instant.EPOCH, ":group"), - new FileEntry( + layerEntry( someFile, AbsoluteUnixPath.get("/file9"), FilePermissions.fromOctalString("123"), - Instant.EPOCH, "user:group"))) .build(); @@ -438,54 +437,63 @@ public void testBuild_ownership() throws IOException { assertThat(entry1.getLongGroupId()).isEqualTo(0); assertThat(entry1.getUserName()).isEmpty(); assertThat(entry1.getGroupName()).isEmpty(); + verifyThatModificationTimeIsReset(entry1); TarArchiveEntry entry2 = in.getNextTarEntry(); assertThat(entry2.getLongUserId()).isEqualTo(0); assertThat(entry2.getLongGroupId()).isEqualTo(0); assertThat(entry2.getUserName()).isEmpty(); assertThat(entry2.getGroupName()).isEmpty(); + verifyThatModificationTimeIsReset(entry2); TarArchiveEntry entry3 = in.getNextTarEntry(); assertThat(entry3.getLongUserId()).isEqualTo(0); assertThat(entry3.getLongGroupId()).isEqualTo(0); assertThat(entry3.getUserName()).isEmpty(); assertThat(entry3.getGroupName()).isEmpty(); + verifyThatModificationTimeIsReset(entry3); TarArchiveEntry entry4 = in.getNextTarEntry(); assertThat(entry4.getLongUserId()).isEqualTo(333); assertThat(entry4.getLongGroupId()).isEqualTo(0); assertThat(entry4.getUserName()).isEmpty(); assertThat(entry4.getGroupName()).isEmpty(); + verifyThatModificationTimeIsReset(entry4); TarArchiveEntry entry5 = in.getNextTarEntry(); assertThat(entry5.getLongUserId()).isEqualTo(0); assertThat(entry5.getLongGroupId()).isEqualTo(555); assertThat(entry5.getUserName()).isEmpty(); assertThat(entry5.getGroupName()).isEmpty(); + verifyThatModificationTimeIsReset(entry5); TarArchiveEntry entry6 = in.getNextTarEntry(); assertThat(entry6.getLongUserId()).isEqualTo(333); assertThat(entry6.getLongGroupId()).isEqualTo(555); assertThat(entry6.getUserName()).isEmpty(); assertThat(entry6.getGroupName()).isEmpty(); + verifyThatModificationTimeIsReset(entry6); TarArchiveEntry entry7 = in.getNextTarEntry(); assertThat(entry7.getLongUserId()).isEqualTo(0); assertThat(entry7.getLongGroupId()).isEqualTo(0); assertThat(entry7.getUserName()).isEqualTo("user"); assertThat(entry7.getGroupName()).isEmpty(); + verifyThatModificationTimeIsReset(entry7); TarArchiveEntry entry8 = in.getNextTarEntry(); assertThat(entry8.getLongUserId()).isEqualTo(0); assertThat(entry8.getLongGroupId()).isEqualTo(0); assertThat(entry8.getUserName()).isEmpty(); assertThat(entry8.getGroupName()).isEqualTo("group"); + verifyThatModificationTimeIsReset(entry8); TarArchiveEntry entry9 = in.getNextTarEntry(); assertThat(entry9.getLongUserId()).isEqualTo(0); assertThat(entry9.getLongGroupId()).isEqualTo(0); assertThat(entry9.getUserName()).isEqualTo("user"); assertThat(entry9.getGroupName()).isEqualTo("group"); + verifyThatModificationTimeIsReset(entry9); } } From 8175386834d0b1a725fd2eef8e9419278108e48a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Bugge=20Grathwohl?= Date: Thu, 25 Jan 2024 09:14:11 +0100 Subject: [PATCH 6/8] Rename `clearTimeHeaders` to `clearPaxTimeHeaders` --- .../cloud/tools/jib/image/ReproducibleLayerBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java index 8c34461f06..563b3b854f 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java @@ -81,7 +81,7 @@ private void add(TarArchiveEntry tarArchiveEntry) throws IOException { dir.setGroupId(0); dir.setUserName(""); dir.setGroupName(""); - clearTimeHeaders(dir); + clearPaxTimeHeaders(dir); add(dir); } @@ -96,7 +96,7 @@ private List getSortedEntries() { } } - private static void clearTimeHeaders(TarArchiveEntry entry) { + private static void clearPaxTimeHeaders(TarArchiveEntry entry) { entry.addPaxHeader("mtime", "0"); entry.addPaxHeader("atime", "0"); entry.addPaxHeader("ctime", "0"); @@ -166,7 +166,7 @@ public Blob build() throws IOException { entry.setMode((entry.getMode() & ~0777) | layerEntry.getPermissions().getPermissionBits()); entry.setModTime(layerEntry.getModificationTime().toEpochMilli()); setUserAndGroup(entry, layerEntry); - clearTimeHeaders(entry); + clearPaxTimeHeaders(entry); uniqueTarArchiveEntries.add(entry); } From de4ab25f246485581529f8b5c17df18d5666fbee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Bugge=20Grathwohl?= Date: Mon, 29 Jan 2024 09:15:37 +0100 Subject: [PATCH 7/8] Set PAX headers to EPOCH plus 1 --- .../cloud/tools/jib/image/ReproducibleLayerBuilder.java | 8 ++++---- .../tools/jib/image/ReproducibleLayerBuilderTest.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java index 563b3b854f..77c345efc2 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java @@ -97,10 +97,10 @@ private List getSortedEntries() { } private static void clearPaxTimeHeaders(TarArchiveEntry entry) { - entry.addPaxHeader("mtime", "0"); - entry.addPaxHeader("atime", "0"); - entry.addPaxHeader("ctime", "0"); - entry.addPaxHeader("LIBARCHIVE.creationtime", "0"); + entry.addPaxHeader("mtime", "1"); // EPOCH plus 1 second + entry.addPaxHeader("atime", "1"); + entry.addPaxHeader("ctime", "1"); + entry.addPaxHeader("LIBARCHIVE.creationtime", "1"); } private static void setUserAndGroup(TarArchiveEntry entry, FileEntry layerEntry) { diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java index e8d26e0513..5770ef9680 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java @@ -91,7 +91,7 @@ private static void verifyNextTarArchiveEntryIsDirectory( * @param entry The archive entry in the layer. */ private static void verifyThatModificationTimeIsReset(ArchiveEntry entry) { - assertThat(entry.getLastModifiedDate().toInstant()).isEqualTo(Instant.EPOCH); + assertThat(entry.getLastModifiedDate().toInstant()).isEqualTo(FileEntriesLayer.DEFAULT_MODIFICATION_TIME); } private static FileEntry layerEntry(Path source, AbsoluteUnixPath destination, FilePermissions permissions, String ownership) throws IOException { From dc3633bbc0cd179ae24fc495607d7d6fa75454de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Bugge=20Grathwohl?= Date: Mon, 29 Jan 2024 15:27:54 +0100 Subject: [PATCH 8/8] Rename to`clearTimeHeaders` and call `setModTime` in helper --- .../tools/jib/image/ReproducibleLayerBuilder.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java index 77c345efc2..1185a5de72 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java @@ -76,12 +76,11 @@ private void add(TarArchiveEntry tarArchiveEntry) throws IOException { if (namePath.getParent() != namePath.getRoot()) { Path tarArchiveParentDir = Verify.verifyNotNull(namePath.getParent()); TarArchiveEntry dir = new TarArchiveEntry(DIRECTORY_FILE, tarArchiveParentDir.toString()); - dir.setModTime(FileEntriesLayer.DEFAULT_MODIFICATION_TIME.toEpochMilli()); dir.setUserId(0); dir.setGroupId(0); dir.setUserName(""); dir.setGroupName(""); - clearPaxTimeHeaders(dir); + clearTimeHeaders(dir); // DEFAULT_MODIFICATION_TIME == EPOCH+1 add(dir); } @@ -96,8 +95,9 @@ private List getSortedEntries() { } } - private static void clearPaxTimeHeaders(TarArchiveEntry entry) { - entry.addPaxHeader("mtime", "1"); // EPOCH plus 1 second + private static void clearTimeHeaders(TarArchiveEntry entry) { + entry.setModTime(FileEntriesLayer.DEFAULT_MODIFICATION_TIME.toEpochMilli()); + entry.addPaxHeader("mtime", "1"); entry.addPaxHeader("atime", "1"); entry.addPaxHeader("ctime", "1"); entry.addPaxHeader("LIBARCHIVE.creationtime", "1"); @@ -164,9 +164,8 @@ public Blob build() throws IOException { // Sets the entry's permissions by masking out the permission bits from the entry's mode (the // lowest 9 bits) then using a bitwise OR to set them to the layerEntry's permissions. entry.setMode((entry.getMode() & ~0777) | layerEntry.getPermissions().getPermissionBits()); - entry.setModTime(layerEntry.getModificationTime().toEpochMilli()); setUserAndGroup(entry, layerEntry); - clearPaxTimeHeaders(entry); + clearTimeHeaders(entry); uniqueTarArchiveEntries.add(entry); }