github.com/demonoid81/containerd@v1.3.4/rootfs/apply.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 rootfs
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"fmt"
    23  	"math/rand"
    24  	"time"
    25  
    26  	"github.com/containerd/containerd/diff"
    27  	"github.com/containerd/containerd/errdefs"
    28  	"github.com/containerd/containerd/log"
    29  	"github.com/containerd/containerd/mount"
    30  	"github.com/containerd/containerd/snapshots"
    31  	"github.com/opencontainers/go-digest"
    32  	"github.com/opencontainers/image-spec/identity"
    33  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    34  	"github.com/pkg/errors"
    35  )
    36  
    37  // Layer represents the descriptors for a layer diff. These descriptions
    38  // include the descriptor for the uncompressed tar diff as well as a blob
    39  // used to transport that tar. The blob descriptor may or may not describe
    40  // a compressed object.
    41  type Layer struct {
    42  	Diff ocispec.Descriptor
    43  	Blob ocispec.Descriptor
    44  }
    45  
    46  // ApplyLayers applies all the layers using the given snapshotter and applier.
    47  // The returned result is a chain id digest representing all the applied layers.
    48  // Layers are applied in order they are given, making the first layer the
    49  // bottom-most layer in the layer chain.
    50  func ApplyLayers(ctx context.Context, layers []Layer, sn snapshots.Snapshotter, a diff.Applier) (digest.Digest, error) {
    51  	return ApplyLayersWithOpts(ctx, layers, sn, a, nil)
    52  }
    53  
    54  // ApplyLayersWithOpts applies all the layers using the given snapshotter, applier, and apply opts.
    55  // The returned result is a chain id digest representing all the applied layers.
    56  // Layers are applied in order they are given, making the first layer the
    57  // bottom-most layer in the layer chain.
    58  func ApplyLayersWithOpts(ctx context.Context, layers []Layer, sn snapshots.Snapshotter, a diff.Applier, applyOpts []diff.ApplyOpt) (digest.Digest, error) {
    59  	chain := make([]digest.Digest, len(layers))
    60  	for i, layer := range layers {
    61  		chain[i] = layer.Diff.Digest
    62  	}
    63  	chainID := identity.ChainID(chain)
    64  
    65  	// Just stat top layer, remaining layers will have their existence checked
    66  	// on prepare. Calling prepare on upper layers first guarantees that upper
    67  	// layers are not removed while calling stat on lower layers
    68  	_, err := sn.Stat(ctx, chainID.String())
    69  	if err != nil {
    70  		if !errdefs.IsNotFound(err) {
    71  			return "", errors.Wrapf(err, "failed to stat snapshot %s", chainID)
    72  		}
    73  
    74  		if err := applyLayers(ctx, layers, chain, sn, a, nil, applyOpts); err != nil && !errdefs.IsAlreadyExists(err) {
    75  			return "", err
    76  		}
    77  	}
    78  
    79  	return chainID, nil
    80  }
    81  
    82  // ApplyLayer applies a single layer on top of the given provided layer chain,
    83  // using the provided snapshotter and applier. If the layer was unpacked true
    84  // is returned, if the layer already exists false is returned.
    85  func ApplyLayer(ctx context.Context, layer Layer, chain []digest.Digest, sn snapshots.Snapshotter, a diff.Applier, opts ...snapshots.Opt) (bool, error) {
    86  	return ApplyLayerWithOpts(ctx, layer, chain, sn, a, opts, nil)
    87  }
    88  
    89  // ApplyLayerWithOpts applies a single layer on top of the given provided layer chain,
    90  // using the provided snapshotter, applier, and apply opts. If the layer was unpacked true
    91  // is returned, if the layer already exists false is returned.
    92  func ApplyLayerWithOpts(ctx context.Context, layer Layer, chain []digest.Digest, sn snapshots.Snapshotter, a diff.Applier, opts []snapshots.Opt, applyOpts []diff.ApplyOpt) (bool, error) {
    93  	var (
    94  		chainID = identity.ChainID(append(chain, layer.Diff.Digest)).String()
    95  		applied bool
    96  	)
    97  	if _, err := sn.Stat(ctx, chainID); err != nil {
    98  		if !errdefs.IsNotFound(err) {
    99  			return false, errors.Wrapf(err, "failed to stat snapshot %s", chainID)
   100  		}
   101  
   102  		if err := applyLayers(ctx, []Layer{layer}, append(chain, layer.Diff.Digest), sn, a, opts, applyOpts); err != nil {
   103  			if !errdefs.IsAlreadyExists(err) {
   104  				return false, err
   105  			}
   106  		} else {
   107  			applied = true
   108  		}
   109  	}
   110  	return applied, nil
   111  
   112  }
   113  
   114  func applyLayers(ctx context.Context, layers []Layer, chain []digest.Digest, sn snapshots.Snapshotter, a diff.Applier, opts []snapshots.Opt, applyOpts []diff.ApplyOpt) error {
   115  	var (
   116  		parent  = identity.ChainID(chain[:len(chain)-1])
   117  		chainID = identity.ChainID(chain)
   118  		layer   = layers[len(layers)-1]
   119  		diff    ocispec.Descriptor
   120  		key     string
   121  		mounts  []mount.Mount
   122  		err     error
   123  	)
   124  
   125  	for {
   126  		key = fmt.Sprintf("extract-%s %s", uniquePart(), chainID)
   127  
   128  		// Prepare snapshot with from parent, label as root
   129  		mounts, err = sn.Prepare(ctx, key, parent.String(), opts...)
   130  		if err != nil {
   131  			if errdefs.IsNotFound(err) && len(layers) > 1 {
   132  				if err := applyLayers(ctx, layers[:len(layers)-1], chain[:len(chain)-1], sn, a, nil, applyOpts); err != nil {
   133  					if !errdefs.IsAlreadyExists(err) {
   134  						return err
   135  					}
   136  				}
   137  				// Do no try applying layers again
   138  				layers = nil
   139  				continue
   140  			} else if errdefs.IsAlreadyExists(err) {
   141  				// Try a different key
   142  				continue
   143  			}
   144  
   145  			// Already exists should have the caller retry
   146  			return errors.Wrapf(err, "failed to prepare extraction snapshot %q", key)
   147  
   148  		}
   149  		break
   150  	}
   151  	defer func() {
   152  		if err != nil {
   153  			if !errdefs.IsAlreadyExists(err) {
   154  				log.G(ctx).WithError(err).WithField("key", key).Infof("apply failure, attempting cleanup")
   155  			}
   156  
   157  			if rerr := sn.Remove(ctx, key); rerr != nil {
   158  				log.G(ctx).WithError(rerr).WithField("key", key).Warnf("extraction snapshot removal failed")
   159  			}
   160  		}
   161  	}()
   162  
   163  	diff, err = a.Apply(ctx, layer.Blob, mounts, applyOpts...)
   164  	if err != nil {
   165  		err = errors.Wrapf(err, "failed to extract layer %s", layer.Diff.Digest)
   166  		return err
   167  	}
   168  	if diff.Digest != layer.Diff.Digest {
   169  		err = errors.Errorf("wrong diff id calculated on extraction %q", diff.Digest)
   170  		return err
   171  	}
   172  
   173  	if err = sn.Commit(ctx, chainID.String(), key, opts...); err != nil {
   174  		err = errors.Wrapf(err, "failed to commit snapshot %s", key)
   175  		return err
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  func uniquePart() string {
   182  	t := time.Now()
   183  	var b [3]byte
   184  	// Ignore read failures, just decreases uniqueness
   185  	rand.Read(b[:])
   186  	return fmt.Sprintf("%d-%s", t.Nanosecond(), base64.URLEncoding.EncodeToString(b[:]))
   187  }