github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/casext/map.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  	"reflect"
    22  
    23  	"github.com/apex/log"
    24  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    25  	"github.com/pkg/errors"
    26  )
    27  
    28  // Used by walkState.mark() to determine which struct members are descriptors to
    29  // recurse into them. We aren't interested in struct members which are not
    30  // either a slice of ispec.Descriptor or ispec.Descriptor themselves.
    31  var descriptorType = reflect.TypeOf(ispec.Descriptor{})
    32  
    33  // DescriptorMapFunc is a function that is used to provide a mapping between
    34  // different descriptor values with MapDescriptors. It will not be called
    35  // concurrently, and will only be called once for each recursively resolved
    36  // element.
    37  type DescriptorMapFunc func(ispec.Descriptor) ispec.Descriptor
    38  
    39  // isDescriptor returns whether the given T is a ispec.Descriptor.
    40  func isDescriptor(T reflect.Type) bool {
    41  	return T == descriptorType
    42  }
    43  
    44  func mapDescriptors(V reflect.Value, mapFunc DescriptorMapFunc) error {
    45  	// We can ignore this value.
    46  	if !V.IsValid() {
    47  		return nil
    48  	}
    49  
    50  	// First check that V isn't actually a ispec.Descriptor, if it is then
    51  	// we're done.
    52  	if isDescriptor(V.Type()) {
    53  		old := V.Interface().(ispec.Descriptor)
    54  		new := mapFunc(old)
    55  
    56  		// We only need to do any assignment if the two are not equal.
    57  		if !reflect.DeepEqual(new, old) {
    58  			// P is a ptr to V (or just V if it's already a pointer).
    59  			P := V
    60  			if !P.CanSet() {
    61  				// This is a programmer error.
    62  				return errors.Errorf("[internal error] cannot apply map function to %v: %v is not settable!", P, P.Type())
    63  			}
    64  			P.Set(reflect.ValueOf(new))
    65  		}
    66  		return nil
    67  	}
    68  
    69  	// Recurse into all the types.
    70  	switch V.Kind() {
    71  	case reflect.Ptr, reflect.Interface:
    72  		// Just deref the pointer/interface.
    73  		if V.IsNil() {
    74  			return nil
    75  		}
    76  		err := mapDescriptors(V.Elem(), mapFunc)
    77  		return errors.Wrapf(err, "%v", V.Type())
    78  
    79  	case reflect.Slice, reflect.Array:
    80  		// Iterate over each element.
    81  		for idx := 0; idx < V.Len(); idx++ {
    82  			err := mapDescriptors(V.Index(idx), mapFunc)
    83  			if err != nil {
    84  				return errors.Wrapf(err, "%v[%d]->%v", V.Type(), idx, V.Index(idx).Type())
    85  			}
    86  		}
    87  		return nil
    88  
    89  	case reflect.Struct:
    90  		// We are only ever going to be interested in ispec.* types.
    91  		// XXX: This is something we might want to revisit in the future.
    92  		if V.Type().PkgPath() != descriptorType.PkgPath() {
    93  			log.WithFields(log.Fields{
    94  				"name":   V.Type().PkgPath() + "::" + V.Type().Name(),
    95  				"v1path": descriptorType.PkgPath(),
    96  			}).Debugf("detected escape to outside ispec.* namespace")
    97  			return nil
    98  		}
    99  
   100  		// We can now actually iterate through a struct to find all descriptors.
   101  		for idx := 0; idx < V.NumField(); idx++ {
   102  			err := mapDescriptors(V.Field(idx), mapFunc)
   103  			if err != nil {
   104  				return errors.Wrapf(err, "%v[%d=%s]->%v", V.Type(), idx, V.Type().Field(idx).Name, V.Field(idx).Type())
   105  			}
   106  		}
   107  		return nil
   108  
   109  	default:
   110  		// FIXME: Should we log something here? While this will be hit normally
   111  		//        (namely when we hit an io.ReadCloser) this seems a bit
   112  		//        careless.
   113  		return nil
   114  	}
   115  
   116  	// Unreachable.
   117  }
   118  
   119  // MapDescriptors applies the given function once for every instance of
   120  // ispec.Descriptor found in the given type, and replaces it with the returned
   121  // value (which may be the same). This is done through the reflection API in
   122  // Go, which means that hidden attributes may be inaccessible.
   123  // DescriptorMapFunc will only be executed once for every ispec.Descriptor
   124  // found.
   125  func MapDescriptors(i interface{}, mapFunc DescriptorMapFunc) error {
   126  	return mapDescriptors(reflect.ValueOf(i), mapFunc)
   127  }