diff --git a/cmd/skopeo/proxy.go b/cmd/skopeo/proxy.go index a0565f70..34d2509f 100644 --- a/cmd/skopeo/proxy.go +++ b/cmd/skopeo/proxy.go @@ -143,9 +143,9 @@ type activePipe struct { // openImage is an opened image reference type openImage struct { // id is an opaque integer handle - id uint32 - src types.ImageSource - img types.Image + id uint32 + src types.ImageSource + 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 } diff --git a/integration/proxy_test.go b/integration/proxy_test.go index cbf6672f..10ad276a 100644 --- a/integration/proxy_test.go +++ b/integration/proxy_test.go @@ -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) + if err != nil { + return err + } fetchRes := <-fetchchan - c.Assert(fetchRes.err, check.IsNil) - + 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) - td, err := ioutil.TempDir("", "skopeo-proxy") - defer os.RemoveAll(td) + err = runTestGetManifest(p, knownNotManifestListedImage_x8664) + if err != nil { + err = fmt.Errorf("Testing image %s: %v", knownNotManifestListedImage_x8664, err) + } + c.Assert(err, check.IsNil) - c.Assert(initOci(td), check.IsNil) + err = runTestGetManifest(p, knownListImage) + if err != nil { + err = fmt.Errorf("Testing image %s: %v", knownListImage, err) + } + c.Assert(err, check.IsNil) }