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 }