diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ImageTarball.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ImageTarball.java index 750a2dcd11..b87f3a52e7 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ImageTarball.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ImageTarball.java @@ -31,6 +31,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.Collections; /** Translates an {@link Image} to a tarball that can be loaded into Docker. */ @@ -45,6 +46,9 @@ public class ImageTarball { /** File name extension for the layer content files. */ private static final String LAYER_FILE_EXTENSION = ".tar.gz"; + /** Time that entry is set in the tar. */ + private static final Instant TAR_ENTRY_MODIFICATION_TIME = Instant.EPOCH; + private final Image image; private final ImageReference imageReference; private final ImmutableSet allTargetImageTags; @@ -88,7 +92,8 @@ private void ociWriteTo(OutputStream out) throws IOException { DescriptorDigest digest = layer.getBlobDescriptor().getDigest(); long size = layer.getBlobDescriptor().getSize(); - tarStreamBuilder.addBlobEntry(layer.getBlob(), size, "blobs/sha256/" + digest.getHash()); + tarStreamBuilder.addBlobEntry( + layer.getBlob(), size, "blobs/sha256/" + digest.getHash(), TAR_ENTRY_MODIFICATION_TIME); manifest.addLayer(size, digest); } @@ -99,21 +104,26 @@ private void ociWriteTo(OutputStream out) throws IOException { manifest.setContainerConfiguration(configDescriptor.getSize(), configDescriptor.getDigest()); tarStreamBuilder.addByteEntry( JsonTemplateMapper.toByteArray(containerConfiguration), - "blobs/sha256/" + configDescriptor.getDigest().getHash()); + "blobs/sha256/" + configDescriptor.getDigest().getHash(), + TAR_ENTRY_MODIFICATION_TIME); // Adds the manifest to the tarball BlobDescriptor manifestDescriptor = Digests.computeDigest(manifest); tarStreamBuilder.addByteEntry( JsonTemplateMapper.toByteArray(manifest), - "blobs/sha256/" + manifestDescriptor.getDigest().getHash()); + "blobs/sha256/" + manifestDescriptor.getDigest().getHash(), + TAR_ENTRY_MODIFICATION_TIME); // Adds the oci-layout and index.json tarStreamBuilder.addByteEntry( - "{\"imageLayoutVersion\": \"1.0.0\"}".getBytes(StandardCharsets.UTF_8), "oci-layout"); + "{\"imageLayoutVersion\": \"1.0.0\"}".getBytes(StandardCharsets.UTF_8), + "oci-layout", + TAR_ENTRY_MODIFICATION_TIME); OciIndexTemplate index = new OciIndexTemplate(); // TODO: figure out how to tag with allTargetImageTags index.addManifest(manifestDescriptor, imageReference.toStringWithQualifier()); - tarStreamBuilder.addByteEntry(JsonTemplateMapper.toByteArray(index), "index.json"); + tarStreamBuilder.addByteEntry( + JsonTemplateMapper.toByteArray(index), "index.json", TAR_ENTRY_MODIFICATION_TIME); tarStreamBuilder.writeAsTarArchiveTo(out); } @@ -127,7 +137,10 @@ private void dockerWriteTo(OutputStream out) throws IOException { String layerName = layer.getBlobDescriptor().getDigest().getHash() + LAYER_FILE_EXTENSION; tarStreamBuilder.addBlobEntry( - layer.getBlob(), layer.getBlobDescriptor().getSize(), layerName); + layer.getBlob(), + layer.getBlobDescriptor().getSize(), + layerName, + TAR_ENTRY_MODIFICATION_TIME); manifestTemplate.addLayerFile(layerName); } @@ -136,7 +149,8 @@ private void dockerWriteTo(OutputStream out) throws IOException { new ImageToJsonTranslator(image).getContainerConfiguration(); tarStreamBuilder.addByteEntry( JsonTemplateMapper.toByteArray(containerConfiguration), - CONTAINER_CONFIGURATION_JSON_FILE_NAME); + CONTAINER_CONFIGURATION_JSON_FILE_NAME, + TAR_ENTRY_MODIFICATION_TIME); // Adds the manifest to tarball. for (String tag : allTargetImageTags) { @@ -144,7 +158,8 @@ private void dockerWriteTo(OutputStream out) throws IOException { } tarStreamBuilder.addByteEntry( JsonTemplateMapper.toByteArray(Collections.singletonList(manifestTemplate)), - MANIFEST_JSON_FILE_NAME); + MANIFEST_JSON_FILE_NAME, + TAR_ENTRY_MODIFICATION_TIME); tarStreamBuilder.writeAsTarArchiveTo(out); } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarStreamBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarStreamBuilder.java index 006963ae02..cccead8a8b 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarStreamBuilder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarStreamBuilder.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; @@ -71,10 +72,12 @@ public void addTarArchiveEntry(TarArchiveEntry entry) { * * @param contents the bytes to add to the tarball * @param name the name of the entry (i.e. filename) + * @param modificationTime the modification time of the entry */ - public void addByteEntry(byte[] contents, String name) { + public void addByteEntry(byte[] contents, String name, Instant modificationTime) { TarArchiveEntry entry = new TarArchiveEntry(name); entry.setSize(contents.length); + entry.setModTime(modificationTime.toEpochMilli()); archiveMap.put(entry, Blobs.from(outputStream -> outputStream.write(contents))); } @@ -85,10 +88,12 @@ public void addByteEntry(byte[] contents, String name) { * @param blob the {@link Blob} to add to the tarball * @param size the size (in bytes) of {@code blob} * @param name the name of the entry (i.e. filename) + * @param modificationTime the modification time of the entry */ - public void addBlobEntry(Blob blob, long size, String name) { + public void addBlobEntry(Blob blob, long size, String name, Instant modificationTime) { TarArchiveEntry entry = new TarArchiveEntry(name); entry.setSize(size); + entry.setModTime(modificationTime.toEpochMilli()); archiveMap.put(entry, blob); } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarStreamBuilderTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarStreamBuilderTest.java index 6555c69c70..6aa2f9f2cd 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarStreamBuilderTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarStreamBuilderTest.java @@ -16,6 +16,8 @@ package com.google.cloud.tools.jib.tar; +import static java.time.temporal.ChronoUnit.SECONDS; + import com.google.cloud.tools.jib.blob.Blobs; import com.google.common.io.ByteStreams; import com.google.common.io.Resources; @@ -29,6 +31,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Instant; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; @@ -96,10 +99,15 @@ public void testToBlob_stringsAndTarArchiveEntriesWithCompression() throws IOExc @Test public void testToBlob_multiByte() throws IOException { - testTarStreamBuilder.addByteEntry("日本語".getBytes(StandardCharsets.UTF_8), "test"); - testTarStreamBuilder.addByteEntry("asdf".getBytes(StandardCharsets.UTF_8), "crepecake"); + Instant modificationTime = Instant.ofEpochMilli(1618041179516L); + Instant timeFromTarArchiveEntry = modificationTime.truncatedTo(SECONDS); + + testTarStreamBuilder.addByteEntry( + "日本語".getBytes(StandardCharsets.UTF_8), "test", modificationTime); + testTarStreamBuilder.addByteEntry( + "asdf".getBytes(StandardCharsets.UTF_8), "crepecake", modificationTime); testTarStreamBuilder.addBlobEntry( - Blobs.from("jib"), "jib".getBytes(StandardCharsets.UTF_8).length, "jib"); + Blobs.from("jib"), "jib".getBytes(StandardCharsets.UTF_8).length, "jib", modificationTime); // Writes the BLOB and captures the output. ByteArrayOutputStream tarByteOutputStream = new ByteArrayOutputStream(); @@ -122,11 +130,13 @@ public void testToBlob_multiByte() throws IOException { Assert.assertEquals("crepecake", headerFile.getName()); Assert.assertEquals( "asdf", new String(ByteStreams.toByteArray(tarArchiveInputStream), StandardCharsets.UTF_8)); + Assert.assertEquals(timeFromTarArchiveEntry, headerFile.getModTime().toInstant()); headerFile = tarArchiveInputStream.getNextTarEntry(); Assert.assertEquals("jib", headerFile.getName()); Assert.assertEquals( "jib", new String(ByteStreams.toByteArray(tarArchiveInputStream), StandardCharsets.UTF_8)); + Assert.assertEquals(timeFromTarArchiveEntry, headerFile.getModTime().toInstant()); Assert.assertNull(tarArchiveInputStream.getNextTarEntry()); } @@ -148,25 +158,27 @@ private void setUpWithTarEntries() { /** Creates a TarStreamBuilder using Strings. */ private void setUpWithStrings() { // Prepares a test TarStreamBuilder. - testTarStreamBuilder.addByteEntry(fileAContents, "some/path/to/resourceFileA"); - testTarStreamBuilder.addByteEntry(fileBContents, "crepecake"); + testTarStreamBuilder.addByteEntry(fileAContents, "some/path/to/resourceFileA", Instant.EPOCH); + testTarStreamBuilder.addByteEntry(fileBContents, "crepecake", Instant.EPOCH); testTarStreamBuilder.addTarArchiveEntry( new TarArchiveEntry(directoryA.toFile(), "some/path/to")); testTarStreamBuilder.addByteEntry( fileAContents, - "some/really/long/path/that/exceeds/100/characters/abcdefghijklmnopqrstuvwxyz0123456789012345678901234567890"); + "some/really/long/path/that/exceeds/100/characters/abcdefghijklmnopqrstuvwxyz0123456789012345678901234567890", + Instant.EPOCH); } /** Creates a TarStreamBuilder using Strings and TarArchiveEntries. */ private void setUpWithStringsAndTarEntries() { // Prepares a test TarStreamBuilder. - testTarStreamBuilder.addByteEntry(fileAContents, "some/path/to/resourceFileA"); + testTarStreamBuilder.addByteEntry(fileAContents, "some/path/to/resourceFileA", Instant.EPOCH); testTarStreamBuilder.addTarArchiveEntry(new TarArchiveEntry(fileB.toFile(), "crepecake")); testTarStreamBuilder.addTarArchiveEntry( new TarArchiveEntry(directoryA.toFile(), "some/path/to")); testTarStreamBuilder.addByteEntry( fileAContents, - "some/really/long/path/that/exceeds/100/characters/abcdefghijklmnopqrstuvwxyz0123456789012345678901234567890"); + "some/really/long/path/that/exceeds/100/characters/abcdefghijklmnopqrstuvwxyz0123456789012345678901234567890", + Instant.EPOCH); } /** Creates a compressed blob from the TarStreamBuilder and verifies it. */