Skip to content

Commit 90ba130

Browse files
committed
Fixes: Distribution API
Signed-off-by: Kan Cheung <[email protected]>
1 parent 350429c commit 90ba130

File tree

3 files changed

+139
-2
lines changed

3 files changed

+139
-2
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//go:build !remote
2+
3+
package compat
4+
5+
import (
6+
"fmt"
7+
"net/http"
8+
9+
"github.com/containers/image/v5/docker"
10+
"github.com/containers/image/v5/docker/reference"
11+
"github.com/containers/image/v5/manifest"
12+
"github.com/containers/podman/v5/pkg/api/handlers/utils"
13+
registrytypes "github.com/docker/docker/api/types/registry"
14+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
15+
)
16+
17+
func InspectDistribution(w http.ResponseWriter, r *http.Request) {
18+
w.Header().Set("Content-Type", "application/json")
19+
20+
name := utils.GetName(r)
21+
22+
ref, err := reference.ParseAnyReference(name)
23+
if err != nil {
24+
utils.Error(w, http.StatusBadRequest, fmt.Errorf("error parsing image reference %q: %w", name, err))
25+
return
26+
}
27+
28+
namedRef, ok := ref.(reference.Named)
29+
if !ok {
30+
if _, ok := ref.(reference.Digested); ok {
31+
// full image ID
32+
utils.Error(w, http.StatusBadRequest, fmt.Errorf("no manifest found for full image ID"))
33+
return
34+
}
35+
utils.Error(w, http.StatusBadRequest, fmt.Errorf("unknown image reference format: %s", name))
36+
return
37+
}
38+
39+
namedRef = reference.TagNameOnly(namedRef)
40+
41+
imgRef, err := docker.NewReference(namedRef)
42+
if err != nil {
43+
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error creating image reference: %w", err))
44+
return
45+
}
46+
47+
imgSrc, err := imgRef.NewImageSource(r.Context(), nil)
48+
if err != nil {
49+
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error getting image source: %w", err))
50+
return
51+
}
52+
defer imgSrc.Close()
53+
54+
manBlob, manType, err := imgSrc.GetManifest(r.Context(), nil)
55+
if err != nil {
56+
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error getting manifest: %w", err))
57+
return
58+
}
59+
60+
digest, err := manifest.Digest(manBlob)
61+
if err != nil {
62+
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error getting manifest digest: %w", err))
63+
return
64+
}
65+
66+
// todo: handle other manifest types
67+
ociIndex, err := manifest.OCI1IndexFromManifest(manBlob)
68+
if err != nil {
69+
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error getting OCI index from manifest: %w", err))
70+
return
71+
}
72+
73+
platforms := make([]ocispec.Platform, len(ociIndex.Manifests))
74+
for i, m := range ociIndex.Manifests {
75+
platforms[i] = *m.Platform
76+
}
77+
78+
distributionInspect := registrytypes.DistributionInspect{
79+
Descriptor: ocispec.Descriptor{
80+
Digest: digest,
81+
Size: int64(len(manBlob)),
82+
MediaType: manType,
83+
},
84+
Platforms: platforms,
85+
}
86+
87+
utils.WriteResponse(w, http.StatusOK, distributionInspect)
88+
}

pkg/api/server/register_distribution.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
package server
44

55
import (
6+
"net/http"
7+
68
"github.com/containers/podman/v5/pkg/api/handlers/compat"
79
"github.com/gorilla/mux"
810
)
911

1012
func (s *APIServer) registerDistributionHandlers(r *mux.Router) error {
11-
r.HandleFunc(VersionedPath("/distribution/{name}/json"), compat.UnsupportedHandler)
13+
r.HandleFunc(VersionedPath("/distribution/{name:.*}/json"), s.APIHandler(compat.InspectDistribution)).Methods(http.MethodGet)
14+
r.HandleFunc(VersionedPath("/libpod/distribution/{name:.*}/json"), s.APIHandler(compat.InspectDistribution)).Methods(http.MethodGet)
1215
// Added non version path to URI to support docker non versioned paths
13-
r.HandleFunc("/distribution/{name}/json", compat.UnsupportedHandler)
16+
r.HandleFunc("/distribution/{name:.*}/json", s.APIHandler(compat.InspectDistribution)).Methods(http.MethodGet)
1417
return nil
1518
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import unittest
2+
3+
import requests
4+
from .fixtures import APITestCase
5+
6+
7+
class DistributionTestCase(APITestCase):
8+
def test_distribution_inspect(self):
9+
# Make sure the image exists
10+
r = requests.post(self.uri("/images/pull?reference=alpine:latest"), timeout=15)
11+
self.assertEqual(r.status_code, 200, r.text)
12+
13+
r = requests.get(self.podman_url + "/v1.40/distribution/alpine/json")
14+
self.assertEqual(r.status_code, 200, r.text)
15+
16+
result = r.json()
17+
self.assertIn("Descriptor", result)
18+
self.assertIn("Platforms", result)
19+
20+
descriptor = result["Descriptor"]
21+
self.assertIn("mediaType", descriptor)
22+
self.assertIn("digest", descriptor)
23+
self.assertIn("size", descriptor)
24+
25+
for platform in result["Platforms"]:
26+
self.assertIn("architecture", platform)
27+
self.assertIn("os", platform)
28+
29+
def test_distribution_inspect_invalid_image(self):
30+
r = requests.get(self.podman_url + "/v1.40/distribution/nonexistentimage/json")
31+
self.assertIn(r.status_code, (404, 500), r.text)
32+
33+
def test_distribution_inspect_with_tag(self):
34+
r = requests.post(self.uri("/images/pull?reference=postgres:14.7"), timeout=15)
35+
self.assertEqual(r.status_code, 200, r.text)
36+
37+
r = requests.get(self.podman_url + "/v1.40/distribution/postgres:14.7/json")
38+
self.assertEqual(r.status_code, 200, r.text)
39+
40+
result = r.json()
41+
self.assertIn("Descriptor", result)
42+
self.assertIn("Platforms", result)
43+
44+
45+
if __name__ == "__main__":
46+
unittest.main()

0 commit comments

Comments
 (0)