Skip to content

Commit

Permalink
#3158 - [Jib core] Tar archives with same contents are not reproducib…
Browse files Browse the repository at this point in the history
…le (#3159)

* #3158 - [Jib core] Tar archives with same contents are not reproducible

* Revert "#3158 - [Jib core] Tar archives with same contents are not reproducible"
This reverts commit 562e2b8

* #3158 - [Jib core] Tar archives with same contents are not reproducible

* Update jib-core/src/main/java/com/google/cloud/tools/jib/image/ImageTarball.java

Co-authored-by: Chanseok Oh <chanseok@google.com>

* Update jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarStreamBuilderTest.java

Co-authored-by: Chanseok Oh <chanseok@google.com>

* Update jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarStreamBuilderTest.java

Co-authored-by: Chanseok Oh <chanseok@google.com>

* Update jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarStreamBuilder.java

Co-authored-by: Chanseok Oh <chanseok@google.com>

* Update jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarStreamBuilder.java

Co-authored-by: Chanseok Oh <chanseok@google.com>

* #3158 - [Jib core] Tar archives with same contents are not reproducible

* #3158 - [Jib core] Tar archives with same contents are not reproducible

* #3158 - [Jib core] Tar archives with same contents are not reproducible

* #3158 - [Jib core] Tar archives with same contents are not reproducible

* #3158 - [Jib core] Tar archives with same contents are not reproducible

* #3158 - [Jib core] Tar archives with same contents are not reproducible

* #3158 - [Jib core] Tar archives with same contents are not reproducible

Co-authored-by: Chanseok Oh <chanseok@google.com>
  • Loading branch information
davidtron and chanseokoh authored Apr 13, 2021
1 parent 652d3b6 commit 367fa66
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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<String> allTargetImageTags;
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}
Expand All @@ -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);
}

Expand All @@ -136,15 +149,17 @@ 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) {
manifestTemplate.addRepoTag(imageReference.withQualifier(tag).toStringWithQualifier());
}
tarStreamBuilder.addByteEntry(
JsonTemplateMapper.toByteArray(Collections.singletonList(manifestTemplate)),
MANIFEST_JSON_FILE_NAME);
MANIFEST_JSON_FILE_NAME,
TAR_ENTRY_MODIFICATION_TIME);

tarStreamBuilder.writeAsTarArchiveTo(out);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)));
}

Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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());
}
Expand All @@ -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. */
Expand Down

0 comments on commit 367fa66

Please sign in to comment.