diff --git a/packer/template.go b/packer/template.go index fb24df062..f7cfca03e 100644 --- a/packer/template.go +++ b/packer/template.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "github.com/mitchellh/mapstructure" + "sort" ) // The rawTemplate struct represents the structure of a template read @@ -15,7 +16,7 @@ type rawTemplate struct { Builders []map[string]interface{} Hooks map[string][]string Provisioners []map[string]interface{} - PostProcessors []interface{} `json:"post-processors"` + PostProcessors []interface{} `mapstructure:"post-processors"` } // The Template struct represents a parsed template, parsed into the most @@ -63,8 +64,8 @@ type rawProvisionerConfig struct { // 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) { - var rawTpl rawTemplate - err = json.Unmarshal(data, &rawTpl) + var rawTplInterface interface{} + err = json.Unmarshal(data, &rawTplInterface) if err != nil { syntaxErr, ok := err.(*json.SyntaxError) if !ok { @@ -92,14 +93,41 @@ func ParseTemplate(data []byte) (t *Template, err error) { return } + // Decode the raw template interface into the actual rawTemplate + // structure, checking for any extranneous keys along the way. + var md mapstructure.Metadata + var rawTpl rawTemplate + decoderConfig := &mapstructure.DecoderConfig{ + Metadata: &md, + Result: &rawTpl, + } + + decoder, err := mapstructure.NewDecoder(decoderConfig) + if err != nil { + return + } + + err = decoder.Decode(rawTplInterface) + if err != nil { + return + } + + errors := make([]error, 0) + + if len(md.Unused) > 0 { + sort.Strings(md.Unused) + for _, unused := range md.Unused { + errors = append( + errors, fmt.Errorf("Unknown root level key in template: '%s'", unused)) + } + } + t = &Template{} t.Builders = make(map[string]rawBuilderConfig) t.Hooks = rawTpl.Hooks t.PostProcessors = make([][]rawPostProcessorConfig, len(rawTpl.PostProcessors)) t.Provisioners = make([]rawProvisionerConfig, len(rawTpl.Provisioners)) - errors := make([]error, 0) - // Gather all the builders for i, v := range rawTpl.Builders { var raw rawBuilderConfig @@ -203,6 +231,7 @@ func ParseTemplate(data []byte) (t *Template, err error) { // If there were errors, we put it into a MultiError and return if len(errors) > 0 { err = &MultiError{errors} + t = nil return } diff --git a/packer/template_test.go b/packer/template_test.go index 6c7414c8a..699ead006 100644 --- a/packer/template_test.go +++ b/packer/template_test.go @@ -37,6 +37,23 @@ func TestParseTemplate_Invalid(t *testing.T) { assert.Nil(result, "should have no result") } +func TestParseTemplate_InvalidKeys(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + // Note there is an extra comma below for a purposeful + // syntax error in the JSON. + data := ` + { + "builders": [{"type": "foo"}], + "what is this": "" + } + ` + + result, err := ParseTemplate([]byte(data)) + assert.NotNil(err, "should have an error") + assert.Nil(result, "should have no result") +} + func TestParseTemplate_BuilderWithoutType(t *testing.T) { assert := asserts.NewTestingAsserts(t, true)