Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for symbolic links. #3505

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
Expand All @@ -33,6 +34,7 @@
import java.util.List;
import java.util.Set;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarConstants;

/**
* Builds a reproducible layer {@link Blob} from files. The reproducibility is implemented by strips
Expand Down Expand Up @@ -147,12 +149,21 @@ public Blob build() throws IOException {

// Adds all the layer entries as tar entries.
for (FileEntry layerEntry : layerEntries) {
// Adds the entries to uniqueTarArchiveEntries, which makes sure all entries are unique and
// adds parent directories for each extraction path.
TarArchiveEntry entry =
new TarArchiveEntry(
layerEntry.getSourceFile(), layerEntry.getExtractionPath().toString());

TarArchiveEntry entry;
if (Files.isSymbolicLink(layerEntry.getSourceFile())) {
entry =
new TarArchiveEntry(
layerEntry.getExtractionPath().toString(), TarConstants.LF_SYMLINK );
Path targetPath = Files.readSymbolicLink(layerEntry.getSourceFile());
entry.setLinkName(targetPath.toString());
} else {
// Adds the entries to uniqueTarArchiveEntries, which makes sure all entries are unique and
// adds parent directories for each extraction path.
entry =
new TarArchiveEntry(
layerEntry.getSourceFile(), layerEntry.getExtractionPath().toString());
}
// 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void writeAsTarArchiveTo(OutputStream out) throws IOException {
*/
public void addTarArchiveEntry(TarArchiveEntry entry) {
archiveMap.put(
entry, entry.isFile() ? Blobs.from(entry.getPath()) : Blobs.from(ignored -> {}, true));
entry, (entry.isFile() && ! entry.isSymbolicLink()) ? Blobs.from(entry.getPath()) : Blobs.from(ignored -> {}, true));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,45 @@
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

/** Tests for {@link ReproducibleLayerBuilder}. */
public class ReproducibleLayerBuilderTest {



@After
public void cleanUp() throws IOException, URISyntaxException {
removeLinks(getLinks());
}

private List<Path> getLinks() throws URISyntaxException {
List<Path> linksList = new ArrayList<>();
Path blobA = Paths.get(Resources.getResource("core/blobA").toURI());
String resourceDir = blobA.getParent().toString();
Path link1 = Paths.get( resourceDir, "blob-link1");
linksList.add(link1);
Path link2 = Paths.get(resourceDir + "/layer/a/b/", "blob-link2");
linksList.add(link2);
return linksList;
}

private void removeLinks(List<Path> linksList) throws IOException {
for (Path path: linksList) {
if (Files.exists(path)) {
Files.delete(path);
}
}
}

/**
* Verifies the correctness of the next {@link TarArchiveEntry} in the {@link
* TarArchiveInputStream}.
Expand All @@ -67,6 +97,24 @@ private static void verifyNextTarArchiveEntry(
assertThat(extractedBytes).isEqualTo(expectedBytes);
}

/**
* Verifies the correctness of the next {@link TarArchiveEntry} in the {@link
* TarArchiveInputStream}.
*
* @param tarArchiveInputStream the {@link TarArchiveInputStream} to read from
* @param expectedExtractionPath the expected extraction path of the next entry
* @param expectedLinkName the expected link name of the next entry
* @throws IOException if an I/O exception occurs
*/
private static void verifyNextTarArchiveEntryIsLink(
TarArchiveInputStream tarArchiveInputStream, String expectedExtractionPath, String expectedLinkName)
throws IOException {
TarArchiveEntry header = tarArchiveInputStream.getNextTarEntry();
assertThat(header.getName()).isEqualTo(expectedExtractionPath);
assertThat(header.getLinkName()).isEqualTo(expectedLinkName);
assertThat(header.isSymbolicLink()).isTrue();
}

/**
* Verifies that the next {@link TarArchiveEntry} in the {@link TarArchiveInputStream} is a
* directory with correct permissions.
Expand Down Expand Up @@ -94,11 +142,22 @@ private static FileEntry defaultLayerEntry(Path source, AbsoluteUnixPath destina

@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();

private String getExtractPath(String parent, Path layerDirectory, Path target) {

return parent + target.getParent().toString().replaceAll(layerDirectory.getParent().toString(), "") + "/" + target.getFileName().toString();
}

@Test
public void testBuild() throws URISyntaxException, IOException {
Path layerDirectory = Paths.get(Resources.getResource("core/layer").toURI());
Path blobA = Paths.get(Resources.getResource("core/blobA").toURI());

List<Path> linksList = getLinks();
removeLinks(linksList);
for (Path path: linksList) {
Files.createSymbolicLink(path, path.getParent().relativize(blobA));
}

ReproducibleLayerBuilder layerBuilder =
new ReproducibleLayerBuilder(
ImmutableList.copyOf(
Expand All @@ -107,6 +166,8 @@ public void testBuild() throws URISyntaxException, IOException {
layerDirectory, AbsoluteUnixPath.get("/extract/here/apple/layer"))
.addEntry(blobA, AbsoluteUnixPath.get("/extract/here/apple/blobA"))
.addEntry(blobA, AbsoluteUnixPath.get("/extract/here/banana/blobA"))
.addEntry(linksList.get(0), AbsoluteUnixPath.get(getExtractPath("/extract/here/apple/", layerDirectory, linksList.get(0) )))
.addEntry(linksList.get(1), AbsoluteUnixPath.get(getExtractPath("/extract/here/apple/", layerDirectory, linksList.get(1) )))
.build()
.getEntries()));

Expand All @@ -124,6 +185,7 @@ public void testBuild() throws URISyntaxException, IOException {
verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, "extract/");
verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, "extract/here/");
verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, "extract/here/apple/");
verifyNextTarArchiveEntryIsLink(tarArchiveInputStream, "extract/here/apple/blob-link1", "blobA");
verifyNextTarArchiveEntry(tarArchiveInputStream, "extract/here/apple/blobA", blobA);
verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, "extract/here/apple/layer/");
verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, "extract/here/apple/layer/a/");
Expand All @@ -132,6 +194,7 @@ public void testBuild() throws URISyntaxException, IOException {
tarArchiveInputStream,
"extract/here/apple/layer/a/b/bar",
Paths.get(Resources.getResource("core/layer/a/b/bar").toURI()));
verifyNextTarArchiveEntryIsLink(tarArchiveInputStream, "extract/here/apple/layer/a/b/blob-link2", "../../../blobA");
verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, "extract/here/apple/layer/c/");
verifyNextTarArchiveEntry(
tarArchiveInputStream,
Expand Down