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  }