mirror of https://github.com/containers/podman.git
Merge 4fe289c214
into 70435a0fe8
This commit is contained in:
commit
8b495c5888
|
@ -0,0 +1,133 @@
|
||||||
|
//go:build !remote
|
||||||
|
|
||||||
|
package compat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/containers/image/v5/docker"
|
||||||
|
"github.com/containers/image/v5/docker/reference"
|
||||||
|
"github.com/containers/image/v5/image"
|
||||||
|
"github.com/containers/image/v5/manifest"
|
||||||
|
"github.com/containers/image/v5/types"
|
||||||
|
"github.com/containers/podman/v5/libpod"
|
||||||
|
"github.com/containers/podman/v5/pkg/api/handlers/utils"
|
||||||
|
api "github.com/containers/podman/v5/pkg/api/types"
|
||||||
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InspectDistribution(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
||||||
|
|
||||||
|
_, imgRef, err := parseImageReference(utils.GetName(r))
|
||||||
|
if err != nil {
|
||||||
|
utils.Error(w, http.StatusUnauthorized, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imgSrc, err := imgRef.NewImageSource(r.Context(), nil)
|
||||||
|
if err != nil {
|
||||||
|
var unauthErr docker.ErrUnauthorizedForCredentials
|
||||||
|
if errors.As(err, &unauthErr) {
|
||||||
|
utils.Error(w, http.StatusUnauthorized, errors.New("401 Unauthorized"))
|
||||||
|
} else {
|
||||||
|
utils.Error(w, http.StatusUnauthorized, fmt.Errorf("image not found: %w", err))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer imgSrc.Close()
|
||||||
|
|
||||||
|
unparsedImage := image.UnparsedInstance(imgSrc, nil)
|
||||||
|
manBlob, manType, err := unparsedImage.Manifest(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error getting manifest: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
img, err := image.FromUnparsedImage(r.Context(), runtime.SystemContext(), unparsedImage)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error getting manifest: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
digest, err := manifest.Digest(manBlob)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error getting manifest digest: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
distributionInspect := registrytypes.DistributionInspect{
|
||||||
|
Descriptor: ocispec.Descriptor{
|
||||||
|
Digest: digest,
|
||||||
|
Size: int64(len(manBlob)),
|
||||||
|
MediaType: manType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
platforms, err := getPlatformsFromManifest(r.Context(), img, manBlob, manType)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
distributionInspect.Platforms = platforms
|
||||||
|
|
||||||
|
utils.WriteResponse(w, http.StatusOK, distributionInspect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseImageReference(name string) (reference.Named, types.ImageReference, error) {
|
||||||
|
namedRef, err := reference.ParseNormalizedNamed(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("not a valid image reference: %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
namedRef = reference.TagNameOnly(namedRef)
|
||||||
|
|
||||||
|
imgRef, err := docker.NewReference(namedRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error creating image reference: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return namedRef, imgRef, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPlatformsFromManifest(ctx context.Context, img types.Image, manBlob []byte, manType string) ([]ocispec.Platform, error) {
|
||||||
|
if manType == "" {
|
||||||
|
manType = manifest.GuessMIMEType(manBlob)
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifest.MIMETypeIsMultiImage(manType) {
|
||||||
|
manifestList, err := manifest.ListFromBlob(manBlob, manType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing manifest list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceDigests := manifestList.Instances()
|
||||||
|
platforms := make([]ocispec.Platform, 0, len(instanceDigests))
|
||||||
|
for _, digest := range instanceDigests {
|
||||||
|
instance, err := manifestList.Instance(digest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting manifest list instance: %w", err)
|
||||||
|
}
|
||||||
|
if instance.ReadOnly.Platform == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
platforms = append(platforms, *instance.ReadOnly.Platform)
|
||||||
|
}
|
||||||
|
return platforms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch manType {
|
||||||
|
case ocispec.MediaTypeImageManifest, manifest.DockerV2Schema2MediaType, manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType:
|
||||||
|
config, err := img.OCIConfig(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting OCI config: %w", err)
|
||||||
|
}
|
||||||
|
return []ocispec.Platform{config.Platform}, nil
|
||||||
|
}
|
||||||
|
return []ocispec.Platform{}, nil
|
||||||
|
}
|
|
@ -3,13 +3,16 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/containers/podman/v5/pkg/api/handlers/compat"
|
"github.com/containers/podman/v5/pkg/api/handlers/compat"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *APIServer) registerDistributionHandlers(r *mux.Router) error {
|
func (s *APIServer) registerDistributionHandlers(r *mux.Router) error {
|
||||||
r.HandleFunc(VersionedPath("/distribution/{name}/json"), compat.UnsupportedHandler)
|
r.HandleFunc(VersionedPath("/distribution/{name:.*}/json"), s.APIHandler(compat.InspectDistribution)).Methods(http.MethodGet)
|
||||||
|
r.HandleFunc(VersionedPath("/libpod/distribution/{name:.*}/json"), s.APIHandler(compat.InspectDistribution)).Methods(http.MethodGet)
|
||||||
// Added non version path to URI to support docker non versioned paths
|
// Added non version path to URI to support docker non versioned paths
|
||||||
r.HandleFunc("/distribution/{name}/json", compat.UnsupportedHandler)
|
r.HandleFunc("/distribution/{name:.*}/json", s.APIHandler(compat.InspectDistribution)).Methods(http.MethodGet)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from .fixtures import APITestCase
|
||||||
|
|
||||||
|
|
||||||
|
class DistributionTestCase(APITestCase):
|
||||||
|
def test_distribution_inspect(self):
|
||||||
|
# Make sure the image exists
|
||||||
|
r = requests.post(self.uri("/images/pull?reference=alpine:latest"), timeout=15)
|
||||||
|
self.assertEqual(r.status_code, 200, r.text)
|
||||||
|
|
||||||
|
r = requests.get(self.podman_url + "/v1.40/distribution/alpine/json")
|
||||||
|
self.assertEqual(r.status_code, 200, r.text)
|
||||||
|
|
||||||
|
result = r.json()
|
||||||
|
self.assertIn("Descriptor", result)
|
||||||
|
self.assertIn("Platforms", result)
|
||||||
|
|
||||||
|
descriptor = result["Descriptor"]
|
||||||
|
self.assertIn("mediaType", descriptor)
|
||||||
|
self.assertIn("digest", descriptor)
|
||||||
|
self.assertIn("size", descriptor)
|
||||||
|
|
||||||
|
for platform in result["Platforms"]:
|
||||||
|
self.assertIn("architecture", platform)
|
||||||
|
self.assertIn("os", platform)
|
||||||
|
|
||||||
|
def test_distribution_inspect_invalid_image(self):
|
||||||
|
r = requests.get(self.podman_url + "/v1.40/distribution/nonexistentimage/json")
|
||||||
|
self.assertEqual(r.status_code, 401, r.text)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
Loading…
Reference in New Issue