diff --git a/cmd/skopeo/proxy.go b/cmd/skopeo/proxy.go index dd0cf123..ee46f47f 100644 --- a/cmd/skopeo/proxy.go +++ b/cmd/skopeo/proxy.go @@ -92,7 +92,8 @@ import ( // 0.2.2: Added support for fetching image configuration as OCI // 0.2.3: Added GetFullConfig // 0.2.4: Added OpenImageOptional -const protocolVersion = "0.2.4" +// 0.2.5: Added LayerInfoJSON +const protocolVersion = "0.2.5" // maxMsgSize is the current limit on a packet size. // Note that all non-metadata (i.e. payload data) is sent over a pipe. @@ -173,6 +174,14 @@ type proxyHandler struct { activePipes map[uint32]*activePipe } +// convertedLayerInfo is the reduced form of the OCI type BlobInfo +// Used in the return value of GetLayerInfo +type convertedLayerInfo struct { + Digest digest.Digest `json:"digest"` + Size int64 `json:"size"` + MediaType string `json:"media_type"` +} + // Initialize performs one-time initialization, and returns the protocol version func (h *proxyHandler) Initialize(args []interface{}) (replyBuf, error) { h.lock.Lock() @@ -614,6 +623,56 @@ func (h *proxyHandler) GetBlob(args []interface{}) (replyBuf, error) { return ret, nil } +// 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). +func (h *proxyHandler) GetLayerInfo(args []interface{}) (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() + } + + var layers []convertedLayerInfo + for _, layer := range layerInfos { + layers = append(layers, convertedLayerInfo{layer.Digest, layer.Size, layer.MediaType}) + } + + ret.value = layers + return ret, nil +} + // FinishPipe waits for the worker goroutine to finish, and closes the write side of the pipe. func (h *proxyHandler) FinishPipe(args []interface{}) (replyBuf, error) { h.lock.Lock() @@ -736,6 +795,8 @@ func (h *proxyHandler) processRequest(readBytes []byte) (rb replyBuf, terminate rb, err = h.GetFullConfig(req.Args) case "GetBlob": rb, err = h.GetBlob(req.Args) + case "GetLayerInfo": + rb, err = h.GetLayerInfo(req.Args) case "FinishPipe": rb, err = h.FinishPipe(req.Args) case "Shutdown":