github.phpd.cn/hashicorp/packer@v1.3.2/template/interpolate/render.go (about) 1 package interpolate 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "sync" 8 9 "github.com/mitchellh/mapstructure" 10 "github.com/mitchellh/reflectwalk" 11 ) 12 13 // RenderFilter is an option for filtering what gets rendered and 14 // doesn't within an interface. 15 type RenderFilter struct { 16 Include []string 17 Exclude []string 18 19 once sync.Once 20 excludeSet map[string]struct{} 21 includeSet map[string]struct{} 22 } 23 24 // RenderMap renders all the strings in the given interface. The 25 // interface must decode into a map[string]interface{}, but is left 26 // as an interface{} type to ease backwards compatibility with the way 27 // arguments are passed around in Packer. 28 func RenderMap(v interface{}, ctx *Context, f *RenderFilter) (map[string]interface{}, error) { 29 // First decode it into the map 30 var m map[string]interface{} 31 if err := mapstructure.Decode(v, &m); err != nil { 32 return nil, err 33 } 34 35 // Now go through each value and render it 36 for k, raw := range m { 37 // Always validate every field 38 if err := ValidateInterface(raw, ctx); err != nil { 39 return nil, fmt.Errorf("invalid '%s': %s", k, err) 40 } 41 42 if !f.include(k) { 43 continue 44 } 45 46 raw, err := RenderInterface(raw, ctx) 47 if err != nil { 48 return nil, fmt.Errorf("render '%s': %s", k, err) 49 } 50 51 m[k] = raw 52 } 53 54 return m, nil 55 } 56 57 // RenderInterface renders any value and returns the resulting value. 58 func RenderInterface(v interface{}, ctx *Context) (interface{}, error) { 59 f := func(v string) (string, error) { 60 return Render(v, ctx) 61 } 62 63 walker := &renderWalker{ 64 F: f, 65 Replace: true, 66 } 67 err := reflectwalk.Walk(v, walker) 68 if err != nil { 69 return nil, err 70 } 71 72 if walker.Top != nil { 73 v = walker.Top 74 } 75 return v, nil 76 } 77 78 // ValidateInterface renders any value and returns the resulting value. 79 func ValidateInterface(v interface{}, ctx *Context) error { 80 f := func(v string) (string, error) { 81 return v, Validate(v, ctx) 82 } 83 84 walker := &renderWalker{ 85 F: f, 86 Replace: false, 87 } 88 err := reflectwalk.Walk(v, walker) 89 if err != nil { 90 return err 91 } 92 93 return nil 94 } 95 96 // Include checks whether a key should be included. 97 func (f *RenderFilter) include(k string) bool { 98 if f == nil { 99 return true 100 } 101 102 k = strings.ToLower(k) 103 104 f.once.Do(f.init) 105 if len(f.includeSet) > 0 { 106 _, ok := f.includeSet[k] 107 return ok 108 } 109 if len(f.excludeSet) > 0 { 110 _, ok := f.excludeSet[k] 111 return !ok 112 } 113 114 return true 115 } 116 117 func (f *RenderFilter) init() { 118 f.includeSet = make(map[string]struct{}) 119 for _, v := range f.Include { 120 f.includeSet[strings.ToLower(v)] = struct{}{} 121 } 122 123 f.excludeSet = make(map[string]struct{}) 124 for _, v := range f.Exclude { 125 f.excludeSet[strings.ToLower(v)] = struct{}{} 126 } 127 } 128 129 // renderWalker implements interfaces for the reflectwalk package 130 // (github.com/mitchellh/reflectwalk) that can be used to automatically 131 // execute a callback for an interpolation. 132 type renderWalker struct { 133 // F is the function to call for every interpolation. It can be nil. 134 // 135 // If Replace is true, then the return value of F will be used to 136 // replace the interpolation. 137 F renderWalkerFunc 138 Replace bool 139 140 // ContextF is an advanced version of F that also receives the 141 // location of where it is in the structure. This lets you do 142 // context-aware validation. 143 ContextF renderWalkerContextFunc 144 145 // Top is the top value of the walk. This might get replaced if the 146 // top value needs to be modified. It is valid to read after any walk. 147 // If it is nil, it means the top wasn't replaced. 148 Top interface{} 149 150 key []string 151 lastValue reflect.Value 152 loc reflectwalk.Location 153 cs []reflect.Value 154 csKey []reflect.Value 155 csData interface{} 156 sliceIndex int 157 } 158 159 // renderWalkerFunc is the callback called by interpolationWalk. 160 // It is called with any interpolation found. It should return a value 161 // to replace the interpolation with, along with any errors. 162 // 163 // If Replace is set to false in renderWalker, then the replace 164 // value can be anything as it will have no effect. 165 type renderWalkerFunc func(string) (string, error) 166 167 // renderWalkerContextFunc is called by interpolationWalk if 168 // ContextF is set. This receives both the interpolation and the location 169 // where the interpolation is. 170 // 171 // This callback can be used to validate the location of the interpolation 172 // within the configuration. 173 type renderWalkerContextFunc func(reflectwalk.Location, string) 174 175 func (w *renderWalker) Enter(loc reflectwalk.Location) error { 176 w.loc = loc 177 return nil 178 } 179 180 func (w *renderWalker) Exit(loc reflectwalk.Location) error { 181 w.loc = reflectwalk.None 182 183 switch loc { 184 case reflectwalk.Map: 185 w.cs = w.cs[:len(w.cs)-1] 186 case reflectwalk.MapValue: 187 w.key = w.key[:len(w.key)-1] 188 w.csKey = w.csKey[:len(w.csKey)-1] 189 case reflectwalk.Slice: 190 // Split any values that need to be split 191 w.cs = w.cs[:len(w.cs)-1] 192 case reflectwalk.SliceElem: 193 w.csKey = w.csKey[:len(w.csKey)-1] 194 } 195 196 return nil 197 } 198 199 func (w *renderWalker) Map(m reflect.Value) error { 200 w.cs = append(w.cs, m) 201 return nil 202 } 203 204 func (w *renderWalker) MapElem(m, k, v reflect.Value) error { 205 w.csData = k 206 w.csKey = append(w.csKey, k) 207 w.key = append(w.key, k.String()) 208 w.lastValue = v 209 return nil 210 } 211 212 func (w *renderWalker) Slice(s reflect.Value) error { 213 w.cs = append(w.cs, s) 214 return nil 215 } 216 217 func (w *renderWalker) SliceElem(i int, elem reflect.Value) error { 218 w.csKey = append(w.csKey, reflect.ValueOf(i)) 219 w.sliceIndex = i 220 return nil 221 } 222 223 func (w *renderWalker) Primitive(v reflect.Value) error { 224 setV := v 225 226 // We only care about strings 227 if v.Kind() == reflect.Interface { 228 setV = v 229 v = v.Elem() 230 } 231 if v.Kind() != reflect.String { 232 return nil 233 } 234 235 strV := v.String() 236 if w.ContextF != nil { 237 w.ContextF(w.loc, strV) 238 } 239 240 if w.F == nil { 241 return nil 242 } 243 244 replaceVal, err := w.F(strV) 245 if err != nil { 246 return fmt.Errorf( 247 "%s in:\n\n%s", 248 err, v.String()) 249 } 250 251 if w.Replace { 252 resultVal := reflect.ValueOf(replaceVal) 253 switch w.loc { 254 case reflectwalk.MapKey: 255 m := w.cs[len(w.cs)-1] 256 257 // Delete the old value 258 var zero reflect.Value 259 m.SetMapIndex(w.csData.(reflect.Value), zero) 260 261 // Set the new key with the existing value 262 m.SetMapIndex(resultVal, w.lastValue) 263 264 // Set the key to be the new key 265 w.csData = resultVal 266 case reflectwalk.MapValue: 267 // If we're in a map, then the only way to set a map value is 268 // to set it directly. 269 m := w.cs[len(w.cs)-1] 270 mk := w.csData.(reflect.Value) 271 m.SetMapIndex(mk, resultVal) 272 case reflectwalk.WalkLoc: 273 // At the root element, we can't write that, so we just save it 274 w.Top = resultVal.Interface() 275 default: 276 // Otherwise, we should be addressable 277 setV.Set(resultVal) 278 } 279 } 280 281 return nil 282 }