ExportedImageTar Class — spring-boot Architecture
Architecture documentation for the ExportedImageTar class in ExportedImageTar.java from the spring-boot codebase.
Entity Profile
Relationship Graph
Source Code
buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/ExportedImageTar.java lines 59–290
class ExportedImageTar implements Closeable {
private final Path tarFile;
private final LayerArchiveFactory layerArchiveFactory;
ExportedImageTar(ImageReference reference, InputStream inputStream) throws IOException {
this.tarFile = Files.createTempFile("docker-layers-", null);
Files.copy(inputStream, this.tarFile, StandardCopyOption.REPLACE_EXISTING);
this.layerArchiveFactory = LayerArchiveFactory.create(reference, this.tarFile);
}
void exportLayers(IOBiConsumer<String, TarArchive> exports) throws IOException {
try (TarArchiveInputStream tar = openTar(this.tarFile)) {
TarArchiveEntry entry = tar.getNextEntry();
while (entry != null) {
TarArchive layerArchive = this.layerArchiveFactory.getLayerArchive(tar, entry);
if (layerArchive != null) {
exports.accept(entry.getName(), layerArchive);
}
entry = tar.getNextEntry();
}
}
}
private static TarArchiveInputStream openTar(Path path) throws IOException {
return new TarArchiveInputStream(Files.newInputStream(path));
}
@Override
public void close() throws IOException {
Files.delete(this.tarFile);
}
/**
* Factory class used to create a {@link TarArchiveEntry} for layer.
*/
private abstract static class LayerArchiveFactory {
/**
* Create a new {@link TarArchive} if the given entry represents a layer.
* @param tar the tar input stream
* @param entry the candidate entry
* @return a new {@link TarArchive} instance or {@code null} if this entry is not
* a layer.
*/
abstract @Nullable TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry);
/**
* Create a new {@link LayerArchiveFactory} for the given tar file using either
* the {@code index.json} or {@code manifest.json} to detect layers.
* @param reference the image that was referenced
* @param tarFile the source tar file
* @return a new {@link LayerArchiveFactory} instance
* @throws IOException on IO error
*/
static LayerArchiveFactory create(ImageReference reference, Path tarFile) throws IOException {
try (TarArchiveInputStream tar = openTar(tarFile)) {
ImageArchiveIndex index = null;
ImageArchiveManifest manifest = null;
TarArchiveEntry entry = tar.getNextEntry();
while (entry != null) {
if ("index.json".equals(entry.getName())) {
index = ImageArchiveIndex.of(tar);
break;
}
if ("manifest.json".equals(entry.getName())) {
manifest = ImageArchiveManifest.of(tar);
}
entry = tar.getNextEntry();
}
Assert.state(index != null || manifest != null,
() -> "Exported image '%s' does not contain 'index.json' or 'manifest.json'"
.formatted(reference));
if (index != null) {
return new IndexLayerArchiveFactory(tarFile, index);
}
Assert.state(manifest != null, "'manifest' must not be null");
return new ManifestLayerArchiveFactory(manifest);
}
}
}
/**
* {@link LayerArchiveFactory} backed by the more recent {@code index.json} file.
*/
private static class IndexLayerArchiveFactory extends LayerArchiveFactory {
private final Map<String, String> layerMediaTypes;
IndexLayerArchiveFactory(Path tarFile, ImageArchiveIndex index) throws IOException {
this(tarFile, withNestedIndexes(tarFile, index));
}
IndexLayerArchiveFactory(Path tarFile, List<ImageArchiveIndex> indexes) throws IOException {
Set<String> manifestDigests = getDigests(indexes, this::isManifest);
Set<String> manifestListDigests = getDigests(indexes, IndexLayerArchiveFactory::isManifestList);
List<ManifestList> manifestLists = getManifestLists(tarFile, manifestListDigests);
List<Manifest> manifests = getManifests(tarFile, manifestDigests, manifestLists);
this.layerMediaTypes = manifests.stream()
.flatMap((manifest) -> manifest.getLayers().stream())
.collect(Collectors.toMap(IndexLayerArchiveFactory::getEntryName, BlobReference::getMediaType));
}
private static List<ImageArchiveIndex> withNestedIndexes(Path tarFile, ImageArchiveIndex index)
throws IOException {
Set<String> indexDigests = getDigests(Stream.of(index), IndexLayerArchiveFactory::isIndex);
List<ImageArchiveIndex> indexes = new ArrayList<>();
indexes.add(index);
indexes.addAll(getDigestMatches(tarFile, indexDigests, ImageArchiveIndex::of));
return indexes;
}
private static Set<String> getDigests(List<ImageArchiveIndex> indexes, Predicate<BlobReference> predicate) {
return getDigests(indexes.stream(), predicate);
}
private static Set<String> getDigests(Stream<ImageArchiveIndex> indexes, Predicate<BlobReference> predicate) {
return indexes.flatMap((index) -> index.getManifests().stream())
.filter(predicate)
.map(BlobReference::getDigest)
.collect(Collectors.toUnmodifiableSet());
}
private static List<ManifestList> getManifestLists(Path tarFile, Set<String> digests) throws IOException {
return getDigestMatches(tarFile, digests, ManifestList::of);
}
private List<Manifest> getManifests(Path tarFile, Set<String> manifestDigests, List<ManifestList> manifestLists)
throws IOException {
Set<String> digests = new HashSet<>(manifestDigests);
manifestLists.stream()
.flatMap(ManifestList::streamManifests)
.filter(this::isManifest)
.map(BlobReference::getDigest)
.forEach(digests::add);
return getDigestMatches(tarFile, digests, Manifest::of);
}
private static <T> List<T> getDigestMatches(Path tarFile, Set<String> digests,
ThrowingFunction<InputStream, T> factory) throws IOException {
if (digests.isEmpty()) {
return Collections.emptyList();
}
Set<String> names = digests.stream()
.map(IndexLayerArchiveFactory::getEntryName)
.collect(Collectors.toUnmodifiableSet());
List<T> result = new ArrayList<>();
try (TarArchiveInputStream tar = openTar(tarFile)) {
TarArchiveEntry entry = tar.getNextEntry();
while (entry != null) {
if (names.contains(entry.getName())) {
result.add(factory.apply(tar));
}
entry = tar.getNextEntry();
}
}
return Collections.unmodifiableList(result);
}
private boolean isManifest(BlobReference reference) {
return isJsonWithPrefix(reference.getMediaType(), "application/vnd.oci.image.manifest.v")
|| isJsonWithPrefix(reference.getMediaType(), "application/vnd.docker.distribution.manifest.v");
}
private static boolean isIndex(BlobReference reference) {
return isJsonWithPrefix(reference.getMediaType(), "application/vnd.oci.image.index.v");
}
private static boolean isManifestList(BlobReference reference) {
return isJsonWithPrefix(reference.getMediaType(), "application/vnd.docker.distribution.manifest.list.v");
}
private static boolean isJsonWithPrefix(String mediaType, String prefix) {
return mediaType.startsWith(prefix) && mediaType.endsWith("+json");
}
private static String getEntryName(BlobReference reference) {
return getEntryName(reference.getDigest());
}
private static String getEntryName(String digest) {
return "blobs/" + digest.replace(':', '/');
}
@Override
@Nullable TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry) {
String mediaType = this.layerMediaTypes.get(entry.getName());
if (mediaType == null) {
return null;
}
return TarArchive.fromInputStream(tar, getCompression(mediaType));
}
private Compression getCompression(String mediaType) {
if (mediaType.endsWith(".tar.gzip") || mediaType.endsWith(".tar+gzip")) {
return Compression.GZIP;
}
if (mediaType.endsWith(".tar.zstd") || mediaType.endsWith(".tar+zstd")) {
return Compression.ZSTD;
}
return Compression.NONE;
}
}
/**
* {@link LayerArchiveFactory} backed by the legacy {@code manifest.json} file.
*/
private static class ManifestLayerArchiveFactory extends LayerArchiveFactory {
private final Set<String> layers;
ManifestLayerArchiveFactory(ImageArchiveManifest manifest) {
this.layers = manifest.getEntries()
.stream()
.flatMap((entry) -> entry.getLayers().stream())
.collect(Collectors.toUnmodifiableSet());
}
@Override
@Nullable TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry) {
if (!this.layers.contains(entry.getName())) {
return null;
}
return TarArchive.fromInputStream(tar, Compression.NONE);
}
}
}
Domain
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free