github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/casext/gc.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016, 2017, 2018 SUSE LLC. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package casext 19 20 import ( 21 "github.com/apex/log" 22 "github.com/opencontainers/go-digest" 23 ispec "github.com/opencontainers/image-spec/specs-go/v1" 24 "github.com/pkg/errors" 25 "golang.org/x/net/context" 26 ) 27 28 // GC will perform a mark-and-sweep garbage collection of the OCI image 29 // referenced by the given CAS engine. The root set is taken to be the set of 30 // references stored in the image, and all blobs not reachable by following a 31 // descriptor path from the root set will be removed. 32 // 33 // GC will only call ListBlobs and ListReferences once, and assumes that there 34 // is no change in the set of references or blobs after calling those 35 // functions. In other words, it assumes it is the only user of the image that 36 // is making modifications. Things will not go well if this assumption is 37 // challenged. 38 func (e Engine) GC(ctx context.Context) error { 39 // Generate the root set of descriptors. 40 var root []ispec.Descriptor 41 42 names, err := e.ListReferences(ctx) 43 if err != nil { 44 return errors.Wrap(err, "get roots") 45 } 46 47 for _, name := range names { 48 // TODO: This code is no longer necessary once we have index.json. 49 descriptorPaths, err := e.ResolveReference(ctx, name) 50 if err != nil { 51 return errors.Wrapf(err, "get root %s", name) 52 } 53 if len(descriptorPaths) == 0 { 54 return errors.Errorf("tag not found: %s", name) 55 } 56 if len(descriptorPaths) != 1 { 57 // TODO: Handle this more nicely. 58 return errors.Errorf("tag is ambiguous: %s", name) 59 } 60 descriptor := descriptorPaths[0].Descriptor() 61 log.WithFields(log.Fields{ 62 "name": name, 63 "digest": descriptor.Digest, 64 }).Debugf("GC: got reference") 65 root = append(root, descriptor) 66 } 67 68 // Mark from the root sets. 69 black := map[digest.Digest]struct{}{} 70 for idx, descriptor := range root { 71 log.WithFields(log.Fields{ 72 "digest": descriptor.Digest, 73 }).Debugf("GC: marking from root") 74 75 reachables, err := e.Reachable(ctx, descriptor) 76 if err != nil { 77 return errors.Wrapf(err, "getting reachables from root %d", idx) 78 } 79 for _, reachable := range reachables { 80 black[reachable] = struct{}{} 81 } 82 } 83 84 // Sweep all blobs in the white set. 85 blobs, err := e.ListBlobs(ctx) 86 if err != nil { 87 return errors.Wrap(err, "get blob list") 88 } 89 90 n := 0 91 for _, digest := range blobs { 92 if _, ok := black[digest]; ok { 93 // Digest is in the black set. 94 continue 95 } 96 log.Infof("garbage collecting blob: %s", digest) 97 98 if err := e.DeleteBlob(ctx, digest); err != nil { 99 return errors.Wrapf(err, "remove unmarked blob %s", digest) 100 } 101 n++ 102 } 103 104 // Finally, tell CAS to GC it. 105 if err := e.Clean(ctx); err != nil { 106 return errors.Wrapf(err, "clean engine") 107 } 108 109 log.Debugf("garbage collected %d blobs", n) 110 return nil 111 }