mirror of
https://github.com/containers/skopeo.git
synced 2025-07-16 15:52:24 +00:00
proxy: Add support for manifest lists
We need to support manifest lists. I'm not sure how I missed this originally. At least now we have integration tests that cover this. The issue here is fairly subtle - the way c/image works right now, `image.FromUnparsedImage` does pick a matching image from a list by default. But it also overrides `GetManifest()` to return the original manifest list, which defeats our goal here. Handle this by adding explicit manifest list support code. We'll want this anyways for future support for `GetRawManifest` or so which exposes OCI manifest lists to the client. Signed-off-by: Colin Walters <walters@verbum.org>
This commit is contained in:
parent
83416068d3
commit
644074cbb4
@ -145,7 +145,7 @@ type openImage struct {
|
||||
// id is an opaque integer handle
|
||||
id uint32
|
||||
src types.ImageSource
|
||||
img types.Image
|
||||
cachedimg types.Image
|
||||
}
|
||||
|
||||
// proxyHandler is the state associated with our socket.
|
||||
@ -219,16 +219,11 @@ func (h *proxyHandler) OpenImage(args []interface{}) (replyBuf, error) {
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
img, err := image.FromUnparsedImage(context.Background(), h.sysctx, image.UnparsedInstance(imgsrc, nil))
|
||||
if err != nil {
|
||||
return ret, fmt.Errorf("failed to load image: %w", err)
|
||||
}
|
||||
|
||||
h.imageSerial++
|
||||
openimg := &openImage{
|
||||
id: h.imageSerial,
|
||||
src: imgsrc,
|
||||
img: img,
|
||||
}
|
||||
h.images[openimg.id] = openimg
|
||||
ret.value = openimg.id
|
||||
@ -326,7 +321,46 @@ func (h *proxyHandler) returnBytes(retval interface{}, buf []byte) (replyBuf, er
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// cacheTargetManifest is invoked when GetManifest or GetConfig is invoked
|
||||
// the first time for a given image. If the requested image is a manifest
|
||||
// list, this function resolves it to the image matching the calling process'
|
||||
// operating system and architecture.
|
||||
//
|
||||
// TODO: Add GetRawManifest or so that exposes manifest lists
|
||||
func (h *proxyHandler) cacheTargetManifest(img *openImage) error {
|
||||
ctx := context.Background()
|
||||
if img.cachedimg != nil {
|
||||
return nil
|
||||
}
|
||||
unparsedToplevel := image.UnparsedInstance(img.src, nil)
|
||||
mfest, manifestType, err := unparsedToplevel.Manifest(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var target *image.UnparsedImage
|
||||
if manifest.MIMETypeIsMultiImage(manifestType) {
|
||||
manifestList, err := manifest.ListFromBlob(mfest, manifestType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
instanceDigest, err := manifestList.ChooseInstance(h.sysctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target = image.UnparsedInstance(img.src, &instanceDigest)
|
||||
} else {
|
||||
target = unparsedToplevel
|
||||
}
|
||||
cachedimg, err := image.FromUnparsedImage(ctx, h.sysctx, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
img.cachedimg = cachedimg
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetManifest returns a copy of the manifest, converted to OCI format, along with the original digest.
|
||||
// Manifest lists are resolved to the current operating system and architecture.
|
||||
func (h *proxyHandler) GetManifest(args []interface{}) (replyBuf, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
@ -344,11 +378,18 @@ func (h *proxyHandler) GetManifest(args []interface{}) (replyBuf, error) {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
rawManifest, manifestType, err := imgref.img.Manifest(ctx)
|
||||
err = h.cacheTargetManifest(imgref)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
img := imgref.cachedimg
|
||||
|
||||
ctx := context.Background()
|
||||
rawManifest, manifestType, err := img.Manifest(ctx)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// We only support OCI and docker2schema2. We know docker2schema2 can be easily+cheaply
|
||||
// converted into OCI, so consumers only need to see OCI.
|
||||
switch manifestType {
|
||||
@ -373,7 +414,7 @@ func (h *proxyHandler) GetManifest(args []interface{}) (replyBuf, error) {
|
||||
// docker schema and MIME types.
|
||||
if manifestType != imgspecv1.MediaTypeImageManifest {
|
||||
manifestUpdates := types.ManifestUpdateOptions{ManifestMIMEType: imgspecv1.MediaTypeImageManifest}
|
||||
ociImage, err := imgref.img.UpdatedImage(ctx, manifestUpdates)
|
||||
ociImage, err := img.UpdatedImage(ctx, manifestUpdates)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
@ -406,9 +447,14 @@ func (h *proxyHandler) GetConfig(args []interface{}) (replyBuf, error) {
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
err = h.cacheTargetManifest(imgref)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
img := imgref.cachedimg
|
||||
|
||||
ctx := context.TODO()
|
||||
config, err := imgref.img.OCIConfig(ctx)
|
||||
config, err := img.OCIConfig(ctx)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
@ -145,9 +145,25 @@ func newProxy() (*proxy, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &proxy{
|
||||
p := &proxy{
|
||||
c: mysock.(*net.UnixConn),
|
||||
}, nil
|
||||
}
|
||||
|
||||
v, fd, err := p.call("Initialize", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fd != nil {
|
||||
return nil, fmt.Errorf("proxy Initialize: Unexpected fd")
|
||||
}
|
||||
semver, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("proxy Initialize: Unexpected value %T", v)
|
||||
}
|
||||
if !strings.HasPrefix(semver, expectedProxySemverMajor) {
|
||||
return nil, fmt.Errorf("Unexpected semver %s", semver)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -182,32 +198,28 @@ type byteFetch struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *ProxySuite) TestProxy(c *check.C) {
|
||||
p, err := newProxy()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
v, fd, err := p.call("Initialize", nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
semver, ok := v.(string)
|
||||
if !ok {
|
||||
c.Fatalf("Unexpected value %T", v)
|
||||
func runTestGetManifest(p *proxy, img string) error {
|
||||
v, fd, err := p.call("OpenImage", []interface{}{knownNotManifestListedImage_x8664})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(semver, expectedProxySemverMajor) {
|
||||
c.Fatalf("Unexpected semver %s", semver)
|
||||
if fd != nil {
|
||||
return fmt.Errorf("Unexpected fd")
|
||||
}
|
||||
c.Assert(fd, check.IsNil)
|
||||
|
||||
v, fd, err = p.call("OpenImage", []interface{}{knownNotManifestListedImage_x8664})
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(fd, check.IsNil)
|
||||
|
||||
imgidv, ok := v.(float64)
|
||||
c.Assert(ok, check.Equals, true)
|
||||
if !ok {
|
||||
return fmt.Errorf("OpenImage return value is %T", v)
|
||||
}
|
||||
imgid := uint32(imgidv)
|
||||
|
||||
v, fd, err = p.call("GetManifest", []interface{}{imgid})
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(fd, check.NotNil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fd == nil {
|
||||
return fmt.Errorf("expected GetManifest fd")
|
||||
}
|
||||
fetchchan := make(chan byteFetch)
|
||||
go func() {
|
||||
manifestBytes, err := ioutil.ReadAll(fd.fd)
|
||||
@ -217,15 +229,36 @@ func (s *ProxySuite) TestProxy(c *check.C) {
|
||||
}
|
||||
}()
|
||||
_, _, err = p.call("FinishPipe", []interface{}{fd.id})
|
||||
c.Assert(err, check.IsNil)
|
||||
fetchRes := <-fetchchan
|
||||
c.Assert(fetchRes.err, check.IsNil)
|
||||
|
||||
_, err = manifest.OCI1FromManifest(fetchRes.content)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
td, err := ioutil.TempDir("", "skopeo-proxy")
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
c.Assert(initOci(td), check.IsNil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fetchRes := <-fetchchan
|
||||
if fetchRes.err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = manifest.OCI1FromManifest(fetchRes.content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = p.call("CloseImage", []interface{}{imgid})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ProxySuite) TestProxy(c *check.C) {
|
||||
p, err := newProxy()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = runTestGetManifest(p, knownNotManifestListedImage_x8664)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Testing image %s: %v", knownNotManifestListedImage_x8664, err)
|
||||
}
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = runTestGetManifest(p, knownListImage)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Testing image %s: %v", knownListImage, err)
|
||||
}
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user