mirror of
https://github.com/containers/skopeo.git
synced 2025-07-17 08:11:50 +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 is an opaque integer handle
|
||||||
id uint32
|
id uint32
|
||||||
src types.ImageSource
|
src types.ImageSource
|
||||||
img types.Image
|
cachedimg types.Image
|
||||||
}
|
}
|
||||||
|
|
||||||
// proxyHandler is the state associated with our socket.
|
// proxyHandler is the state associated with our socket.
|
||||||
@ -219,16 +219,11 @@ func (h *proxyHandler) OpenImage(args []interface{}) (replyBuf, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ret, err
|
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++
|
h.imageSerial++
|
||||||
openimg := &openImage{
|
openimg := &openImage{
|
||||||
id: h.imageSerial,
|
id: h.imageSerial,
|
||||||
src: imgsrc,
|
src: imgsrc,
|
||||||
img: img,
|
|
||||||
}
|
}
|
||||||
h.images[openimg.id] = openimg
|
h.images[openimg.id] = openimg
|
||||||
ret.value = openimg.id
|
ret.value = openimg.id
|
||||||
@ -326,7 +321,46 @@ func (h *proxyHandler) returnBytes(retval interface{}, buf []byte) (replyBuf, er
|
|||||||
return ret, nil
|
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.
|
// 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) {
|
func (h *proxyHandler) GetManifest(args []interface{}) (replyBuf, error) {
|
||||||
h.lock.Lock()
|
h.lock.Lock()
|
||||||
defer h.lock.Unlock()
|
defer h.lock.Unlock()
|
||||||
@ -344,11 +378,18 @@ func (h *proxyHandler) GetManifest(args []interface{}) (replyBuf, error) {
|
|||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
err = h.cacheTargetManifest(imgref)
|
||||||
rawManifest, manifestType, err := imgref.img.Manifest(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ret, err
|
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
|
// We only support OCI and docker2schema2. We know docker2schema2 can be easily+cheaply
|
||||||
// converted into OCI, so consumers only need to see OCI.
|
// converted into OCI, so consumers only need to see OCI.
|
||||||
switch manifestType {
|
switch manifestType {
|
||||||
@ -373,7 +414,7 @@ func (h *proxyHandler) GetManifest(args []interface{}) (replyBuf, error) {
|
|||||||
// docker schema and MIME types.
|
// docker schema and MIME types.
|
||||||
if manifestType != imgspecv1.MediaTypeImageManifest {
|
if manifestType != imgspecv1.MediaTypeImageManifest {
|
||||||
manifestUpdates := types.ManifestUpdateOptions{ManifestMIMEType: imgspecv1.MediaTypeImageManifest}
|
manifestUpdates := types.ManifestUpdateOptions{ManifestMIMEType: imgspecv1.MediaTypeImageManifest}
|
||||||
ociImage, err := imgref.img.UpdatedImage(ctx, manifestUpdates)
|
ociImage, err := img.UpdatedImage(ctx, manifestUpdates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
@ -406,9 +447,14 @@ func (h *proxyHandler) GetConfig(args []interface{}) (replyBuf, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
err = h.cacheTargetManifest(imgref)
|
||||||
|
if err != nil {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
img := imgref.cachedimg
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
config, err := imgref.img.OCIConfig(ctx)
|
config, err := img.OCIConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
@ -145,9 +145,25 @@ func newProxy() (*proxy, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &proxy{
|
p := &proxy{
|
||||||
c: mysock.(*net.UnixConn),
|
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() {
|
func init() {
|
||||||
@ -182,32 +198,28 @@ type byteFetch struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProxySuite) TestProxy(c *check.C) {
|
func runTestGetManifest(p *proxy, img string) error {
|
||||||
p, err := newProxy()
|
v, fd, err := p.call("OpenImage", []interface{}{knownNotManifestListedImage_x8664})
|
||||||
c.Assert(err, check.IsNil)
|
if err != nil {
|
||||||
|
return err
|
||||||
v, fd, err := p.call("Initialize", nil)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
semver, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
c.Fatalf("Unexpected value %T", v)
|
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(semver, expectedProxySemverMajor) {
|
if fd != nil {
|
||||||
c.Fatalf("Unexpected semver %s", semver)
|
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)
|
imgidv, ok := v.(float64)
|
||||||
c.Assert(ok, check.Equals, true)
|
if !ok {
|
||||||
|
return fmt.Errorf("OpenImage return value is %T", v)
|
||||||
|
}
|
||||||
imgid := uint32(imgidv)
|
imgid := uint32(imgidv)
|
||||||
|
|
||||||
v, fd, err = p.call("GetManifest", []interface{}{imgid})
|
v, fd, err = p.call("GetManifest", []interface{}{imgid})
|
||||||
c.Assert(err, check.IsNil)
|
if err != nil {
|
||||||
c.Assert(fd, check.NotNil)
|
return err
|
||||||
|
}
|
||||||
|
if fd == nil {
|
||||||
|
return fmt.Errorf("expected GetManifest fd")
|
||||||
|
}
|
||||||
fetchchan := make(chan byteFetch)
|
fetchchan := make(chan byteFetch)
|
||||||
go func() {
|
go func() {
|
||||||
manifestBytes, err := ioutil.ReadAll(fd.fd)
|
manifestBytes, err := ioutil.ReadAll(fd.fd)
|
||||||
@ -217,15 +229,36 @@ func (s *ProxySuite) TestProxy(c *check.C) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
_, _, err = p.call("FinishPipe", []interface{}{fd.id})
|
_, _, err = p.call("FinishPipe", []interface{}{fd.id})
|
||||||
c.Assert(err, check.IsNil)
|
if err != nil {
|
||||||
fetchRes := <-fetchchan
|
return err
|
||||||
c.Assert(fetchRes.err, check.IsNil)
|
}
|
||||||
|
fetchRes := <-fetchchan
|
||||||
_, err = manifest.OCI1FromManifest(fetchRes.content)
|
if fetchRes.err != nil {
|
||||||
c.Assert(err, check.IsNil)
|
return err
|
||||||
|
}
|
||||||
td, err := ioutil.TempDir("", "skopeo-proxy")
|
_, err = manifest.OCI1FromManifest(fetchRes.content)
|
||||||
defer os.RemoveAll(td)
|
if err != nil {
|
||||||
|
return err
|
||||||
c.Assert(initOci(td), check.IsNil)
|
}
|
||||||
|
|
||||||
|
_, _, 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