github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/plugin/expand_params.go (about)

     1  package plugin
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  
     7  	"github.com/evergreen-ci/evergreen/command"
     8  	"github.com/evergreen-ci/evergreen/util"
     9  	"github.com/pkg/errors"
    10  )
    11  
    12  const (
    13  	PluginTagPrefix      = "plugin"
    14  	PluginExpandAllowed  = "expand"
    15  	PluginExpandStartTag = "${"
    16  	PluginExpandEndTag   = "}"
    17  )
    18  
    19  // Taking in the input and expansions map, apply the expansions to any
    20  // appropriate fields in the input.  The input must be a pointer to a struct
    21  // so that the underlying struct can be modified.
    22  func ExpandValues(input interface{}, expansions *command.Expansions) error {
    23  
    24  	// make sure the input is a pointer to a map or struct
    25  	if reflect.ValueOf(input).Type().Kind() != reflect.Ptr {
    26  		return errors.New("input to expand must be a pointer")
    27  	}
    28  	inputVal := reflect.Indirect(reflect.ValueOf(input))
    29  
    30  	// expand map or struct
    31  	switch inputVal.Type().Kind() {
    32  	case reflect.Struct:
    33  		if err := expandStruct(inputVal, expansions); err != nil {
    34  			return errors.Wrap(err, "error expanding struct")
    35  		}
    36  	case reflect.Map:
    37  		if err := expandMap(inputVal, expansions); err != nil {
    38  			return errors.Wrap(err, "error expanding map")
    39  		}
    40  	default:
    41  		return errors.New("input to expand must be a pointer to a struct or map")
    42  	}
    43  
    44  	return nil
    45  }
    46  
    47  // Helper function to expand a map. Returns expanded version with
    48  // both keys and values expanded
    49  func expandMap(inputMap reflect.Value, expansions *command.Expansions) error {
    50  
    51  	if inputMap.Type().Key().Kind() != reflect.String {
    52  		return errors.New("input map to expand must have keys of string type")
    53  	}
    54  
    55  	// iterate through keys and value, expanding them
    56  	for _, key := range inputMap.MapKeys() {
    57  		expandedKeyString, err := expansions.ExpandString(key.String())
    58  		if err != nil {
    59  			return errors.Wrapf(err, "could not expand key %v", key.String())
    60  		}
    61  
    62  		// expand and set new value
    63  		val := inputMap.MapIndex(key)
    64  		expandedVal := reflect.Value{}
    65  		switch val.Type().Kind() {
    66  		case reflect.String:
    67  			expandedValString, err := expansions.ExpandString(val.String())
    68  			if err != nil {
    69  				return errors.Wrapf(err, "could not expand value %v", val.String())
    70  			}
    71  			expandedVal = reflect.ValueOf(expandedValString)
    72  		case reflect.Map:
    73  			if err := expandMap(val, expansions); err != nil {
    74  				return errors.Wrapf(err, "could not expand value %v: %v", val.String())
    75  			}
    76  			expandedVal = val
    77  		default:
    78  			return errors.Errorf(
    79  				"could not expand value %v: must be string, map, or struct",
    80  				val.String())
    81  		}
    82  
    83  		// unset unexpanded key then set expanded key
    84  		inputMap.SetMapIndex(key, reflect.Value{})
    85  		inputMap.SetMapIndex(reflect.ValueOf(expandedKeyString), expandedVal)
    86  	}
    87  
    88  	return nil
    89  }
    90  
    91  // Helper function to expand a single struct.  Returns the expanded version
    92  // of the struct.
    93  func expandStruct(inputVal reflect.Value, expansions *command.Expansions) error {
    94  
    95  	// find any values with an expandable tag
    96  	numFields := inputVal.NumField()
    97  	for i := 0; i < numFields; i++ {
    98  		field := inputVal.Type().Field(i)
    99  		fieldTag := field.Tag.Get(PluginTagPrefix)
   100  
   101  		// no tag, skip
   102  		if fieldTag == "" {
   103  			continue
   104  		}
   105  
   106  		// split the tag into its parts
   107  		tagParts := strings.Split(fieldTag, ",")
   108  
   109  		// see if the field is expandable
   110  		if !util.SliceContains(tagParts, PluginExpandAllowed) {
   111  			continue
   112  		}
   113  
   114  		// if the field is a struct, descend recursively
   115  		if field.Type.Kind() == reflect.Struct {
   116  			err := expandStruct(inputVal.FieldByName(field.Name), expansions)
   117  			if err != nil {
   118  				return errors.Wrapf(err, "error expanding struct in field %v",
   119  					field.Name)
   120  			}
   121  			continue
   122  		}
   123  
   124  		// if the field is a map, descend recursively
   125  		if field.Type.Kind() == reflect.Map {
   126  			inputMap := inputVal.FieldByName(field.Name)
   127  			err := expandMap(inputMap, expansions)
   128  			if err != nil {
   129  				return errors.Wrapf(err, "error expanding map in field %v",
   130  					field.Name)
   131  			}
   132  			continue
   133  		}
   134  
   135  		// if the field is a slice, descend recursively
   136  		if field.Type.Kind() == reflect.Slice {
   137  			slice := inputVal.FieldByName(field.Name)
   138  			for i := 0; i < slice.Len(); i++ {
   139  				var err error
   140  				sliceKind := slice.Index(i).Kind()
   141  				if sliceKind == reflect.String {
   142  					err = expandString(slice.Index(i), expansions)
   143  				} else if sliceKind == reflect.Interface || sliceKind == reflect.Ptr {
   144  					err = expandStruct(slice.Index(i).Elem(), expansions)
   145  				} else {
   146  					//don't take elem if it's not an array of interface/pointers
   147  					err = expandStruct(slice.Index(i), expansions)
   148  				}
   149  
   150  				if err != nil {
   151  					return errors.Wrapf(err, "error expanding struct in field %v",
   152  						field.Name)
   153  				}
   154  			}
   155  			continue
   156  		}
   157  
   158  		// make sure the field is a string
   159  		if field.Type.Kind() != reflect.String {
   160  			return errors.Errorf("cannot expand non-string field '%v' "+
   161  				"which is of type %v", field.Name, field.Type.Kind())
   162  		}
   163  
   164  		// it's expandable - apply the expansions
   165  		fieldOfElem := inputVal.FieldByName(field.Name)
   166  		err := expandString(fieldOfElem, expansions)
   167  		if err != nil {
   168  			return errors.Wrapf(err, "error applying expansions to field %v with value %v",
   169  				field.Name, fieldOfElem.String())
   170  		}
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  func expandString(inputVal reflect.Value, expansions *command.Expansions) error {
   177  	expanded, err := expansions.ExpandString(inputVal.String())
   178  	if err != nil {
   179  		return errors.WithStack(err)
   180  	}
   181  	inputVal.SetString(expanded)
   182  	return nil
   183  }
   184  
   185  // IsExpandable returns true if the passed in string contains an
   186  // expandable parameter
   187  func IsExpandable(param string) bool {
   188  	startIndex := strings.Index(param, PluginExpandStartTag)
   189  	endIndex := strings.Index(param, PluginExpandEndTag)
   190  	return startIndex >= 0 && endIndex >= 0 && endIndex > startIndex
   191  }