packer-cn/packer/template.go

243 lines
6.3 KiB
Go
Raw Normal View History

2013-03-25 19:29:26 -04:00
package packer
2013-04-21 15:36:55 -04:00
import (
"encoding/json"
"fmt"
"github.com/mitchellh/mapstructure"
2013-04-21 15:36:55 -04:00
)
2013-03-25 19:29:26 -04:00
// The rawTemplate struct represents the structure of a template read
// directly from a file. The builders and other components map just to
// "interface{}" pointers since we actually don't know what their contents
// are until we read the "type" field.
type rawTemplate struct {
2013-06-17 17:54:04 -04:00
Builders []map[string]interface{}
Hooks map[string][]string
Provisioners []map[string]interface{}
PostProcessors []map[string]interface{} `json:"post-processors"`
2013-03-25 19:29:26 -04:00
}
2013-04-20 20:33:27 -04:00
// The Template struct represents a parsed template, parsed into the most
// completed form it can be without additional processing by the caller.
2013-03-25 19:29:26 -04:00
type Template struct {
Builders map[string]rawBuilderConfig
Hooks map[string][]string
Provisioners []rawProvisionerConfig
2013-03-25 19:29:26 -04:00
}
// The rawBuilderConfig struct represents a raw, unprocessed builder
// configuration. It contains the name of the builder as well as the
// raw configuration. If requested, this is used to compile into a full
// builder configuration at some point.
type rawBuilderConfig struct {
Name string
Type string
rawConfig interface{}
2013-03-25 19:29:26 -04:00
}
// rawProvisionerConfig represents a raw, unprocessed provisioner configuration.
// It contains the type of the provisioner as well as the raw configuration
// that is handed to the provisioner for it to process.
type rawProvisionerConfig struct {
Type string
Override map[string]interface{}
rawConfig interface{}
}
2013-04-20 20:33:27 -04:00
// ParseTemplate takes a byte slice and parses a Template from it, returning
2013-05-22 17:36:21 -04:00
// the template and possibly errors while loading the template. The error
// could potentially be a MultiError, representing multiple errors. Knowing
// and checking for this can be useful, if you wish to format it in a certain
// way.
func ParseTemplate(data []byte) (t *Template, err error) {
2013-03-25 19:29:26 -04:00
var rawTpl rawTemplate
err = json.Unmarshal(data, &rawTpl)
2013-03-25 19:29:26 -04:00
if err != nil {
return
2013-03-25 19:29:26 -04:00
}
2013-04-15 20:04:19 -04:00
t = &Template{}
t.Builders = make(map[string]rawBuilderConfig)
t.Hooks = rawTpl.Hooks
t.Provisioners = make([]rawProvisionerConfig, len(rawTpl.Provisioners))
2013-04-15 20:04:19 -04:00
errors := make([]error, 0)
2013-05-22 17:36:21 -04:00
// Gather all the builders
for i, v := range rawTpl.Builders {
var raw rawBuilderConfig
if err := mapstructure.Decode(v, &raw); err != nil {
if merr, ok := err.(*mapstructure.Error); ok {
for _, err := range merr.Errors {
errors = append(errors, fmt.Errorf("builder %d: %s", i+1, err))
}
} else {
errors = append(errors, fmt.Errorf("builder %d: %s", i+1, err))
}
continue
}
if raw.Type == "" {
2013-05-22 17:36:33 -04:00
errors = append(errors, fmt.Errorf("builder %d: missing 'type'", i+1))
continue
2013-04-15 20:04:19 -04:00
}
// Attempt to get the name of the builder. If the "name" key
// missing, use the "type" field, which is guaranteed to exist
// at this point.
if raw.Name == "" {
raw.Name = raw.Type
}
2013-04-15 20:04:19 -04:00
// Check if we already have a builder with this name and error if so
if _, ok := t.Builders[raw.Name]; ok {
errors = append(errors, fmt.Errorf("builder with name '%s' already exists", raw.Name))
continue
2013-04-15 20:04:19 -04:00
}
raw.rawConfig = v
t.Builders[raw.Name] = raw
}
// Gather all the provisioners
for i, v := range rawTpl.Provisioners {
raw := &t.Provisioners[i]
if err := mapstructure.Decode(v, raw); err != nil {
if merr, ok := err.(*mapstructure.Error); ok {
for _, err := range merr.Errors {
errors = append(errors, fmt.Errorf("provisioner %d: %s", i+1, err))
}
} else {
errors = append(errors, fmt.Errorf("provisioner %d: %s", i+1, err))
}
continue
}
if raw.Type == "" {
errors = append(errors, fmt.Errorf("provisioner %d: missing 'type'", i+1))
continue
}
raw.rawConfig = v
}
2013-05-22 17:36:21 -04:00
// If there were errors, we put it into a MultiError and return
if len(errors) > 0 {
err = &MultiError{errors}
return
}
return
2013-03-25 19:29:26 -04:00
}
2013-04-21 15:36:55 -04:00
// BuildNames returns a slice of the available names of builds that
// this template represents.
func (t *Template) BuildNames() []string {
names := make([]string, 0, len(t.Builders))
2013-04-21 15:36:55 -04:00
for name, _ := range t.Builders {
names = append(names, name)
2013-04-21 15:36:55 -04:00
}
return names
}
// Build returns a Build for the given name.
//
// If the build does not exist as part of this template, an error is
// returned.
func (t *Template) Build(name string, components *ComponentFinder) (b Build, err error) {
// Setup the Builder
2013-04-21 15:36:55 -04:00
builderConfig, ok := t.Builders[name]
if !ok {
err = fmt.Errorf("No such build found in template: %s", name)
return
}
// We panic if there is no builder function because this is really
// an internal bug that always needs to be fixed, not an error.
if components.Builder == nil {
panic("no builder function")
}
2013-05-22 20:06:06 -04:00
// Panic if there are provisioners on the template but no provisioner
// component finder. This is always an internal error, so we panic.
if len(t.Provisioners) > 0 && components.Provisioner == nil {
panic("no provisioner function")
}
builder, err := components.Builder(builderConfig.Type)
if err != nil {
return
}
2013-04-21 15:36:55 -04:00
if builder == nil {
err = fmt.Errorf("Builder type not found: %s", builderConfig.Type)
2013-04-21 15:36:55 -04:00
return
}
// Gather the Hooks
hooks := make(map[string][]Hook)
for tplEvent, tplHooks := range t.Hooks {
curHooks := make([]Hook, 0, len(tplHooks))
for _, hookName := range tplHooks {
var hook Hook
hook, err = components.Hook(hookName)
if err != nil {
return
}
if hook == nil {
err = fmt.Errorf("Hook not found: %s", hookName)
return
}
curHooks = append(curHooks, hook)
}
hooks[tplEvent] = curHooks
}
// Prepare the provisioners
provisioners := make([]coreBuildProvisioner, 0, len(t.Provisioners))
for _, rawProvisioner := range t.Provisioners {
var provisioner Provisioner
provisioner, err = components.Provisioner(rawProvisioner.Type)
if err != nil {
return
}
if provisioner == nil {
err = fmt.Errorf("Provisioner type not found: %s", rawProvisioner.Type)
return
}
configs := make([]interface{}, 1, 2)
configs[0] = rawProvisioner.rawConfig
if rawProvisioner.Override != nil {
if override, ok := rawProvisioner.Override[name]; ok {
configs = append(configs, override)
}
}
coreProv := coreBuildProvisioner{provisioner, configs}
provisioners = append(provisioners, coreProv)
}
2013-05-03 23:45:38 -04:00
b = &coreBuild{
name: name,
builder: builder,
builderConfig: builderConfig.rawConfig,
hooks: hooks,
provisioners: provisioners,
2013-04-21 15:36:55 -04:00
}
return
}