github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/casext/walk.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 "errors" 22 23 "github.com/apex/log" 24 "github.com/openSUSE/umoci/oci/cas" 25 "github.com/opencontainers/go-digest" 26 ispec "github.com/opencontainers/image-spec/specs-go/v1" 27 "golang.org/x/net/context" 28 ) 29 30 // childDescriptors is a wrapper around MapDescriptors which just creates a 31 // slice of all of the arguments, and doesn't modify them. 32 func childDescriptors(i interface{}) []ispec.Descriptor { 33 var children []ispec.Descriptor 34 if err := MapDescriptors(i, func(descriptor ispec.Descriptor) ispec.Descriptor { 35 children = append(children, descriptor) 36 return descriptor 37 }); err != nil { 38 // If we got an error, this is a bug in MapDescriptors proper. 39 log.Fatalf("[internal error] MapDescriptors returned an error inside childDescriptors: %+v", err) 40 } 41 return children 42 } 43 44 // walkState stores state information about the recursion into a given 45 // descriptor tree. 46 type walkState struct { 47 // engine is the CAS engine we are operating on. 48 engine Engine 49 50 // walkFunc is the WalkFunc provided by the user. 51 walkFunc WalkFunc 52 } 53 54 // DescriptorPath is used to describe the path of descriptors (from a top-level 55 // index) that were traversed when resolving a particular reference name. The 56 // purpose of this is to allow libraries like github.com/openSUSE/umoci/mutate 57 // to handle generic manifest updates given an arbitrary descriptor walk. Users 58 // of ResolveReference that don't care about the descriptor path can just use 59 // .Descriptor. 60 type DescriptorPath struct { 61 // Walk is the set of descriptors walked to reach Descriptor (inclusive). 62 // The order is the same as the order of the walk, with the target being 63 // the last entry and the entrypoint from index.json being the first. 64 Walk []ispec.Descriptor `json:"descriptor_walk"` 65 } 66 67 // Root returns the first step in the DescriptorPath, which is the point where 68 // the walk started. This is just shorthand for DescriptorPath.Walk[0]. Root 69 // will *panic* if DescriptorPath is invalid. 70 func (d DescriptorPath) Root() ispec.Descriptor { 71 if len(d.Walk) < 1 { 72 panic("empty DescriptorPath") 73 } 74 return d.Walk[0] 75 } 76 77 // Descriptor returns the final step in the DescriptorPath, which is the target 78 // descriptor being referenced by DescriptorPath. This is just shorthand for 79 // accessing the last entry of DescriptorPath.Walk. Descriptor will *panic* if 80 // DescriptorPath is invalid. 81 func (d DescriptorPath) Descriptor() ispec.Descriptor { 82 if len(d.Walk) < 1 { 83 panic("empty DescriptorPath") 84 } 85 return d.Walk[len(d.Walk)-1] 86 } 87 88 // ErrSkipDescriptor is a special error returned by WalkFunc which will cause 89 // Walk to not recurse into the descriptor currently being evaluated by 90 // WalkFunc. This interface is roughly equivalent to filepath.SkipDir. 91 var ErrSkipDescriptor = errors.New("[internal] do not recurse into descriptor") 92 93 // WalkFunc is the type of function passed to Walk. It will be a called on each 94 // descriptor encountered, recursively -- which may involve the function being 95 // called on the same descriptor multiple times (though because an OCI image is 96 // a Merkle tree there will never be any loops). If an error is returned by 97 // WalkFunc, the recursion will halt and the error will bubble up to the 98 // caller. 99 // 100 // TODO: Also provide Blob to WalkFunc so that callers don't need to load blobs 101 // more than once. This is quite important for remote CAS implementations. 102 type WalkFunc func(descriptorPath DescriptorPath) error 103 104 func (ws *walkState) recurse(ctx context.Context, descriptorPath DescriptorPath) error { 105 log.WithFields(log.Fields{ 106 "digest": descriptorPath.Descriptor().Digest, 107 }).Debugf("-> ws.recurse") 108 defer log.WithFields(log.Fields{ 109 "digest": descriptorPath.Descriptor().Digest, 110 }).Debugf("<- ws.recurse") 111 112 // Run walkFunc. 113 if err := ws.walkFunc(descriptorPath); err != nil { 114 if err == ErrSkipDescriptor { 115 return nil 116 } 117 return err 118 } 119 120 // Get blob to recurse into. 121 descriptor := descriptorPath.Descriptor() 122 blob, err := ws.engine.FromDescriptor(ctx, descriptor) 123 if err != nil { 124 // Ignore cases where the descriptor points to an object we don't know 125 // how to parse. 126 if err == cas.ErrUnknownType { 127 log.Infof("skipping walk into unknown media-type %v of blob %v", descriptor.MediaType, descriptor.Digest) 128 return nil 129 } 130 return err 131 } 132 defer blob.Close() 133 134 // Recurse into children. 135 for _, child := range childDescriptors(blob.Data) { 136 if err := ws.recurse(ctx, DescriptorPath{ 137 Walk: append(descriptorPath.Walk, child), 138 }); err != nil { 139 return err 140 } 141 } 142 143 return nil 144 } 145 146 // Walk preforms a depth-first walk from a given root descriptor, using the 147 // provided CAS engine to fetch all other necessary descriptors. If an error is 148 // returned by the provided WalkFunc, walking is terminated and the error is 149 // returned to the caller. 150 func (e Engine) Walk(ctx context.Context, root ispec.Descriptor, walkFunc WalkFunc) error { 151 ws := &walkState{ 152 engine: e, 153 walkFunc: walkFunc, 154 } 155 return ws.recurse(ctx, DescriptorPath{ 156 Walk: []ispec.Descriptor{root}, 157 }) 158 } 159 160 // Paths returns the set of descriptor paths that can be traversed from the 161 // provided root descriptor. It is effectively shorthand for Walk(). Note that 162 // there may be repeated descriptors in the returned slice, due to different 163 // blobs containing the same (or a similar) descriptor. However, the 164 // DescriptorPaths should be unique. 165 func (e Engine) Paths(ctx context.Context, root ispec.Descriptor) ([]DescriptorPath, error) { 166 var reachable []DescriptorPath 167 err := e.Walk(ctx, root, func(descriptorPath DescriptorPath) error { 168 reachable = append(reachable, descriptorPath) 169 return nil 170 }) 171 return reachable, err 172 } 173 174 // Reachable returns the set of digests which can be reached using a descriptor 175 // path from the provided root descriptor. It is effectively a shorthand for 176 // Walk(). The returned slice will *not* contain any duplicate digest.Digest 177 // entries. Note that without descriptors, a digest is not particularly 178 // meaninful (OCI blobs are not self-descriptive). 179 func (e Engine) Reachable(ctx context.Context, root ispec.Descriptor) ([]digest.Digest, error) { 180 seen := map[digest.Digest]struct{}{} 181 182 if err := e.Walk(ctx, root, func(descriptorPath DescriptorPath) error { 183 seen[descriptorPath.Descriptor().Digest] = struct{}{} 184 return nil 185 }); err != nil { 186 return nil, err 187 } 188 189 var reachable []digest.Digest 190 for node := range seen { 191 reachable = append(reachable, node) 192 } 193 return reachable, nil 194 }