Skip to content

Commit

Permalink
Support pulling OCI Image Index manifests
Browse files Browse the repository at this point in the history
- Add Accept header application/vnd.oci.image.index.v1+json during base image pull
- Re-use the logic from V22ManifestListTemplate to select the target platform diget via a new interface ManifestListTemplate
  • Loading branch information
rquinio committed Aug 24, 2022
1 parent 9027c8d commit 93962c4
Show file tree
Hide file tree
Showing 13 changed files with 414 additions and 31 deletions.
Expand Up @@ -41,6 +41,7 @@
import com.google.cloud.tools.jib.image.json.ImageMetadataTemplate;
import com.google.cloud.tools.jib.image.json.JsonToImageTranslator;
import com.google.cloud.tools.jib.image.json.ManifestAndConfigTemplate;
import com.google.cloud.tools.jib.image.json.ManifestListTemplate;
import com.google.cloud.tools.jib.image.json.ManifestTemplate;
import com.google.cloud.tools.jib.image.json.PlatformNotFoundInBaseImageException;
import com.google.cloud.tools.jib.image.json.UnknownManifestFormatException;
Expand Down Expand Up @@ -315,8 +316,7 @@ private List<Image> pullBaseImages(
JsonToImageTranslator.toImage(imageManifest, containerConfig));
}

// TODO: support OciIndexTemplate once AbstractManifestPuller starts to accept it.
Verify.verify(manifestTemplate instanceof V22ManifestListTemplate);
Verify.verify(manifestTemplate instanceof ManifestListTemplate);

List<ManifestAndConfigTemplate> manifestsAndConfigs = new ArrayList<>();
ImmutableList.Builder<Image> images = ImmutableList.builder();
Expand All @@ -332,7 +332,7 @@ private List<Image> pullBaseImages(

String manifestDigest =
lookUpPlatformSpecificImageManifest(
(V22ManifestListTemplate) manifestTemplate, platform);
(ManifestListTemplate) manifestTemplate, platform);
// TODO: pull multiple manifests (+ container configs) in parallel.
ManifestAndDigest<?> imageManifestAndDigest = registryClient.pullManifest(manifestDigest);
progressDispatcher2.dispatchProgress(1);
Expand Down Expand Up @@ -360,10 +360,9 @@ private List<Image> pullBaseImages(
* Looks through a manifest list for the manifest matching the {@code platform} and returns the
* digest of the first manifest it finds.
*/
// TODO: support OciIndexTemplate once AbstractManifestPuller starts to accept it.
@VisibleForTesting
String lookUpPlatformSpecificImageManifest(
V22ManifestListTemplate manifestListTemplate, Platform platform)
ManifestListTemplate manifestListTemplate, Platform platform)
throws UnlistedPlatformInManifestListException {
EventHandlers eventHandlers = buildContext.getEventHandlers();

Expand Down
Expand Up @@ -59,7 +59,7 @@ class ContentDescriptorTemplate implements JsonTemplate {

/** Necessary for Jackson to create from JSON. */
@SuppressWarnings("unused")
private ContentDescriptorTemplate() {}
protected ContentDescriptorTemplate() {}

public long getSize() {
return size;
Expand Down
@@ -0,0 +1,38 @@
/*
* Copyright 2022 Google LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/

package com.google.cloud.tools.jib.image.json;

import java.util.List;

/**
* Parent class for manifest lists.
*
* @see V22ManifestListTemplate Docker V2.2 format
* @see OciIndexTemplate OCI format
*/
public interface ManifestListTemplate extends ManifestTemplate {

/**
* Returns a list of digests for a specific platform found in the manifest list. see
* <a>https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list</a>
*
* @param architecture the architecture of the target platform
* @param os the os of the target platform
* @return a list of matching digests
*/
List<String> getDigestsForPlatform(String architecture, String os);
}
Expand Up @@ -16,11 +16,17 @@

package com.google.cloud.tools.jib.image.json;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.blob.BlobDescriptor;
import com.google.cloud.tools.jib.json.JsonTemplate;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

/**
* JSON template for OCI archive "index.json" file.
Expand All @@ -36,6 +42,10 @@
* "mediaType": "application/vnd.oci.image.manifest.v1+json",
* "digest": "sha256:e684b1dceef404268f17d4adf7f755fd9912b8ae64864b3954a83ebb8aa628b3",
* "size": 1132,
* "platform": {
* "architecture": "ppc64le",
* "os": "linux"
* },
* "annotations": {
* "org.opencontainers.image.ref.name": "gcr.io/project/image:tag"
* }
Expand All @@ -47,16 +57,15 @@
* @see <a href="https://github.com/opencontainers/image-spec/blob/master/image-index.md">OCI Image
* Index Specification</a>
*/
public class OciIndexTemplate implements ManifestTemplate {
public class OciIndexTemplate implements ManifestListTemplate {

/** The OCI Index media type. */
public static final String MEDIA_TYPE = "application/vnd.oci.image.index.v1+json";

private final int schemaVersion = 2;
private final String mediaType = MEDIA_TYPE;

private final List<BuildableManifestTemplate.ContentDescriptorTemplate> manifests =
new ArrayList<>();
private final List<ManifestDescriptorTemplate> manifests = new ArrayList<>();

@Override
public int getSchemaVersion() {
Expand All @@ -75,16 +84,93 @@ public String getManifestMediaType() {
* @param imageReferenceName the image reference name
*/
public void addManifest(BlobDescriptor descriptor, String imageReferenceName) {
BuildableManifestTemplate.ContentDescriptorTemplate contentDescriptorTemplate =
new BuildableManifestTemplate.ContentDescriptorTemplate(
ManifestDescriptorTemplate contentDescriptorTemplate =
new ManifestDescriptorTemplate(
OciManifestTemplate.MANIFEST_MEDIA_TYPE, descriptor.getSize(), descriptor.getDigest());
contentDescriptorTemplate.setAnnotations(
ImmutableMap.of("org.opencontainers.image.ref.name", imageReferenceName));
manifests.add(contentDescriptorTemplate);
}

/**
* Adds a manifest.
*
* @param manifest a manifest descriptor
*/
public void addManifest(OciIndexTemplate.ManifestDescriptorTemplate manifest) {
manifests.add(manifest);
}

@VisibleForTesting
public List<BuildableManifestTemplate.ContentDescriptorTemplate> getManifests() {
public List<ManifestDescriptorTemplate> getManifests() {
return manifests;
}

@Override
public List<String> getDigestsForPlatform(String architecture, String os) {
return getManifests().stream()
.filter(
manifest ->
manifest.platform != null
&& os.equals(manifest.platform.os)
&& architecture.equals(manifest.platform.architecture))
.map(ManifestDescriptorTemplate::getDigest)
.filter(Objects::nonNull)
.map(DescriptorDigest::toString)
.collect(Collectors.toList());
}

/**
* Template for inner JSON object representing a single platform specific manifest. See <a
* href="https://github.com/opencontainers/image-spec/blob/main/image-index.md">OCI Image Index
* Specification</a>
*/
public static class ManifestDescriptorTemplate
extends BuildableManifestTemplate.ContentDescriptorTemplate {

ManifestDescriptorTemplate(String mediaType, long size, DescriptorDigest digest) {
super(mediaType, size, digest);
}

/** Necessary for Jackson to create from JSON. */
@SuppressWarnings("unused")
private ManifestDescriptorTemplate() {
super();
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class Platform implements JsonTemplate {
@Nullable private String architecture;
@Nullable private String os;

@Nullable
public String getArchitecture() {
return architecture;
}

@Nullable
public String getOs() {
return os;
}
}

@Nullable private OciIndexTemplate.ManifestDescriptorTemplate.Platform platform;

/**
* Sets a platform.
*
* @param architecture the manifest architecture
* @param os the manifest os
*/
public void setPlatform(String architecture, String os) {
platform = new OciIndexTemplate.ManifestDescriptorTemplate.Platform();
platform.architecture = architecture;
platform.os = os;
}

@Nullable
public OciIndexTemplate.ManifestDescriptorTemplate.Platform getPlatform() {
return platform;
}
}
}
Expand Up @@ -63,7 +63,7 @@
* @see <a href="https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list">Image Manifest
* Version 2, Schema 2: Manifest List</a>
*/
public class V22ManifestListTemplate implements ManifestTemplate {
public class V22ManifestListTemplate implements ManifestListTemplate {

public static final String MANIFEST_MEDIA_TYPE =
"application/vnd.docker.distribution.manifest.list.v2+json";
Expand Down Expand Up @@ -101,14 +101,7 @@ public List<ManifestDescriptorTemplate> getManifests() {
return Preconditions.checkNotNull(manifests);
}

/**
* Returns a list of digests for a specific platform found in the manifest list. see
* <a>https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list</a>
*
* @param architecture the architecture of the target platform
* @param os the os of the target platform
* @return a list of matching digests
*/
@Override
public List<String> getDigestsForPlatform(String architecture, String os) {
return getManifests().stream()
.filter(
Expand Down
Expand Up @@ -80,12 +80,16 @@ public List<String> getAccept() {
if (manifestTemplateClass.equals(V22ManifestListTemplate.class)) {
return Collections.singletonList(V22ManifestListTemplate.MANIFEST_MEDIA_TYPE);
}
if (manifestTemplateClass.equals(OciIndexTemplate.class)) {
return Collections.singletonList(OciIndexTemplate.MEDIA_TYPE);
}

return Arrays.asList(
OciManifestTemplate.MANIFEST_MEDIA_TYPE,
V22ManifestTemplate.MANIFEST_MEDIA_TYPE,
V21ManifestTemplate.MEDIA_TYPE,
V22ManifestListTemplate.MANIFEST_MEDIA_TYPE);
V22ManifestListTemplate.MANIFEST_MEDIA_TYPE,
OciIndexTemplate.MEDIA_TYPE);
}

/** Parses the response body into a {@link ManifestAndDigest}. */
Expand Down Expand Up @@ -174,6 +178,10 @@ private T getManifestTemplateFromJson(String jsonString)
return manifestTemplateClass.cast(
JsonTemplateMapper.readJson(jsonString, V22ManifestListTemplate.class));
}
if (OciIndexTemplate.MEDIA_TYPE.equals(mediaType)) {
return manifestTemplateClass.cast(
JsonTemplateMapper.readJson(jsonString, OciIndexTemplate.class));
}
throw new UnknownManifestFormatException("Unknown mediaType: " + mediaType);
}
throw new UnknownManifestFormatException(
Expand Down

0 comments on commit 93962c4

Please sign in to comment.