diff --git a/cmd/skopeo/proxy.go b/cmd/skopeo/proxy.go index b1389396..f37fb22f 100644 --- a/cmd/skopeo/proxy.go +++ b/cmd/skopeo/proxy.go @@ -93,7 +93,8 @@ import ( // 0.2.4: Added OpenImageOptional // 0.2.5: Added LayerInfoJSON // 0.2.6: Policy Verification before pulling OCI -const protocolVersion = "0.2.6" +// 0.2.7: Added GetLayerInfoPiped +const protocolVersion = "0.2.7" // maxMsgSize is the current limit on a packet size. // Note that all non-metadata (i.e. payload data) is sent over a pipe. @@ -619,9 +620,10 @@ func (h *proxyHandler) GetBlob(args []any) (replyBuf, error) { // GetLayerInfo returns data about the layers of an image, useful for reading the layer contents. // -// This needs to be called since the data returned by GetManifest() does not allow to correctly -// calling GetBlob() for the containers-storage: transport (which doesn’t store the original compressed -// representations referenced in the manifest). +// This is the same as GetLayerInfoPiped, but returns its contents inline. This is subject to +// failure for large images (because we use SOCK_SEQPACKET which has a maximum buffer size) +// and is hence only retained for backwards compatibility. Callers are expected to use +// the semver to know whether they can call the new API. func (h *proxyHandler) GetLayerInfo(args []any) (replyBuf, error) { h.lock.Lock() defer h.lock.Unlock() @@ -667,6 +669,59 @@ func (h *proxyHandler) GetLayerInfo(args []any) (replyBuf, error) { return ret, nil } +// GetLayerInfoPiped returns data about the layers of an image, useful for reading the layer contents. +// +// This needs to be called since the data returned by GetManifest() does not allow to correctly +// calling GetBlob() for the containers-storage: transport (which doesn’t store the original compressed +// representations referenced in the manifest). +func (h *proxyHandler) GetLayerInfoPiped(args []any) (replyBuf, error) { + h.lock.Lock() + defer h.lock.Unlock() + + var ret replyBuf + + if h.sysctx == nil { + return ret, fmt.Errorf("client error: must invoke Initialize") + } + + if len(args) != 1 { + return ret, fmt.Errorf("found %d args, expecting (imgid)", len(args)) + } + + imgref, err := h.parseImageFromID(args[0]) + if err != nil { + return ret, err + } + + ctx := context.TODO() + + err = h.cacheTargetManifest(imgref) + if err != nil { + return ret, err + } + img := imgref.cachedimg + + layerInfos, err := img.LayerInfosForCopy(ctx) + if err != nil { + return ret, err + } + + if layerInfos == nil { + layerInfos = img.LayerInfos() + } + + layers := make([]convertedLayerInfo, 0, len(layerInfos)) + for _, layer := range layerInfos { + layers = append(layers, convertedLayerInfo{layer.Digest, layer.Size, layer.MediaType}) + } + + serialized, err := json.Marshal(&layers) + if err != nil { + return ret, err + } + return h.returnBytes(nil, serialized) +} + // FinishPipe waits for the worker goroutine to finish, and closes the write side of the pipe. func (h *proxyHandler) FinishPipe(args []any) (replyBuf, error) { h.lock.Lock() @@ -806,6 +861,8 @@ func (h *proxyHandler) processRequest(readBytes []byte) (rb replyBuf, terminate rb, err = h.GetBlob(req.Args) case "GetLayerInfo": rb, err = h.GetLayerInfo(req.Args) + case "GetLayerInfoPiped": + rb, err = h.GetLayerInfoPiped(req.Args) case "FinishPipe": rb, err = h.FinishPipe(req.Args) case "Shutdown": diff --git a/integration/proxy_test.go b/integration/proxy_test.go index 826128da..7d973d85 100644 --- a/integration/proxy_test.go +++ b/integration/proxy_test.go @@ -229,7 +229,8 @@ type byteFetch struct { err error } -func runTestGetManifestAndConfig(p *proxy, img string) error { +// This exercises all the metadata fetching APIs. +func runTestMetadataAPIs(p *proxy, img string) error { v, err := p.callNoFd("OpenImage", []any{img}) if err != nil { return err @@ -291,6 +292,19 @@ func runTestGetManifestAndConfig(p *proxy, img string) error { return fmt.Errorf("No CMD or ENTRYPOINT set") } + _, layerInfoBytes, err := p.callReadAllBytes("GetLayerInfoPiped", []any{imgid}) + if err != nil { + return err + } + var layerInfoBytesData []interface{} + err = json.Unmarshal(layerInfoBytes, &layerInfoBytesData) + if err != nil { + return err + } + if len(layerInfoBytesData) == 0 { + return fmt.Errorf("expected layer info data") + } + // Also test this legacy interface _, ctrconfigBytes, err := p.callReadAllBytes("GetConfig", []any{imgid}) if err != nil { @@ -337,13 +351,13 @@ func (s *proxySuite) TestProxy() { p, err := newProxy() require.NoError(t, err) - err = runTestGetManifestAndConfig(p, knownNotManifestListedImageX8664) + err = runTestMetadataAPIs(p, knownNotManifestListedImageX8664) if err != nil { err = fmt.Errorf("Testing image %s: %v", knownNotManifestListedImageX8664, err) } assert.NoError(t, err) - err = runTestGetManifestAndConfig(p, knownListImage) + err = runTestMetadataAPIs(p, knownListImage) if err != nil { err = fmt.Errorf("Testing image %s: %v", knownListImage, err) }