github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/imgutil/converter/zstd.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package converter 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 24 "github.com/containerd/containerd/content" 25 "github.com/containerd/containerd/errdefs" 26 "github.com/containerd/containerd/images" 27 "github.com/containerd/containerd/images/converter" 28 "github.com/containerd/containerd/images/converter/uncompress" 29 "github.com/containerd/log" 30 "github.com/containerd/nerdctl/v2/pkg/api/types" 31 "github.com/klauspost/compress/zstd" 32 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 33 ) 34 35 // ZstdLayerConvertFunc converts legacy tar.gz layers into zstd layers with 36 // the specified compression level. 37 func ZstdLayerConvertFunc(options types.ImageConvertOptions) (converter.ConvertFunc, error) { 38 return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) { 39 if !images.IsLayerType(desc.MediaType) { 40 // No conversion. No need to return an error here. 41 return nil, nil 42 } 43 uncompressedDesc := &desc 44 if !uncompress.IsUncompressedType(desc.MediaType) { 45 var err error 46 uncompressedDesc, err = uncompress.LayerConvertFunc(ctx, cs, desc) 47 if err != nil { 48 return nil, err 49 } 50 if uncompressedDesc == nil { 51 return nil, fmt.Errorf("unexpectedly got the same blob after compression (%s, %q)", desc.Digest, desc.MediaType) 52 } 53 defer func() { 54 if err := cs.Delete(ctx, uncompressedDesc.Digest); err != nil { 55 log.L.WithError(err).WithField("uncompressedDesc", uncompressedDesc).Warn("failed to remove tmp uncompressed layer") 56 } 57 }() 58 log.L.Debugf("zstd: uncompressed %s into %s", desc.Digest, uncompressedDesc.Digest) 59 } 60 61 info, err := cs.Info(ctx, desc.Digest) 62 if err != nil { 63 return nil, err 64 } 65 readerAt, err := cs.ReaderAt(ctx, *uncompressedDesc) 66 if err != nil { 67 return nil, err 68 } 69 defer readerAt.Close() 70 sr := io.NewSectionReader(readerAt, 0, uncompressedDesc.Size) 71 ref := fmt.Sprintf("convert-zstd-from-%s", desc.Digest) 72 w, err := content.OpenWriter(ctx, cs, content.WithRef(ref)) 73 if err != nil { 74 return nil, err 75 } 76 defer w.Close() 77 78 // Reset the writing position 79 // Old writer possibly remains without aborted 80 // (e.g. conversion interrupted by a signal) 81 if err := w.Truncate(0); err != nil { 82 return nil, err 83 } 84 85 pr, pw := io.Pipe() 86 enc, err := zstd.NewWriter(pw, zstd.WithEncoderLevel(zstd.EncoderLevelFromZstd(options.ZstdCompressionLevel))) 87 if err != nil { 88 return nil, err 89 } 90 go func() { 91 if _, err := io.Copy(enc, sr); err != nil { 92 pr.CloseWithError(err) 93 return 94 } 95 if err = enc.Close(); err != nil { 96 pr.CloseWithError(err) 97 return 98 } 99 if err = pw.Close(); err != nil { 100 pr.CloseWithError(err) 101 return 102 } 103 }() 104 105 n, err := io.Copy(w, pr) 106 if err != nil { 107 return nil, err 108 } 109 110 if err = w.Commit(ctx, 0, "", content.WithLabels(info.Labels)); err != nil && !errdefs.IsAlreadyExists(err) { 111 return nil, err 112 } 113 if err := w.Close(); err != nil { 114 return nil, err 115 } 116 newDesc := desc 117 newDesc.Digest = w.Digest() 118 newDesc.Size = n 119 newDesc.MediaType = ocispec.MediaTypeImageLayerZstd 120 return &newDesc, nil 121 }, nil 122 }