Write buildpack directories to builder layer
When a custom buildpack is provided for image building, the contents of the buildpack directory, tgz file, or image are copied as tar entries to a new layer in the ephemeral builder image. Prior to this commit, only file entries from the buildpack source were copied as builder layer tar entries; intermediate directory entries from the source were not copied. This results in directories being created in the builder container using default permissions. This worked on most Linux-like OSs where the default permissions allow others-read access. On some OSs like Arch Linux where the default directory permissions do not allow others-read, this prevented the lifecycle processes from reading the buildpack files. This commit explicitly creates all intermediate directory tar entries in the builder image layer to ensure that the buildpack directories and files can be read by the lifecycle processes. Fixes gh-26658
This commit is contained in:
@@ -133,8 +133,11 @@ class DirectoryBuildpackTests {
|
||||
entries.add(entry);
|
||||
entry = tar.getNextTarEntry();
|
||||
}
|
||||
assertThat(entries).extracting("name", "mode").containsExactlyInAnyOrder(
|
||||
assertThat(entries).extracting("name", "mode").containsExactlyInAnyOrder(tuple("/cnb/", 0755),
|
||||
tuple("/cnb/buildpacks/", 0755), tuple("/cnb/buildpacks/example_buildpack1/", 0755),
|
||||
tuple("/cnb/buildpacks/example_buildpack1/0.0.1/", 0755),
|
||||
tuple("/cnb/buildpacks/example_buildpack1/0.0.1/buildpack.toml", 0644),
|
||||
tuple("/cnb/buildpacks/example_buildpack1/0.0.1/bin/", 0755),
|
||||
tuple("/cnb/buildpacks/example_buildpack1/0.0.1/bin/detect", 0744),
|
||||
tuple("/cnb/buildpacks/example_buildpack1/0.0.1/bin/build", 0744));
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.assertj.core.api.Assertions.tuple;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
@@ -126,6 +127,10 @@ class ImageBuildpackTests extends AbstractJsonTests {
|
||||
TarArchive archive = (out) -> {
|
||||
try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream(out)) {
|
||||
tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
|
||||
writeTarEntry(tarOut, "/cnb/");
|
||||
writeTarEntry(tarOut, "/cnb/buildpacks/");
|
||||
writeTarEntry(tarOut, "/cnb/buildpacks/example_buildpack/");
|
||||
writeTarEntry(tarOut, "/cnb/buildpacks/example_buildpack/0.0.1/");
|
||||
writeTarEntry(tarOut, "/cnb/buildpacks/example_buildpack/0.0.1/buildpack.toml");
|
||||
writeTarEntry(tarOut, "/cnb/buildpacks/example_buildpack/0.0.1/" + this.longFilePath);
|
||||
tarOut.finish();
|
||||
@@ -154,16 +159,22 @@ class ImageBuildpackTests extends AbstractJsonTests {
|
||||
});
|
||||
assertThat(layers).hasSize(1);
|
||||
byte[] content = layers.get(0).toByteArray();
|
||||
List<String> names = new ArrayList<>();
|
||||
List<TarArchiveEntry> entries = new ArrayList<>();
|
||||
try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) {
|
||||
TarArchiveEntry entry = tar.getNextTarEntry();
|
||||
while (entry != null) {
|
||||
names.add(entry.getName());
|
||||
entries.add(entry);
|
||||
entry = tar.getNextTarEntry();
|
||||
}
|
||||
}
|
||||
assertThat(names).containsExactlyInAnyOrder("cnb/buildpacks/example_buildpack/0.0.1/buildpack.toml",
|
||||
"cnb/buildpacks/example_buildpack/0.0.1/" + this.longFilePath);
|
||||
assertThat(entries).extracting("name", "mode").containsExactlyInAnyOrder(
|
||||
tuple("cnb/", TarArchiveEntry.DEFAULT_DIR_MODE),
|
||||
tuple("cnb/buildpacks/", TarArchiveEntry.DEFAULT_DIR_MODE),
|
||||
tuple("cnb/buildpacks/example_buildpack/", TarArchiveEntry.DEFAULT_DIR_MODE),
|
||||
tuple("cnb/buildpacks/example_buildpack/0.0.1/", TarArchiveEntry.DEFAULT_DIR_MODE),
|
||||
tuple("cnb/buildpacks/example_buildpack/0.0.1/buildpack.toml", TarArchiveEntry.DEFAULT_FILE_MODE),
|
||||
tuple("cnb/buildpacks/example_buildpack/0.0.1/" + this.longFilePath,
|
||||
TarArchiveEntry.DEFAULT_FILE_MODE));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -87,12 +87,19 @@ class TestTarGzip {
|
||||
String buildScript = "#!/usr/bin/env bash\n" + "echo \"---> build\"\n";
|
||||
try (TarArchiveOutputStream tar = new TarArchiveOutputStream(Files.newOutputStream(archive))) {
|
||||
writeEntry(tar, "buildpack.toml", buildpackToml.toString());
|
||||
writeEntry(tar, "bin/");
|
||||
writeEntry(tar, "bin/detect", detectScript);
|
||||
writeEntry(tar, "bin/build", buildScript);
|
||||
tar.finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeEntry(TarArchiveOutputStream tar, String entryName) throws IOException {
|
||||
TarArchiveEntry entry = new TarArchiveEntry(entryName);
|
||||
tar.putArchiveEntry(entry);
|
||||
tar.closeArchiveEntry();
|
||||
}
|
||||
|
||||
private void writeEntry(TarArchiveOutputStream tar, String entryName, String content) throws IOException {
|
||||
TarArchiveEntry entry = new TarArchiveEntry(entryName);
|
||||
entry.setSize(content.length());
|
||||
@@ -111,8 +118,13 @@ class TestTarGzip {
|
||||
assertThat(layers).hasSize(1);
|
||||
byte[] content = layers.get(0).toByteArray();
|
||||
try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) {
|
||||
assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/");
|
||||
assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/");
|
||||
assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/");
|
||||
assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/");
|
||||
assertThat(tar.getNextEntry().getName())
|
||||
.isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/buildpack.toml");
|
||||
assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/bin/");
|
||||
assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/bin/detect");
|
||||
assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/bin/build");
|
||||
assertThat(tar.getNextEntry()).isNull();
|
||||
|
||||
@@ -16,14 +16,21 @@
|
||||
|
||||
package org.springframework.boot.buildpack.platform.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIOException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
@@ -33,6 +40,28 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
*/
|
||||
class FilePermissionsTests {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void umaskForPath() throws IOException {
|
||||
FileAttribute<Set<PosixFilePermission>> fileAttribute = PosixFilePermissions
|
||||
.asFileAttribute(PosixFilePermissions.fromString("rw-r-----"));
|
||||
Path tempFile = Files.createTempFile(this.tempDir, "umask", null, fileAttribute);
|
||||
assertThat(FilePermissions.umaskForPath(tempFile)).isEqualTo(0640);
|
||||
}
|
||||
|
||||
@Test
|
||||
void umaskForPathWithNonExistentFile() throws IOException {
|
||||
assertThatIOException()
|
||||
.isThrownBy(() -> FilePermissions.umaskForPath(Paths.get(this.tempDir.toString(), "does-not-exist")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void umaskForPathWithNullPath() throws IOException {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> FilePermissions.umaskForPath(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void posixPermissionsToUmask() {
|
||||
Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("rwxrw-r--");
|
||||
|
||||
Reference in New Issue
Block a user