Skip to content
Merged
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 @@ -132,35 +132,41 @@ private static class ExportedLayers {

private Path createLayerFile(TarArchive tarArchive) throws IOException {
Path sourceTarFile = Files.createTempFile("create-builder-scratch-source-", null);
try (OutputStream out = Files.newOutputStream(sourceTarFile)) {
tarArchive.writeTo(out);
}
Path layerFile = Files.createTempFile("create-builder-scratch-", null);
try (TarArchiveOutputStream out = new TarArchiveOutputStream(Files.newOutputStream(layerFile))) {
try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(sourceTarFile))) {
out.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
TarArchiveEntry entry = in.getNextEntry();
while (entry != null) {
String entryName = entry.getName();
Path entryPath = Path.of(entryName);
Assert.state(entryPath.toAbsolutePath().equals(entryPath.toAbsolutePath().normalize()),
() -> "Malformed zip entry name '%s'".formatted(entryName));
out.putArchiveEntry(entry);
StreamUtils.copy(in, out);
out.closeArchiveEntry();
entry = in.getNextEntry();
try {
try (OutputStream out = Files.newOutputStream(sourceTarFile)) {
tarArchive.writeTo(out);
}
Path layerFile = Files.createTempFile("create-builder-scratch-", null);
try (TarArchiveOutputStream out = new TarArchiveOutputStream(Files.newOutputStream(layerFile))) {
try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(sourceTarFile))) {
out.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
TarArchiveEntry entry = in.getNextEntry();
while (entry != null) {
String entryName = entry.getName();
Path entryPath = Path.of(entryName);
Assert.state(entryPath.toAbsolutePath().equals(entryPath.toAbsolutePath().normalize()),
() -> "Malformed zip entry name '%s'".formatted(entryName));
out.putArchiveEntry(entry);
StreamUtils.copy(in, out);
out.closeArchiveEntry();
entry = in.getNextEntry();
}
out.finish();
}
out.finish();
}
return layerFile;
}
finally {
Files.deleteIfExists(sourceTarFile);
}
return layerFile;
}

void apply(IOConsumer<Layer> layers) throws IOException {
for (Path path : this.layerFiles) {
layers.accept(Layer.fromTarArchive((out) -> {
InputStream in = Files.newInputStream(path);
StreamUtils.copy(in, out);
try (InputStream in = Files.newInputStream(path)) {
StreamUtils.copy(in, out);
}
}));
Files.delete(path);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
Expand Down Expand Up @@ -197,6 +200,42 @@ void resolveWhenEntryWouldWriteOutsideOfDestinationThrowsException() throws Exce
.withMessage("Malformed zip entry name '../cnb/'");
}

@Test
void resolveDeletesIntermediateSourceTarTempFiles() throws Exception {
File tempDir = new File(System.getProperty("java.io.tmpdir"));
Set<String> tempsBefore = listTempFileNames(tempDir, "create-builder-scratch-");
Image image = Image.of(getContent("buildpack-image.json"));
ImageReference imageReference = ImageReference.of("example/buildpack1:1.0.0");
BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class);
given(resolverContext.getBuildpackLayersMetadata()).willReturn(BuildpackLayersMetadata.fromJson("{}"));
given(resolverContext.fetchImage(eq(imageReference), eq(ImageType.BUILDPACK))).willReturn(image);
willAnswer(this::withMockLayers).given(resolverContext).exportImageLayers(eq(imageReference), any());
BuildpackReference reference = BuildpackReference.of("docker://example/buildpack1:1.0.0");
Buildpack buildpack = ImageBuildpack.resolve(resolverContext, reference);
assertThat(buildpack).isNotNull();
Set<String> createdOnResolve = new HashSet<>(listTempFileNames(tempDir, "create-builder-scratch-"));
createdOnResolve.removeAll(tempsBefore);
assertThat(createdOnResolve).as("intermediate source tar temp files must be deleted after createLayerFile")
.noneMatch((name) -> name.startsWith("create-builder-scratch-source-"));
assertThat(createdOnResolve).isNotEmpty();
assertAppliesExpectedLayers(buildpack);
Set<String> remainingCreated = new HashSet<>(listTempFileNames(tempDir, "create-builder-scratch-"));
remainingCreated.retainAll(createdOnResolve);
assertThat(remainingCreated).as("layer temp files created on resolve must be deleted after apply").isEmpty();
}

private Set<String> listTempFileNames(File tempDir, String prefix) {
File[] files = tempDir.listFiles((dir, name) -> name.startsWith(prefix));
if (files == null) {
return Collections.emptySet();
}
Set<String> names = new HashSet<>();
for (File file : files) {
names.add(file.getName());
}
return names;
}

private @Nullable Object withMockLayers(InvocationOnMock invocation) {
return withMockLayers(invocation, "");
}
Expand Down
Loading