github.com/supr/packer@v0.3.10-0.20131015195147-7b09e24ac3c1/packer/template.go (about) 1 package packer 2 3 import ( 4 "bytes" 5 "fmt" 6 "github.com/mitchellh/mapstructure" 7 jsonutil "github.com/mitchellh/packer/common/json" 8 "io" 9 "io/ioutil" 10 "os" 11 "sort" 12 ) 13 14 // The rawTemplate struct represents the structure of a template read 15 // directly from a file. The builders and other components map just to 16 // "interface{}" pointers since we actually don't know what their contents 17 // are until we read the "type" field. 18 type rawTemplate struct { 19 Variables map[string]interface{} 20 Builders []map[string]interface{} 21 Hooks map[string][]string 22 Provisioners []map[string]interface{} 23 PostProcessors []interface{} `mapstructure:"post-processors"` 24 } 25 26 // The Template struct represents a parsed template, parsed into the most 27 // completed form it can be without additional processing by the caller. 28 type Template struct { 29 Variables map[string]RawVariable 30 Builders map[string]RawBuilderConfig 31 Hooks map[string][]string 32 PostProcessors [][]RawPostProcessorConfig 33 Provisioners []RawProvisionerConfig 34 } 35 36 // The RawBuilderConfig struct represents a raw, unprocessed builder 37 // configuration. It contains the name of the builder as well as the 38 // raw configuration. If requested, this is used to compile into a full 39 // builder configuration at some point. 40 type RawBuilderConfig struct { 41 Name string 42 Type string 43 44 RawConfig interface{} 45 } 46 47 // RawPostProcessorConfig represents a raw, unprocessed post-processor 48 // configuration. It contains the type of the post processor as well as the 49 // raw configuration that is handed to the post-processor for it to process. 50 type RawPostProcessorConfig struct { 51 TemplateOnlyExcept `mapstructure:",squash"` 52 53 Type string 54 KeepInputArtifact bool `mapstructure:"keep_input_artifact"` 55 RawConfig map[string]interface{} 56 } 57 58 // RawProvisionerConfig represents a raw, unprocessed provisioner configuration. 59 // It contains the type of the provisioner as well as the raw configuration 60 // that is handed to the provisioner for it to process. 61 type RawProvisionerConfig struct { 62 TemplateOnlyExcept `mapstructure:",squash"` 63 64 Type string 65 Override map[string]interface{} 66 67 RawConfig interface{} 68 } 69 70 // RawVariable represents a variable configuration within a template. 71 type RawVariable struct { 72 Default string 73 Required bool 74 } 75 76 // ParseTemplate takes a byte slice and parses a Template from it, returning 77 // the template and possibly errors while loading the template. The error 78 // could potentially be a MultiError, representing multiple errors. Knowing 79 // and checking for this can be useful, if you wish to format it in a certain 80 // way. 81 func ParseTemplate(data []byte) (t *Template, err error) { 82 var rawTplInterface interface{} 83 err = jsonutil.Unmarshal(data, &rawTplInterface) 84 if err != nil { 85 return 86 } 87 88 // Decode the raw template interface into the actual rawTemplate 89 // structure, checking for any extranneous keys along the way. 90 var md mapstructure.Metadata 91 var rawTpl rawTemplate 92 decoderConfig := &mapstructure.DecoderConfig{ 93 Metadata: &md, 94 Result: &rawTpl, 95 } 96 97 decoder, err := mapstructure.NewDecoder(decoderConfig) 98 if err != nil { 99 return 100 } 101 102 err = decoder.Decode(rawTplInterface) 103 if err != nil { 104 return 105 } 106 107 errors := make([]error, 0) 108 109 if len(md.Unused) > 0 { 110 sort.Strings(md.Unused) 111 for _, unused := range md.Unused { 112 errors = append( 113 errors, fmt.Errorf("Unknown root level key in template: '%s'", unused)) 114 } 115 } 116 117 t = &Template{} 118 t.Variables = make(map[string]RawVariable) 119 t.Builders = make(map[string]RawBuilderConfig) 120 t.Hooks = rawTpl.Hooks 121 t.PostProcessors = make([][]RawPostProcessorConfig, len(rawTpl.PostProcessors)) 122 t.Provisioners = make([]RawProvisionerConfig, len(rawTpl.Provisioners)) 123 124 // Gather all the variables 125 for k, v := range rawTpl.Variables { 126 var variable RawVariable 127 variable.Required = v == nil 128 129 // Create a new mapstructure decoder in order to decode the default 130 // value since this is the only value in the regular template that 131 // can be weakly typed. 132 decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 133 Result: &variable.Default, 134 WeaklyTypedInput: true, 135 }) 136 if err != nil { 137 // This should never happen. 138 panic(err) 139 } 140 141 err = decoder.Decode(v) 142 if err != nil { 143 errors = append(errors, 144 fmt.Errorf("Error decoding default value for user var '%s': %s", k, err)) 145 continue 146 } 147 148 t.Variables[k] = variable 149 } 150 151 // Gather all the builders 152 for i, v := range rawTpl.Builders { 153 var raw RawBuilderConfig 154 if err := mapstructure.Decode(v, &raw); err != nil { 155 if merr, ok := err.(*mapstructure.Error); ok { 156 for _, err := range merr.Errors { 157 errors = append(errors, fmt.Errorf("builder %d: %s", i+1, err)) 158 } 159 } else { 160 errors = append(errors, fmt.Errorf("builder %d: %s", i+1, err)) 161 } 162 163 continue 164 } 165 166 if raw.Type == "" { 167 errors = append(errors, fmt.Errorf("builder %d: missing 'type'", i+1)) 168 continue 169 } 170 171 // Attempt to get the name of the builder. If the "name" key 172 // missing, use the "type" field, which is guaranteed to exist 173 // at this point. 174 if raw.Name == "" { 175 raw.Name = raw.Type 176 } 177 178 // Check if we already have a builder with this name and error if so 179 if _, ok := t.Builders[raw.Name]; ok { 180 errors = append(errors, fmt.Errorf("builder with name '%s' already exists", raw.Name)) 181 continue 182 } 183 184 // Now that we have the name, remove it from the config - as the builder 185 // itself doesn't know about, and it will cause a validation error. 186 delete(v, "name") 187 188 raw.RawConfig = v 189 190 t.Builders[raw.Name] = raw 191 } 192 193 // Gather all the post-processors. This is a complicated process since there 194 // are actually three different formats that the user can use to define 195 // a post-processor. 196 for i, rawV := range rawTpl.PostProcessors { 197 rawPP, err := parsePostProcessor(i, rawV) 198 if err != nil { 199 errors = append(errors, err...) 200 continue 201 } 202 203 configs := make([]RawPostProcessorConfig, 0, len(rawPP)) 204 for j, pp := range rawPP { 205 var config RawPostProcessorConfig 206 if err := mapstructure.Decode(pp, &config); err != nil { 207 if merr, ok := err.(*mapstructure.Error); ok { 208 for _, err := range merr.Errors { 209 errors = append(errors, 210 fmt.Errorf("Post-processor #%d.%d: %s", i+1, j+1, err)) 211 } 212 } else { 213 errors = append(errors, 214 fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err)) 215 } 216 217 continue 218 } 219 220 if config.Type == "" { 221 errors = append(errors, 222 fmt.Errorf("Post-processor %d.%d: missing 'type'", i+1, j+1)) 223 continue 224 } 225 226 // Remove the input keep_input_artifact option 227 config.TemplateOnlyExcept.Prune(pp) 228 delete(pp, "keep_input_artifact") 229 230 // Verify that the only settings are good 231 if errs := config.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 { 232 for _, err := range errs { 233 errors = append(errors, 234 fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err)) 235 } 236 237 continue 238 } 239 240 config.RawConfig = pp 241 242 // Add it to the list of configs 243 configs = append(configs, config) 244 } 245 246 t.PostProcessors[i] = configs 247 } 248 249 // Gather all the provisioners 250 for i, v := range rawTpl.Provisioners { 251 raw := &t.Provisioners[i] 252 if err := mapstructure.Decode(v, raw); err != nil { 253 if merr, ok := err.(*mapstructure.Error); ok { 254 for _, err := range merr.Errors { 255 errors = append(errors, fmt.Errorf("provisioner %d: %s", i+1, err)) 256 } 257 } else { 258 errors = append(errors, fmt.Errorf("provisioner %d: %s", i+1, err)) 259 } 260 261 continue 262 } 263 264 if raw.Type == "" { 265 errors = append(errors, fmt.Errorf("provisioner %d: missing 'type'", i+1)) 266 continue 267 } 268 269 // Delete the keys that we used 270 raw.TemplateOnlyExcept.Prune(v) 271 delete(v, "override") 272 273 // Verify that the override keys exist... 274 for name, _ := range raw.Override { 275 if _, ok := t.Builders[name]; !ok { 276 errors = append( 277 errors, fmt.Errorf("provisioner %d: build '%s' not found for override", i+1, name)) 278 } 279 } 280 281 // Verify that the only settings are good 282 if errs := raw.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 { 283 for _, err := range errs { 284 errors = append(errors, 285 fmt.Errorf("provisioner %d: %s", i+1, err)) 286 } 287 } 288 289 raw.RawConfig = v 290 } 291 292 if len(t.Builders) == 0 { 293 errors = append(errors, fmt.Errorf("No builders are defined in the template.")) 294 } 295 296 // If there were errors, we put it into a MultiError and return 297 if len(errors) > 0 { 298 err = &MultiError{errors} 299 t = nil 300 return 301 } 302 303 return 304 } 305 306 // ParseTemplateFile takes the given template file and parses it into 307 // a single template. 308 func ParseTemplateFile(path string) (*Template, error) { 309 var data []byte 310 311 if path == "-" { 312 // Read from stdin... 313 buf := new(bytes.Buffer) 314 _, err := io.Copy(buf, os.Stdin) 315 if err != nil { 316 return nil, err 317 } 318 319 data = buf.Bytes() 320 } else { 321 var err error 322 data, err = ioutil.ReadFile(path) 323 if err != nil { 324 return nil, err 325 } 326 } 327 328 return ParseTemplate(data) 329 } 330 331 func parsePostProcessor(i int, rawV interface{}) (result []map[string]interface{}, errors []error) { 332 switch v := rawV.(type) { 333 case string: 334 result = []map[string]interface{}{ 335 {"type": v}, 336 } 337 case map[string]interface{}: 338 result = []map[string]interface{}{v} 339 case []interface{}: 340 result = make([]map[string]interface{}, len(v)) 341 errors = make([]error, 0) 342 for j, innerRawV := range v { 343 switch innerV := innerRawV.(type) { 344 case string: 345 result[j] = map[string]interface{}{"type": innerV} 346 case map[string]interface{}: 347 result[j] = innerV 348 case []interface{}: 349 errors = append( 350 errors, 351 fmt.Errorf("Post-processor %d.%d: sequences not allowed to be nested in sequences", i+1, j+1)) 352 default: 353 errors = append(errors, fmt.Errorf("Post-processor %d.%d is in a bad format.", i+1, j+1)) 354 } 355 } 356 357 if len(errors) == 0 { 358 errors = nil 359 } 360 default: 361 result = nil 362 errors = []error{fmt.Errorf("Post-processor %d is in a bad format.", i+1)} 363 } 364 365 return 366 } 367 368 // BuildNames returns a slice of the available names of builds that 369 // this template represents. 370 func (t *Template) BuildNames() []string { 371 names := make([]string, 0, len(t.Builders)) 372 for name, _ := range t.Builders { 373 names = append(names, name) 374 } 375 376 return names 377 } 378 379 // Build returns a Build for the given name. 380 // 381 // If the build does not exist as part of this template, an error is 382 // returned. 383 func (t *Template) Build(name string, components *ComponentFinder) (b Build, err error) { 384 // Setup the Builder 385 builderConfig, ok := t.Builders[name] 386 if !ok { 387 err = fmt.Errorf("No such build found in template: %s", name) 388 return 389 } 390 391 // We panic if there is no builder function because this is really 392 // an internal bug that always needs to be fixed, not an error. 393 if components.Builder == nil { 394 panic("no builder function") 395 } 396 397 // Panic if there are provisioners on the template but no provisioner 398 // component finder. This is always an internal error, so we panic. 399 if len(t.Provisioners) > 0 && components.Provisioner == nil { 400 panic("no provisioner function") 401 } 402 403 builder, err := components.Builder(builderConfig.Type) 404 if err != nil { 405 return 406 } 407 408 if builder == nil { 409 err = fmt.Errorf("Builder type not found: %s", builderConfig.Type) 410 return 411 } 412 413 // Gather the Hooks 414 hooks := make(map[string][]Hook) 415 for tplEvent, tplHooks := range t.Hooks { 416 curHooks := make([]Hook, 0, len(tplHooks)) 417 418 for _, hookName := range tplHooks { 419 var hook Hook 420 hook, err = components.Hook(hookName) 421 if err != nil { 422 return 423 } 424 425 if hook == nil { 426 err = fmt.Errorf("Hook not found: %s", hookName) 427 return 428 } 429 430 curHooks = append(curHooks, hook) 431 } 432 433 hooks[tplEvent] = curHooks 434 } 435 436 // Prepare the post-processors 437 postProcessors := make([][]coreBuildPostProcessor, 0, len(t.PostProcessors)) 438 for _, rawPPs := range t.PostProcessors { 439 current := make([]coreBuildPostProcessor, 0, len(rawPPs)) 440 for _, rawPP := range rawPPs { 441 if rawPP.TemplateOnlyExcept.Skip(name) { 442 continue 443 } 444 445 pp, err := components.PostProcessor(rawPP.Type) 446 if err != nil { 447 return nil, err 448 } 449 450 if pp == nil { 451 return nil, fmt.Errorf("PostProcessor type not found: %s", rawPP.Type) 452 } 453 454 current = append(current, coreBuildPostProcessor{ 455 processor: pp, 456 processorType: rawPP.Type, 457 config: rawPP.RawConfig, 458 keepInputArtifact: rawPP.KeepInputArtifact, 459 }) 460 } 461 462 // If we have no post-processors in this chain, just continue. 463 // This can happen if the post-processors skip certain builds. 464 if len(current) == 0 { 465 continue 466 } 467 468 postProcessors = append(postProcessors, current) 469 } 470 471 // Prepare the provisioners 472 provisioners := make([]coreBuildProvisioner, 0, len(t.Provisioners)) 473 for _, rawProvisioner := range t.Provisioners { 474 if rawProvisioner.TemplateOnlyExcept.Skip(name) { 475 continue 476 } 477 478 var provisioner Provisioner 479 provisioner, err = components.Provisioner(rawProvisioner.Type) 480 if err != nil { 481 return 482 } 483 484 if provisioner == nil { 485 err = fmt.Errorf("Provisioner type not found: %s", rawProvisioner.Type) 486 return 487 } 488 489 configs := make([]interface{}, 1, 2) 490 configs[0] = rawProvisioner.RawConfig 491 492 if rawProvisioner.Override != nil { 493 if override, ok := rawProvisioner.Override[name]; ok { 494 configs = append(configs, override) 495 } 496 } 497 498 coreProv := coreBuildProvisioner{provisioner, configs} 499 provisioners = append(provisioners, coreProv) 500 } 501 502 // Prepare the variables 503 variables := make(map[string]coreBuildVariable) 504 for k, v := range t.Variables { 505 variables[k] = coreBuildVariable{ 506 Default: v.Default, 507 Required: v.Required, 508 } 509 } 510 511 b = &coreBuild{ 512 name: name, 513 builder: builder, 514 builderConfig: builderConfig.RawConfig, 515 builderType: builderConfig.Type, 516 hooks: hooks, 517 postProcessors: postProcessors, 518 provisioners: provisioners, 519 variables: variables, 520 } 521 522 return 523 } 524 525 // TemplateOnlyExcept contains the logic required for "only" and "except" 526 // meta-parameters. 527 type TemplateOnlyExcept struct { 528 Only []string 529 Except []string 530 } 531 532 // Prune will prune out the used values from the raw map. 533 func (t *TemplateOnlyExcept) Prune(raw map[string]interface{}) { 534 delete(raw, "except") 535 delete(raw, "only") 536 } 537 538 // Skip tests if we should skip putting this item onto a build. 539 func (t *TemplateOnlyExcept) Skip(name string) bool { 540 if len(t.Only) > 0 { 541 onlyFound := false 542 for _, n := range t.Only { 543 if n == name { 544 onlyFound = true 545 break 546 } 547 } 548 549 if !onlyFound { 550 // Skip this provisioner 551 return true 552 } 553 } 554 555 // If the name is in the except list, then skip that 556 for _, n := range t.Except { 557 if n == name { 558 return true 559 } 560 } 561 562 return false 563 } 564 565 // Validates the only/except parameters. 566 func (t *TemplateOnlyExcept) Validate(b map[string]RawBuilderConfig) (e []error) { 567 if len(t.Only) > 0 && len(t.Except) > 0 { 568 e = append(e, 569 fmt.Errorf("Only one of 'only' or 'except' may be specified.")) 570 } 571 572 if len(t.Only) > 0 { 573 for _, n := range t.Only { 574 if _, ok := b[n]; !ok { 575 e = append(e, 576 fmt.Errorf("'only' specified builder '%s' not found", n)) 577 } 578 } 579 } 580 581 for _, n := range t.Except { 582 if _, ok := b[n]; !ok { 583 e = append(e, 584 fmt.Errorf("'except' specified builder '%s' not found", n)) 585 } 586 } 587 588 return 589 }