From 2f19840f17383a6a32d7d746f7707c13210c5745 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 01/16] 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 8d5beddddccd5d7f88a4f0edf3c58e2a161cb52e 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 02/16] 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 3180eda05865806b6155bb89e21a44ee45e36282 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 03/16] 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 db224ae782586531f0181bb7c4138c6162fb056d 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 04/16] 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 a352e300d4d8da4df5e8bfec7ea6db6ef0a1a371 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 05/16] 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 08944211da39fcdbc10140e132d771abfd96aa03 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 06/16] 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 85c9dd17d75726ea142b444435c7a21eb349b708 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 07/16] 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 028db50c727b9894e764241416e50eea46600408 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 08/16] 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); } From 909bfcd91f4e433a88193942242bc5ba421008b3 Mon Sep 17 00:00:00 2001 From: izogfif Date: Sat, 2 Mar 2024 14:49:06 +0000 Subject: [PATCH 09/16] File modification time set by JIB (and possibly overridden by user) is now used for PAX headers. Some parts of test were reverted. Code reformat. --- .../jib/image/ReproducibleLayerBuilder.java | 26 ++++-- .../image/ReproducibleLayerBuilderTest.java | 84 +++++++++++-------- 2 files changed, 68 insertions(+), 42 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 1185a5de72..9474cedeec 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 @@ -27,6 +27,7 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Instant; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; @@ -80,7 +81,10 @@ private void add(TarArchiveEntry tarArchiveEntry) throws IOException { dir.setGroupId(0); dir.setUserName(""); dir.setGroupName(""); - clearTimeHeaders(dir); // DEFAULT_MODIFICATION_TIME == EPOCH+1 + // In this particular place we have to use default modification time (1 second past epoch) + // instead the modification time user might've set in JIB settings. That's because we have + // no way to find out the modification time user might've set in JIB settings here. + clearTimeHeaders(dir, FileEntriesLayer.DEFAULT_MODIFICATION_TIME); add(dir); } @@ -95,12 +99,18 @@ private List getSortedEntries() { } } - 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"); + private static void clearTimeHeaders(TarArchiveEntry entry, Instant modTime) { + entry.setModTime(modTime.toEpochMilli()); + + String headerTime = Long.toString(modTime.getEpochSecond()); + final long nanos = modTime.getNano(); + if (nanos > 0) { + headerTime += "." + nanos; + } + entry.addPaxHeader("mtime", headerTime); + entry.addPaxHeader("atime", headerTime); + entry.addPaxHeader("ctime", headerTime); + entry.addPaxHeader("LIBARCHIVE.creationtime", headerTime); } private static void setUserAndGroup(TarArchiveEntry entry, FileEntry layerEntry) { @@ -165,7 +175,7 @@ public Blob build() throws IOException { // lowest 9 bits) then using a bitwise OR to set them to the layerEntry's permissions. entry.setMode((entry.getMode() & ~0777) | layerEntry.getPermissions().getPermissionBits()); setUserAndGroup(entry, layerEntry); - clearTimeHeaders(entry); + clearTimeHeaders(entry, layerEntry.getModificationTime()); 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 5770ef9680..98e8a68f6c 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,27 +91,37 @@ private static void verifyNextTarArchiveEntryIsDirectory( * @param entry The archive entry in the layer. */ private static void verifyThatModificationTimeIsReset(ArchiveEntry entry) { - assertThat(entry.getLastModifiedDate().toInstant()).isEqualTo(FileEntriesLayer.DEFAULT_MODIFICATION_TIME); + assertThat(entry.getLastModifiedDate().toInstant()) + .isEqualTo(FileEntriesLayer.DEFAULT_MODIFICATION_TIME); } - private static FileEntry layerEntry(Path source, AbsoluteUnixPath destination, FilePermissions permissions, String ownership) throws IOException { + private static FileEntry layerEntry( + Path source, AbsoluteUnixPath destination, FilePermissions permissions, String ownership) + throws IOException { return new FileEntry( source, destination, 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 - ); + // Here we make sure to use a default modification time + // since JIB will set modification time for all files to + // the one set in JIB settings by a user. In case user + // hasn't set it, JIB will use default modification time, + // i.e. 1 second past January 1st, 1970 (1 second past epoch). + FileEntriesLayer.DEFAULT_MODIFICATION_TIME, + ownership); } - private static FileEntry layerEntry(Path source, AbsoluteUnixPath destination, FilePermissions permissions) throws IOException { + 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)); + 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(); @@ -217,7 +227,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( @@ -258,7 +268,7 @@ public void testBuild_parentDirBehavior() throws IOException { // root (default folder permissions) TarArchiveEntry root = in.getNextTarEntry(); assertThat(root.getMode()).isEqualTo(040755); - verifyThatModificationTimeIsReset(root); + assertThat(root.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(1)); assertThat(root.getLongUserId()).isEqualTo(0); assertThat(root.getLongGroupId()).isEqualTo(0); assertThat(root.getUserName()).isEmpty(); @@ -267,7 +277,7 @@ public void testBuild_parentDirBehavior() throws IOException { // parentAAA (custom permissions, custom timestamp) TarArchiveEntry rootParentA = in.getNextTarEntry(); assertThat(rootParentA.getMode()).isEqualTo(040111); - verifyThatModificationTimeIsReset(root); + assertThat(rootParentA.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(10)); assertThat(rootParentA.getLongUserId()).isEqualTo(0); assertThat(rootParentA.getLongGroupId()).isEqualTo(0); assertThat(rootParentA.getUserName()).isEmpty(); @@ -280,7 +290,8 @@ public void testBuild_parentDirBehavior() throws IOException { TarArchiveEntry rootParentB = in.getNextTarEntry(); // TODO (#1650): we want 040444 here. assertThat(rootParentB.getMode()).isEqualTo(040755); - verifyThatModificationTimeIsReset(root); + // TODO (#1650): we want Instant.ofEpochSecond(40) here. + assertThat(rootParentB.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(1)); assertThat(rootParentB.getLongUserId()).isEqualTo(0); assertThat(rootParentB.getLongGroupId()).isEqualTo(0); assertThat(rootParentB.getUserName()).isEmpty(); @@ -292,7 +303,7 @@ public void testBuild_parentDirBehavior() throws IOException { // parentCCC (default permissions - no entry provided) TarArchiveEntry rootParentC = in.getNextTarEntry(); assertThat(rootParentC.getMode()).isEqualTo(040755); - verifyThatModificationTimeIsReset(root); + assertThat(rootParentC.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(1)); assertThat(rootParentC.getLongUserId()).isEqualTo(0); assertThat(rootParentC.getLongGroupId()).isEqualTo(0); assertThat(rootParentC.getUserName()).isEmpty(); @@ -324,23 +335,22 @@ public void testBuild_timestampDefault() throws IOException { @Test public void testBuild_timestampNonDefault() throws IOException { - 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()); - } + 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 @@ -354,8 +364,14 @@ public void testBuild_permissions() throws IOException { new ReproducibleLayerBuilder( ImmutableList.of( layerEntry(fileA, AbsoluteUnixPath.get("/somewhere/fileA")), - layerEntry(fileB, AbsoluteUnixPath.get("/somewhere/fileB"), FilePermissions.fromOctalString("123")), - layerEntry(folder, AbsoluteUnixPath.get("/somewhere/folder"), FilePermissions.fromOctalString("456")))) + layerEntry( + fileB, + AbsoluteUnixPath.get("/somewhere/fileB"), + FilePermissions.fromOctalString("123")), + layerEntry( + folder, + AbsoluteUnixPath.get("/somewhere/folder"), + FilePermissions.fromOctalString("456")))) .build(); Path tarFile = temporaryFolder.newFile().toPath(); From b885461b376076fc75a500e5a15873629b9bd149 Mon Sep 17 00:00:00 2001 From: izogfif Date: Sun, 3 Mar 2024 02:40:28 +0000 Subject: [PATCH 10/16] Bump org.apache.commons:commons-compress from 1.21 to 1.26.0. Wrong comment was removed. ReproducibleLayerBuilderTest.java was reverted to its original state. --- build.gradle | 2 +- .../jib/image/ReproducibleLayerBuilder.java | 3 - .../image/ReproducibleLayerBuilderTest.java | 108 +++++++----------- 3 files changed, 43 insertions(+), 70 deletions(-) diff --git a/build.gradle b/build.gradle index a8ec1fe552..a1834ed8b0 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ project.ext.dependencyStrings = [ GRADLE_EXTENSION: 'com.google.cloud.tools:jib-gradle-plugin-extension-api:0.4.0', MAVEN_EXTENSION: 'com.google.cloud.tools:jib-maven-plugin-extension-api:0.4.0', - COMMONS_COMPRESS: 'org.apache.commons:commons-compress:1.21', + COMMONS_COMPRESS: 'org.apache.commons:commons-compress:1.26.0', ZSTD_JNI: 'com.github.luben:zstd-jni:1.5.5-5', COMMONS_TEXT: 'org.apache.commons:commons-text:1.10.0', JACKSON_BOM: 'com.fasterxml.jackson:jackson-bom:2.15.2', 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 9474cedeec..ea7ea5a943 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,9 +81,6 @@ private void add(TarArchiveEntry tarArchiveEntry) throws IOException { dir.setGroupId(0); dir.setUserName(""); dir.setGroupName(""); - // In this particular place we have to use default modification time (1 second past epoch) - // instead the modification time user might've set in JIB settings. That's because we have - // no way to find out the modification time user might've set in JIB settings here. clearTimeHeaders(dir, FileEntriesLayer.DEFAULT_MODIFICATION_TIME); add(dir); } 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 98e8a68f6c..d3860184ae 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,7 +38,6 @@ 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; @@ -85,43 +84,12 @@ private static void verifyNextTarArchiveEntryIsDirectory( assertThat(extractionPathEntry.getMode()).isEqualTo(TarArchiveEntry.DEFAULT_DIR_MODE); } - /** - * 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(FileEntriesLayer.DEFAULT_MODIFICATION_TIME); - } - - private static FileEntry layerEntry( - Path source, AbsoluteUnixPath destination, FilePermissions permissions, String ownership) - throws IOException { + private static FileEntry defaultLayerEntry(Path source, AbsoluteUnixPath destination) { return new FileEntry( source, destination, - permissions, - // Here we make sure to use a default modification time - // since JIB will set modification time for all files to - // the one set in JIB settings by a user. In case user - // hasn't set it, JIB will use default modification time, - // i.e. 1 second past January 1st, 1970 (1 second past epoch). - FileEntriesLayer.DEFAULT_MODIFICATION_TIME, - 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)); + FileEntriesLayer.DEFAULT_FILE_PERMISSIONS_PROVIDER.get(source, destination), + FileEntriesLayer.DEFAULT_MODIFICATION_TIME); } @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -201,14 +169,14 @@ public void testToBlob_reproducibility() throws IOException { Blob layer = new ReproducibleLayerBuilder( ImmutableList.of( - layerEntry(fileA1, AbsoluteUnixPath.get("/somewhere/fileA")), - layerEntry(fileB1, AbsoluteUnixPath.get("/somewhere/fileB")))) + defaultLayerEntry(fileA1, AbsoluteUnixPath.get("/somewhere/fileA")), + defaultLayerEntry(fileB1, AbsoluteUnixPath.get("/somewhere/fileB")))) .build(); Blob reproduced = new ReproducibleLayerBuilder( ImmutableList.of( - layerEntry(fileB2, AbsoluteUnixPath.get("/somewhere/fileB")), - layerEntry(fileA2, AbsoluteUnixPath.get("/somewhere/fileA")))) + defaultLayerEntry(fileB2, AbsoluteUnixPath.get("/somewhere/fileB")), + defaultLayerEntry(fileA2, AbsoluteUnixPath.get("/somewhere/fileA")))) .build(); byte[] layerContent = Blobs.writeToByteArray(layer); @@ -319,7 +287,7 @@ public void testBuild_timestampDefault() throws IOException { Blob blob = new ReproducibleLayerBuilder( - ImmutableList.of(layerEntry(file, AbsoluteUnixPath.get("/fileA")))) + ImmutableList.of(defaultLayerEntry(file, AbsoluteUnixPath.get("/fileA")))) .build(); Path tarFile = temporaryFolder.newFile().toPath(); @@ -329,7 +297,8 @@ public void testBuild_timestampDefault() throws IOException { // Reads the file back. try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(tarFile))) { - verifyThatModificationTimeIsReset(in.getNextEntry()); + assertThat(in.getNextEntry().getLastModifiedDate().toInstant()) + .isEqualTo(Instant.EPOCH.plusSeconds(1)); } } @@ -339,7 +308,12 @@ public void testBuild_timestampNonDefault() throws IOException { Blob blob = new ReproducibleLayerBuilder( - ImmutableList.of(layerEntry(file, AbsoluteUnixPath.get("/fileA")))) + ImmutableList.of( + new FileEntry( + file, + AbsoluteUnixPath.get("/fileA"), + FilePermissions.DEFAULT_FILE_PERMISSIONS, + Instant.ofEpochSecond(123)))) .build(); Path tarFile = temporaryFolder.newFile().toPath(); @@ -349,7 +323,8 @@ public void testBuild_timestampNonDefault() throws IOException { // Reads the file back. try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(tarFile))) { - verifyThatModificationTimeIsReset(in.getNextTarEntry()); + assertThat(in.getNextEntry().getLastModifiedDate().toInstant()) + .isEqualTo(Instant.EPOCH.plusSeconds(123)); } } @@ -363,15 +338,17 @@ public void testBuild_permissions() throws IOException { Blob blob = new ReproducibleLayerBuilder( ImmutableList.of( - layerEntry(fileA, AbsoluteUnixPath.get("/somewhere/fileA")), - layerEntry( + defaultLayerEntry(fileA, AbsoluteUnixPath.get("/somewhere/fileA")), + new FileEntry( fileB, AbsoluteUnixPath.get("/somewhere/fileB"), - FilePermissions.fromOctalString("123")), - layerEntry( + FilePermissions.fromOctalString("123"), + FileEntriesLayer.DEFAULT_MODIFICATION_TIME), + new FileEntry( folder, AbsoluteUnixPath.get("/somewhere/folder"), - FilePermissions.fromOctalString("456")))) + FilePermissions.fromOctalString("456"), + FileEntriesLayer.DEFAULT_MODIFICATION_TIME))) .build(); Path tarFile = temporaryFolder.newFile().toPath(); @@ -399,46 +376,54 @@ public void testBuild_ownership() throws IOException { Blob blob = new ReproducibleLayerBuilder( ImmutableList.of( - layerEntry(someFile, AbsoluteUnixPath.get("/file1")), - layerEntry( + defaultLayerEntry(someFile, AbsoluteUnixPath.get("/file1")), + new FileEntry( someFile, AbsoluteUnixPath.get("/file2"), FilePermissions.fromOctalString("123"), + Instant.EPOCH, ""), - layerEntry( + new FileEntry( someFile, AbsoluteUnixPath.get("/file3"), FilePermissions.fromOctalString("123"), + Instant.EPOCH, ":"), - layerEntry( + new FileEntry( someFile, AbsoluteUnixPath.get("/file4"), FilePermissions.fromOctalString("123"), + Instant.EPOCH, "333:"), - layerEntry( + new FileEntry( someFile, AbsoluteUnixPath.get("/file5"), FilePermissions.fromOctalString("123"), + Instant.EPOCH, ":555"), - layerEntry( + new FileEntry( someFile, AbsoluteUnixPath.get("/file6"), FilePermissions.fromOctalString("123"), + Instant.EPOCH, "333:555"), - layerEntry( + new FileEntry( someFile, AbsoluteUnixPath.get("/file7"), FilePermissions.fromOctalString("123"), + Instant.EPOCH, "user:"), - layerEntry( + new FileEntry( someFile, AbsoluteUnixPath.get("/file8"), FilePermissions.fromOctalString("123"), + Instant.EPOCH, ":group"), - layerEntry( + new FileEntry( someFile, AbsoluteUnixPath.get("/file9"), FilePermissions.fromOctalString("123"), + Instant.EPOCH, "user:group"))) .build(); @@ -453,63 +438,54 @@ 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 6b22b687a3216767014100eda2b48e61de6c9871 Mon Sep 17 00:00:00 2001 From: izogfif Date: Sun, 3 Mar 2024 03:01:40 +0000 Subject: [PATCH 11/16] TarExtractorTest fixes: modification time from tar archive is now retrieved with higher precision. --- .../cloud/tools/jib/tar/TarExtractorTest.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java index 9f82420b39..622549b3c8 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java @@ -27,6 +27,8 @@ import java.nio.file.Paths; import java.nio.file.attribute.FileTime; import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.Assert; @@ -92,16 +94,20 @@ public void testExtract_modificationTimePreserved() throws URISyntaxException, I TarExtractor.extract(source, destination); + // We have to use millisecond precision here. Taken from Instant.parse. + final DateTimeFormatter parser = + new DateTimeFormatterBuilder().parseCaseInsensitive().appendInstant(3).toFormatter(); + assertThat(Files.getLastModifiedTime(destination.resolve("file A"))) - .isEqualTo(FileTime.from(Instant.parse("2019-08-01T16:13:09Z"))); + .isEqualTo(FileTime.from(parser.parse("2019-08-01T16:13:09.720Z", Instant::from))); assertThat(Files.getLastModifiedTime(destination.resolve("file B"))) - .isEqualTo(FileTime.from(Instant.parse("2019-08-01T16:12:00Z"))); + .isEqualTo(FileTime.from(parser.parse("2019-08-01T16:12:00.797Z", Instant::from))); assertThat(Files.getLastModifiedTime(destination.resolve("folder"))) - .isEqualTo(FileTime.from(Instant.parse("2019-08-01T16:12:33Z"))); + .isEqualTo(FileTime.from(parser.parse("2019-08-01T16:12:33.001Z", Instant::from))); assertThat(Files.getLastModifiedTime(destination.resolve("folder/nested folder"))) - .isEqualTo(FileTime.from(Instant.parse("2019-08-01T16:13:30Z"))); + .isEqualTo(FileTime.from(parser.parse("2019-08-01T16:13:30.404Z", Instant::from))); assertThat(Files.getLastModifiedTime(destination.resolve("folder/nested folder/file C"))) - .isEqualTo(FileTime.from(Instant.parse("2019-08-01T16:12:21Z"))); + .isEqualTo(FileTime.from(parser.parse("2019-08-01T16:12:21.273Z", Instant::from))); } @Test @@ -120,7 +126,7 @@ public void testExtract_reproducibleTimestampsEnabled() throws URISyntaxExceptio assertThat(Files.getLastModifiedTime(destination.resolve("level-1/level-2/level-3"))) .isEqualTo(FileTime.fromMillis(1000L)); assertThat(Files.getLastModifiedTime(destination.resolve("level-1/level-2/level-3/file.txt"))) - .isEqualTo(FileTime.from(Instant.parse("2021-01-29T21:10:02Z"))); + .isEqualTo(FileTime.from(Instant.parse("2021-01-29T21:10:02.78Z"))); } @Test From 2663c800be2dc4e4394794eb26e2c4667f3c8033 Mon Sep 17 00:00:00 2001 From: izogfif Date: Mon, 4 Mar 2024 18:07:29 +0000 Subject: [PATCH 12/16] CHANGELOG files were updated. --- jib-core/CHANGELOG.md | 3 ++- jib-gradle-plugin/CHANGELOG.md | 3 ++- jib-maven-plugin/CHANGELOG.md | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/jib-core/CHANGELOG.md b/jib-core/CHANGELOG.md index 89c67264e8..32b9adcabb 100644 --- a/jib-core/CHANGELOG.md +++ b/jib-core/CHANGELOG.md @@ -7,9 +7,10 @@ All notable changes to this project will be documented in this file. - ### Changed -- +- deps: bump org.apache.commons:commons-compress from 1.21 to 1.26.0 ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) ### Fixed +- fix: image builds should become reproducible once again ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) ## 0.26.0 diff --git a/jib-gradle-plugin/CHANGELOG.md b/jib-gradle-plugin/CHANGELOG.md index abc27188b3..4f4e323ea9 100644 --- a/jib-gradle-plugin/CHANGELOG.md +++ b/jib-gradle-plugin/CHANGELOG.md @@ -7,9 +7,10 @@ All notable changes to this project will be documented in this file. - ### Changed -- +- deps: bump org.apache.commons:commons-compress from 1.21 to 1.26.0 ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) ### Fixed +- fix: image builds should become reproducible once again ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) ## 3.4.1 diff --git a/jib-maven-plugin/CHANGELOG.md b/jib-maven-plugin/CHANGELOG.md index 931d2ada7b..a39c7f1414 100644 --- a/jib-maven-plugin/CHANGELOG.md +++ b/jib-maven-plugin/CHANGELOG.md @@ -7,9 +7,10 @@ All notable changes to this project will be documented in this file. - ### Changed -- +- deps: bump org.apache.commons:commons-compress from 1.21 to 1.26.0 ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) ### Fixed +- fix: image builds should become reproducible once again ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) ## 3.4.1 From b979655de8d7b45594adbb43d596b78df5c40de2 Mon Sep 17 00:00:00 2001 From: izogfif Date: Thu, 7 Mar 2024 08:42:07 +0000 Subject: [PATCH 13/16] Tests should now accommodate file systems incapable of storing file modification time with millisecond precision. --- .../cloud/tools/jib/tar/TarExtractorTest.java | 70 ++++++++++++------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java index 622549b3c8..2bff23dbdc 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java @@ -27,8 +27,7 @@ import java.nio.file.Paths; import java.nio.file.attribute.FileTime; import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoUnit; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.Assert; @@ -94,20 +93,31 @@ public void testExtract_modificationTimePreserved() throws URISyntaxException, I TarExtractor.extract(source, destination); - // We have to use millisecond precision here. Taken from Instant.parse. - final DateTimeFormatter parser = - new DateTimeFormatterBuilder().parseCaseInsensitive().appendInstant(3).toFormatter(); - - assertThat(Files.getLastModifiedTime(destination.resolve("file A"))) - .isEqualTo(FileTime.from(parser.parse("2019-08-01T16:13:09.720Z", Instant::from))); - assertThat(Files.getLastModifiedTime(destination.resolve("file B"))) - .isEqualTo(FileTime.from(parser.parse("2019-08-01T16:12:00.797Z", Instant::from))); - assertThat(Files.getLastModifiedTime(destination.resolve("folder"))) - .isEqualTo(FileTime.from(parser.parse("2019-08-01T16:12:33.001Z", Instant::from))); - assertThat(Files.getLastModifiedTime(destination.resolve("folder/nested folder"))) - .isEqualTo(FileTime.from(parser.parse("2019-08-01T16:13:30.404Z", Instant::from))); - assertThat(Files.getLastModifiedTime(destination.resolve("folder/nested folder/file C"))) - .isEqualTo(FileTime.from(parser.parse("2019-08-01T16:12:21.273Z", Instant::from))); + assertThat( + Files.getLastModifiedTime(destination.resolve("file A")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2019-08-01T16:13:09Z")); + assertThat( + Files.getLastModifiedTime(destination.resolve("file B")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2019-08-01T16:12:00Z")); + assertThat( + Files.getLastModifiedTime(destination.resolve("folder")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2019-08-01T16:12:33Z")); + assertThat( + Files.getLastModifiedTime(destination.resolve("folder/nested folder")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2019-08-01T16:13:30Z")); + assertThat( + Files.getLastModifiedTime(destination.resolve("folder/nested folder/file C")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2019-08-01T16:12:21Z")); } @Test @@ -119,14 +129,26 @@ public void testExtract_reproducibleTimestampsEnabled() throws URISyntaxExceptio TarExtractor.extract(source, destination, true); - assertThat(Files.getLastModifiedTime(destination.resolve("level-1"))) - .isEqualTo(FileTime.fromMillis(1000L)); - assertThat(Files.getLastModifiedTime(destination.resolve("level-1/level-2"))) - .isEqualTo(FileTime.fromMillis(1000L)); - assertThat(Files.getLastModifiedTime(destination.resolve("level-1/level-2/level-3"))) - .isEqualTo(FileTime.fromMillis(1000L)); - assertThat(Files.getLastModifiedTime(destination.resolve("level-1/level-2/level-3/file.txt"))) - .isEqualTo(FileTime.from(Instant.parse("2021-01-29T21:10:02.78Z"))); + assertThat( + Files.getLastModifiedTime(destination.resolve("level-1")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(FileTime.fromMillis(1000L).toInstant()); + assertThat( + Files.getLastModifiedTime(destination.resolve("level-1/level-2")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(FileTime.fromMillis(1000L).toInstant()); + assertThat( + Files.getLastModifiedTime(destination.resolve("level-1/level-2/level-3")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(FileTime.fromMillis(1000L).toInstant()); + assertThat( + Files.getLastModifiedTime(destination.resolve("level-1/level-2/level-3/file.txt")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2021-01-29T21:10:02Z")); } @Test From 8256b8adc4d1dad1bc46a51aff763cae5b68d6ba Mon Sep 17 00:00:00 2001 From: izogfif Date: Thu, 7 Mar 2024 09:54:13 +0000 Subject: [PATCH 14/16] Tar hashes and file names were updated. --- .../cloud/tools/jib/api/ReproducibleImageTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java index 1c194b2198..1526d32afa 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java @@ -102,9 +102,9 @@ public void testTarballStructure() throws IOException { assertThat(actual) .containsExactly( - "c46572ef74f58d95e44dd36c1fbdfebd3752e8b56a794a13c11cfed35a1a6e1c.tar.gz", - "6d2763b0f3940d324ea6b55386429e5b173899608abf7d1bff62e25dd2e4dcea.tar.gz", - "530c1954a2b087d0b989895ea56435c9dc739a973f2d2b6cb9bb98e55bbea7ac.tar.gz", + "16d03883198935b4119896dcea0ea14e1bf105b6ac0a35a88820d08bc0263306.tar.gz", + "527db49d4e0c4159346119b4971d59016bfedceed874abab2b510ce433f6b15c.tar.gz", + "98682a867906d9d07cf3c51a4fb9e08e9d5baddd1ca5dc7834f58f434c9cb15c.tar.gz", "config.json", "manifest.json") .inOrder(); @@ -114,7 +114,7 @@ public void testTarballStructure() throws IOException { public void testManifest() throws IOException { String expectedManifest = "[{\"Config\":\"config.json\",\"RepoTags\":[\"jib-core/reproducible:latest\"]," - + "\"Layers\":[\"c46572ef74f58d95e44dd36c1fbdfebd3752e8b56a794a13c11cfed35a1a6e1c.tar.gz\",\"6d2763b0f3940d324ea6b55386429e5b173899608abf7d1bff62e25dd2e4dcea.tar.gz\",\"530c1954a2b087d0b989895ea56435c9dc739a973f2d2b6cb9bb98e55bbea7ac.tar.gz\"]}]"; + + "\"Layers\":[\"98682a867906d9d07cf3c51a4fb9e08e9d5baddd1ca5dc7834f58f434c9cb15c.tar.gz\",\"527db49d4e0c4159346119b4971d59016bfedceed874abab2b510ce433f6b15c.tar.gz\",\"16d03883198935b4119896dcea0ea14e1bf105b6ac0a35a88820d08bc0263306.tar.gz\"]}]"; String generatedManifest = extractFromTarFileAsString(imageTar, "manifest.json"); assertThat(generatedManifest).isEqualTo(expectedManifest); } @@ -125,7 +125,7 @@ public void testConfiguration() throws IOException { "{\"created\":\"1970-01-01T00:00:00Z\",\"architecture\":\"amd64\",\"os\":\"linux\"," + "\"config\":{\"Env\":[],\"Entrypoint\":[\"echo\",\"Hello World\"],\"ExposedPorts\":{},\"Labels\":{},\"Volumes\":{}}," + "\"history\":[{\"created\":\"1970-01-01T00:00:00Z\",\"author\":\"Jib\",\"created_by\":\"jib-core:null\",\"comment\":\"\"},{\"created\":\"1970-01-01T00:00:00Z\",\"author\":\"Jib\",\"created_by\":\"jib-core:null\",\"comment\":\"\"},{\"created\":\"1970-01-01T00:00:00Z\",\"author\":\"Jib\",\"created_by\":\"jib-core:null\",\"comment\":\"\"}]," - + "\"rootfs\":{\"type\":\"layers\",\"diff_ids\":[\"sha256:18e4f44e6d1835bd968339b166057bd17ab7d4cbb56dc7262a5cafea7cf8d405\",\"sha256:13369c34f073f2b9c1fa6431e23d925f1a8eac65b1726c8cc8fcc2596c69b414\",\"sha256:4f92c507112d7880ca0f504ef8272b7fdee107263270125036a260a741565923\"]}}"; + + "\"rootfs\":{\"type\":\"layers\",\"diff_ids\":[\"sha256:2fcc2157bf42c89195676ef6e973a96d7b018c9d30ba89db95e9e0722e1c8ef3\",\"sha256:21f521f3217067d277af37512a08c72281d90fdd02d7174db632c8c3a34403bd\",\"sha256:6beba018395265af5061864b7f4678e831eb2daebb1045487c641fc8b142e319\"]}}"; String generatedConfig = extractFromTarFileAsString(imageTar, "config.json"); assertThat(generatedConfig).isEqualTo(expectedConfig); } From 5af8470fe3e333007c1c7b211a868f116f403bdf Mon Sep 17 00:00:00 2001 From: izogfif Date: Thu, 7 Mar 2024 09:57:02 +0000 Subject: [PATCH 15/16] Typo fix. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a1834ed8b0..32f6f02523 100644 --- a/build.gradle +++ b/build.gradle @@ -371,7 +371,7 @@ subprojects { // sourceProject(Project) accepts a project and adds it as a dependency in a special manner: // 1. force evaluation of the project first // 2. add the project classes as "compileOnly" and make it available to tests in "testImplementation" - // 3. add the project's depedencies as "implementation" + // 3. add the project's dependencies as "implementation" // 4. remove any transitive reference of any sourceProject depenency that may have appeared // 5. add the project's classes to the final jar // Other nice effects (vs shadowJar) From 042bcb1da01046fb9eb3134d6d6c6eb795c4bf66 Mon Sep 17 00:00:00 2001 From: izogfif Date: Thu, 7 Mar 2024 13:54:56 +0000 Subject: [PATCH 16/16] ReproducibleImageTest error fix. --- .../com/google/cloud/tools/jib/api/ReproducibleImageTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java index 1526d32afa..9351974634 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java @@ -102,9 +102,9 @@ public void testTarballStructure() throws IOException { assertThat(actual) .containsExactly( - "16d03883198935b4119896dcea0ea14e1bf105b6ac0a35a88820d08bc0263306.tar.gz", - "527db49d4e0c4159346119b4971d59016bfedceed874abab2b510ce433f6b15c.tar.gz", "98682a867906d9d07cf3c51a4fb9e08e9d5baddd1ca5dc7834f58f434c9cb15c.tar.gz", + "527db49d4e0c4159346119b4971d59016bfedceed874abab2b510ce433f6b15c.tar.gz", + "16d03883198935b4119896dcea0ea14e1bf105b6ac0a35a88820d08bc0263306.tar.gz", "config.json", "manifest.json") .inOrder();