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  }