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 }