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 17package rootfs 18 19import ( 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. 41type 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. 50func ApplyLayers(ctx context.Context, layers []Layer, sn snapshots.Snapshotter, a diff.Applier) (digest.Digest, error) { 51 chain := make([]digest.Digest, len(layers)) 52 for i, layer := range layers { 53 chain[i] = layer.Diff.Digest 54 } 55 chainID := identity.ChainID(chain) 56 57 // Just stat top layer, remaining layers will have their existence checked 58 // on prepare. Calling prepare on upper layers first guarantees that upper 59 // layers are not removed while calling stat on lower layers 60 _, err := sn.Stat(ctx, chainID.String()) 61 if err != nil { 62 if !errdefs.IsNotFound(err) { 63 return "", errors.Wrapf(err, "failed to stat snapshot %s", chainID) 64 } 65 66 if err := applyLayers(ctx, layers, chain, sn, a); err != nil && !errdefs.IsAlreadyExists(err) { 67 return "", err 68 } 69 } 70 71 return chainID, nil 72} 73 74// ApplyLayer applies a single layer on top of the given provided layer chain, 75// using the provided snapshotter and applier. If the layer was unpacked true 76// is returned, if the layer already exists false is returned. 77func ApplyLayer(ctx context.Context, layer Layer, chain []digest.Digest, sn snapshots.Snapshotter, a diff.Applier, opts ...snapshots.Opt) (bool, error) { 78 var ( 79 chainID = identity.ChainID(append(chain, layer.Diff.Digest)).String() 80 applied bool 81 ) 82 if _, err := sn.Stat(ctx, chainID); err != nil { 83 if !errdefs.IsNotFound(err) { 84 return false, errors.Wrapf(err, "failed to stat snapshot %s", chainID) 85 } 86 87 if err := applyLayers(ctx, []Layer{layer}, append(chain, layer.Diff.Digest), sn, a, opts...); err != nil { 88 if !errdefs.IsAlreadyExists(err) { 89 return false, err 90 } 91 } else { 92 applied = true 93 } 94 } 95 return applied, nil 96} 97 98func applyLayers(ctx context.Context, layers []Layer, chain []digest.Digest, sn snapshots.Snapshotter, a diff.Applier, opts ...snapshots.Opt) error { 99 var ( 100 parent = identity.ChainID(chain[:len(chain)-1]) 101 chainID = identity.ChainID(chain) 102 layer = layers[len(layers)-1] 103 diff ocispec.Descriptor 104 key string 105 mounts []mount.Mount 106 err error 107 ) 108 109 for { 110 key = fmt.Sprintf("extract-%s %s", uniquePart(), chainID) 111 112 // Prepare snapshot with from parent, label as root 113 mounts, err = sn.Prepare(ctx, key, parent.String(), opts...) 114 if err != nil { 115 if errdefs.IsNotFound(err) && len(layers) > 1 { 116 if err := applyLayers(ctx, layers[:len(layers)-1], chain[:len(chain)-1], sn, a); err != nil { 117 if !errdefs.IsAlreadyExists(err) { 118 return err 119 } 120 } 121 // Do no try applying layers again 122 layers = nil 123 continue 124 } else if errdefs.IsAlreadyExists(err) { 125 // Try a different key 126 continue 127 } 128 129 // Already exists should have the caller retry 130 return errors.Wrapf(err, "failed to prepare extraction snapshot %q", key) 131 132 } 133 break 134 } 135 defer func() { 136 if err != nil { 137 if !errdefs.IsAlreadyExists(err) { 138 log.G(ctx).WithError(err).WithField("key", key).Infof("apply failure, attempting cleanup") 139 } 140 141 if rerr := sn.Remove(ctx, key); rerr != nil { 142 log.G(ctx).WithError(rerr).WithField("key", key).Warnf("extraction snapshot removal failed") 143 } 144 } 145 }() 146 147 diff, err = a.Apply(ctx, layer.Blob, mounts) 148 if err != nil { 149 err = errors.Wrapf(err, "failed to extract layer %s", layer.Diff.Digest) 150 return err 151 } 152 if diff.Digest != layer.Diff.Digest { 153 err = errors.Errorf("wrong diff id calculated on extraction %q", diff.Digest) 154 return err 155 } 156 157 if err = sn.Commit(ctx, chainID.String(), key, opts...); err != nil { 158 err = errors.Wrapf(err, "failed to commit snapshot %s", key) 159 return err 160 } 161 162 return nil 163} 164 165func uniquePart() string { 166 t := time.Now() 167 var b [3]byte 168 // Ignore read failures, just decreases uniqueness 169 rand.Read(b[:]) 170 return fmt.Sprintf("%d-%s", t.Nanosecond(), base64.URLEncoding.EncodeToString(b[:])) 171} 172