github.com/kimor79/packer@v0.8.7-0.20151221212622-d507b18eb4cf/template/parse.go (about) 1 package template 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "sort" 13 14 "github.com/hashicorp/go-multierror" 15 "github.com/mitchellh/mapstructure" 16 ) 17 18 // rawTemplate is the direct JSON document format of the template file. 19 // This is what is decoded directly from the file, and then it is turned 20 // into a Template object thereafter. 21 type rawTemplate struct { 22 MinVersion string `mapstructure:"min_packer_version"` 23 Description string 24 25 Builders []map[string]interface{} 26 Push map[string]interface{} 27 PostProcessors []interface{} `mapstructure:"post-processors"` 28 Provisioners []map[string]interface{} 29 Variables map[string]interface{} 30 31 RawContents []byte 32 } 33 34 // Template returns the actual Template object built from this raw 35 // structure. 36 func (r *rawTemplate) Template() (*Template, error) { 37 var result Template 38 var errs error 39 40 // Copy some literals 41 result.Description = r.Description 42 result.MinVersion = r.MinVersion 43 result.RawContents = r.RawContents 44 45 // Gather the variables 46 if len(r.Variables) > 0 { 47 result.Variables = make(map[string]*Variable, len(r.Variables)) 48 } 49 for k, rawV := range r.Variables { 50 var v Variable 51 52 // Variable is required if the value is exactly nil 53 v.Required = rawV == nil 54 55 // Weak decode the default if we have one 56 if err := r.decoder(&v.Default, nil).Decode(rawV); err != nil { 57 errs = multierror.Append(errs, fmt.Errorf( 58 "variable %s: %s", k, err)) 59 continue 60 } 61 62 result.Variables[k] = &v 63 } 64 65 // Let's start by gathering all the builders 66 if len(r.Builders) > 0 { 67 result.Builders = make(map[string]*Builder, len(r.Builders)) 68 } 69 for i, rawB := range r.Builders { 70 var b Builder 71 if err := mapstructure.WeakDecode(rawB, &b); err != nil { 72 errs = multierror.Append(errs, fmt.Errorf( 73 "builder %d: %s", i+1, err)) 74 continue 75 } 76 77 // Set the raw configuration and delete any special keys 78 b.Config = rawB 79 delete(b.Config, "name") 80 delete(b.Config, "type") 81 if len(b.Config) == 0 { 82 b.Config = nil 83 } 84 85 // If there is no type set, it is an error 86 if b.Type == "" { 87 errs = multierror.Append(errs, fmt.Errorf( 88 "builder %d: missing 'type'", i+1)) 89 continue 90 } 91 92 // The name defaults to the type if it isn't set 93 if b.Name == "" { 94 b.Name = b.Type 95 } 96 97 // If this builder already exists, it is an error 98 if _, ok := result.Builders[b.Name]; ok { 99 errs = multierror.Append(errs, fmt.Errorf( 100 "builder %d: builder with name '%s' already exists", 101 i+1, b.Name)) 102 continue 103 } 104 105 // Append the builders 106 result.Builders[b.Name] = &b 107 } 108 109 // Gather all the post-processors 110 if len(r.PostProcessors) > 0 { 111 result.PostProcessors = make([][]*PostProcessor, 0, len(r.PostProcessors)) 112 } 113 for i, v := range r.PostProcessors { 114 // Parse the configurations. We need to do this because post-processors 115 // can take three different formats. 116 configs, err := r.parsePostProcessor(i, v) 117 if err != nil { 118 errs = multierror.Append(errs, err) 119 continue 120 } 121 122 // Parse the PostProcessors out of the configs 123 pps := make([]*PostProcessor, 0, len(configs)) 124 for j, c := range configs { 125 var pp PostProcessor 126 if err := r.decoder(&pp, nil).Decode(c); err != nil { 127 errs = multierror.Append(errs, fmt.Errorf( 128 "post-processor %d.%d: %s", i+1, j+1, err)) 129 continue 130 } 131 132 // Type is required 133 if pp.Type == "" { 134 errs = multierror.Append(errs, fmt.Errorf( 135 "post-processor %d.%d: type is required", i+1, j+1)) 136 continue 137 } 138 139 // Set the configuration 140 delete(c, "except") 141 delete(c, "only") 142 delete(c, "keep_input_artifact") 143 delete(c, "type") 144 if len(c) > 0 { 145 pp.Config = c 146 } 147 148 pps = append(pps, &pp) 149 } 150 151 result.PostProcessors = append(result.PostProcessors, pps) 152 } 153 154 // Gather all the provisioners 155 if len(r.Provisioners) > 0 { 156 result.Provisioners = make([]*Provisioner, 0, len(r.Provisioners)) 157 } 158 for i, v := range r.Provisioners { 159 var p Provisioner 160 if err := r.decoder(&p, nil).Decode(v); err != nil { 161 errs = multierror.Append(errs, fmt.Errorf( 162 "provisioner %d: %s", i+1, err)) 163 continue 164 } 165 166 // Type is required before any richer validation 167 if p.Type == "" { 168 errs = multierror.Append(errs, fmt.Errorf( 169 "provisioner %d: missing 'type'", i+1)) 170 continue 171 } 172 173 // Copy the configuration 174 delete(v, "except") 175 delete(v, "only") 176 delete(v, "override") 177 delete(v, "pause_before") 178 delete(v, "type") 179 if len(v) > 0 { 180 p.Config = v 181 } 182 183 // TODO: stuff 184 result.Provisioners = append(result.Provisioners, &p) 185 } 186 187 // Push 188 if len(r.Push) > 0 { 189 var p Push 190 if err := r.decoder(&p, nil).Decode(r.Push); err != nil { 191 errs = multierror.Append(errs, fmt.Errorf( 192 "push: %s", err)) 193 } 194 195 result.Push = p 196 } 197 198 // If we have errors, return those with a nil result 199 if errs != nil { 200 return nil, errs 201 } 202 203 return &result, nil 204 } 205 206 func (r *rawTemplate) decoder( 207 result interface{}, 208 md *mapstructure.Metadata) *mapstructure.Decoder { 209 d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 210 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 211 Metadata: md, 212 Result: result, 213 }) 214 if err != nil { 215 // This really shouldn't happen since we have firm control over 216 // all the arguments and they're all unit tested. So we use a 217 // panic here to note this would definitely be a bug. 218 panic(err) 219 } 220 return d 221 } 222 223 func (r *rawTemplate) parsePostProcessor( 224 i int, raw interface{}) ([]map[string]interface{}, error) { 225 switch v := raw.(type) { 226 case string: 227 return []map[string]interface{}{ 228 {"type": v}, 229 }, nil 230 case map[string]interface{}: 231 return []map[string]interface{}{v}, nil 232 case []interface{}: 233 var err error 234 result := make([]map[string]interface{}, len(v)) 235 for j, innerRaw := range v { 236 switch innerV := innerRaw.(type) { 237 case string: 238 result[j] = map[string]interface{}{"type": innerV} 239 case map[string]interface{}: 240 result[j] = innerV 241 case []interface{}: 242 err = multierror.Append(err, fmt.Errorf( 243 "post-processor %d.%d: sequence not allowed to be nested in a sequence", 244 i+1, j+1)) 245 default: 246 err = multierror.Append(err, fmt.Errorf( 247 "post-processor %d.%d: unknown format", 248 i+1, j+1)) 249 } 250 } 251 252 if err != nil { 253 return nil, err 254 } 255 256 return result, nil 257 default: 258 return nil, fmt.Errorf("post-processor %d: bad format", i+1) 259 } 260 } 261 262 // Parse takes the given io.Reader and parses a Template object out of it. 263 func Parse(r io.Reader) (*Template, error) { 264 // Create a buffer to copy what we read 265 var buf bytes.Buffer 266 r = io.TeeReader(r, &buf) 267 268 // First, decode the object into an interface{}. We do this instead of 269 // the rawTemplate directly because we'd rather use mapstructure to 270 // decode since it has richer errors. 271 var raw interface{} 272 if err := json.NewDecoder(r).Decode(&raw); err != nil { 273 return nil, err 274 } 275 276 // Create our decoder 277 var md mapstructure.Metadata 278 var rawTpl rawTemplate 279 rawTpl.RawContents = buf.Bytes() 280 decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 281 Metadata: &md, 282 Result: &rawTpl, 283 }) 284 if err != nil { 285 return nil, err 286 } 287 288 // Do the actual decode into our structure 289 if err := decoder.Decode(raw); err != nil { 290 return nil, err 291 } 292 293 // Build an error if there are unused root level keys 294 if len(md.Unused) > 0 { 295 sort.Strings(md.Unused) 296 for _, unused := range md.Unused { 297 // Ignore keys starting with '_' as comments 298 if unused[0] == '_' { 299 continue 300 } 301 302 err = multierror.Append(err, fmt.Errorf( 303 "Unknown root level key in template: '%s'", unused)) 304 } 305 } 306 if err != nil { 307 return nil, err 308 } 309 310 // Return the template parsed from the raw structure 311 return rawTpl.Template() 312 } 313 314 // Find line number and position based on the offset 315 func findLinePos(f *os.File, offset int64) (int64, int64, string) { 316 scanner := bufio.NewScanner(f) 317 count := int64(0) 318 for scanner.Scan() { 319 count += 1 320 scanLength := len(scanner.Text()) + 1 321 if offset < int64(scanLength) { 322 return count, offset, scanner.Text() 323 } 324 offset = offset - int64(scanLength) 325 } 326 if err := scanner.Err(); err != nil { 327 return 0, 0, err.Error() 328 } 329 return 0, 0, "" 330 } 331 332 // ParseFile is the same as Parse but is a helper to automatically open 333 // a file for parsing. 334 func ParseFile(path string) (*Template, error) { 335 var f *os.File 336 var err error 337 if path == "-" { 338 // Create a temp file for stdin in case of errors 339 f, err = ioutil.TempFile(os.TempDir(), "packer") 340 if err != nil { 341 return nil, err 342 } 343 defer os.Remove(f.Name()) 344 defer f.Close() 345 io.Copy(f, os.Stdin) 346 f.Seek(0, os.SEEK_SET) 347 } else { 348 f, err = os.Open(path) 349 if err != nil { 350 return nil, err 351 } 352 defer f.Close() 353 } 354 tpl, err := Parse(f) 355 if err != nil { 356 syntaxErr, ok := err.(*json.SyntaxError) 357 if !ok { 358 return nil, err 359 } 360 // Rewind the file and get a better error 361 f.Seek(0, os.SEEK_SET) 362 line, pos, errorLine := findLinePos(f, syntaxErr.Offset) 363 err = fmt.Errorf("Error in line %d, char %d: %s\n%s", line, pos, syntaxErr, errorLine) 364 return nil, err 365 } 366 367 if !filepath.IsAbs(path) { 368 path, err = filepath.Abs(path) 369 if err != nil { 370 return nil, err 371 } 372 } 373 374 tpl.Path = path 375 return tpl, nil 376 }