diff --git a/packer/template.go b/packer/template.go index 0e6b6150b..c08ab0cde 100644 --- a/packer/template.go +++ b/packer/template.go @@ -14,15 +14,16 @@ type rawTemplate struct { Builders []map[string]interface{} Hooks map[string][]string Provisioners []map[string]interface{} - PostProcessors []map[string]interface{} `json:"post-processors"` + PostProcessors []interface{} `json:"post-processors"` } // The Template struct represents a parsed template, parsed into the most // completed form it can be without additional processing by the caller. type Template struct { - Builders map[string]rawBuilderConfig - Hooks map[string][]string - Provisioners []rawProvisionerConfig + Builders map[string]rawBuilderConfig + Hooks map[string][]string + PostProcessors [][]rawPostProcessorConfig + Provisioners []rawProvisionerConfig } // The rawBuilderConfig struct represents a raw, unprocessed builder @@ -36,6 +37,14 @@ type rawBuilderConfig struct { rawConfig interface{} } +// rawPostProcessorConfig represents a raw, unprocessed post-processor +// configuration. It contains the type of the post processor as well as the +// raw configuration that is handed to the post-processor for it to process. +type rawPostProcessorConfig struct { + Type string + rawConfig interface{} +} + // 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. @@ -61,6 +70,7 @@ func ParseTemplate(data []byte) (t *Template, err error) { 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) @@ -103,6 +113,43 @@ func ParseTemplate(data []byte) (t *Template, err error) { t.Builders[raw.Name] = raw } + // Gather all the post-processors. This is a complicated process since there + // are actually three different formats that the user can use to define + // a post-processor. + for i, rawV := range rawTpl.PostProcessors { + rawPP, err := parsePostProvisioner(i, rawV) + if err != nil { + errors = append(errors, err...) + continue + } + + t.PostProcessors[i] = make([]rawPostProcessorConfig, len(rawPP)) + configs := t.PostProcessors[i] + for j, pp := range rawPP { + var config rawPostProcessorConfig + if err := mapstructure.Decode(pp, &config); err != nil { + if merr, ok := err.(*mapstructure.Error); ok { + for _, err := range merr.Errors { + errors = append(errors, fmt.Errorf("Post-processor #%d.%d: %s", i+1, j+1, err)) + } + } else { + errors = append(errors, fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err)) + } + + continue + } + + if config.Type == "" { + errors = append(errors, fmt.Errorf("Post-processor %d.%d: missing 'type'", i+1, j+1)) + continue + } + + config.rawConfig = pp + + configs[j] = config + } + } + // Gather all the provisioners for i, v := range rawTpl.Provisioners { raw := &t.Provisioners[i] @@ -135,6 +182,43 @@ func ParseTemplate(data []byte) (t *Template, err error) { return } +func parsePostProvisioner(i int, rawV interface{}) (result []map[string]interface{}, errors []error) { + switch v := rawV.(type) { + case string: + result = []map[string]interface{}{ + {"type": v}, + } + case map[string]interface{}: + result = []map[string]interface{}{v} + case []interface{}: + result = make([]map[string]interface{}, len(v)) + errors = make([]error, 0) + for j, innerRawV := range v { + switch innerV := innerRawV.(type) { + case string: + result[j] = map[string]interface{}{"type": innerV} + case map[string]interface{}: + result[j] = innerV + case []interface{}: + errors = append( + errors, + fmt.Errorf("Post-processor %d.%d: sequences not allowed to be nested in sequences", i+1, j+1)) + default: + errors = append(errors, fmt.Errorf("Post-processor %d.%d is in a bad format.", i+1, j+1)) + } + } + + if len(errors) == 0 { + errors = nil + } + default: + result = nil + errors = []error{fmt.Errorf("Post-processor %d is in a bad format.", i+1)} + } + + return +} + // BuildNames returns a slice of the available names of builds that // this template represents. func (t *Template) BuildNames() []string { diff --git a/packer/template_test.go b/packer/template_test.go index f2297cfd5..3347d2bdc 100644 --- a/packer/template_test.go +++ b/packer/template_test.go @@ -28,8 +28,7 @@ func TestParseTemplate_Invalid(t *testing.T) { // syntax error in the JSON. data := ` { - "name": "my-image",, - "builders": [] + "builders": [], } ` @@ -43,7 +42,6 @@ func TestParseTemplate_BuilderWithoutType(t *testing.T) { data := ` { - "name": "my-image", "builders": [{}] } ` @@ -57,7 +55,6 @@ func TestParseTemplate_BuilderWithNonStringType(t *testing.T) { data := ` { - "name": "my-image", "builders": [{ "type": 42 }] @@ -73,7 +70,6 @@ func TestParseTemplate_BuilderWithoutName(t *testing.T) { data := ` { - "name": "my-image", "builders": [ { "type": "amazon-ebs" @@ -97,7 +93,6 @@ func TestParseTemplate_BuilderWithName(t *testing.T) { data := ` { - "name": "my-image", "builders": [ { "name": "bob", @@ -122,7 +117,6 @@ func TestParseTemplate_BuilderWithConflictingName(t *testing.T) { data := ` { - "name": "my-image", "builders": [ { "name": "bob", @@ -145,7 +139,6 @@ func TestParseTemplate_Hooks(t *testing.T) { data := ` { - "name": "my-image", "hooks": { "event": ["foo", "bar"] @@ -163,12 +156,65 @@ func TestParseTemplate_Hooks(t *testing.T) { assert.Equal(hooks, []string{"foo", "bar"}, "hooks should be correct") } +func TestParseTemplate_PostProcessors(t *testing.T) { + data := ` + { + "post-processors": [ + "simple", + + { "type": "detailed" }, + + [ "foo", { "type": "bar" } ] + ] + } + ` + + tpl, err := ParseTemplate([]byte(data)) + if err != nil { + t.Fatalf("error parsing: %s", err) + } + + if len(tpl.PostProcessors) != 3 { + t.Fatalf("bad number of post-processors: %d", len(tpl.PostProcessors)) + } + + pp := tpl.PostProcessors[0] + if len(pp) != 1 { + t.Fatalf("wrong number of configs in simple: %d", len(pp)) + } + + if pp[0].Type != "simple" { + t.Fatalf("wrong type for simple: %s", pp[0].Type) + } + + pp = tpl.PostProcessors[1] + if len(pp) != 1 { + t.Fatalf("wrong number of configs in detailed: %d", len(pp)) + } + + if pp[0].Type != "detailed" { + t.Fatalf("wrong type for detailed: %s", pp[0].Type) + } + + pp = tpl.PostProcessors[2] + if len(pp) != 2 { + t.Fatalf("wrong number of configs for sequence: %d", len(pp)) + } + + if pp[0].Type != "foo" { + t.Fatalf("wrong type for sequence 0: %s", pp[0].Type) + } + + if pp[1].Type != "bar" { + t.Fatalf("wrong type for sequence 1: %s", pp[1].Type) + } +} + func TestParseTemplate_ProvisionerWithoutType(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) data := ` { - "name": "my-image", "provisioners": [{}] } ` @@ -182,7 +228,6 @@ func TestParseTemplate_ProvisionerWithNonStringType(t *testing.T) { data := ` { - "name": "my-image", "provisioners": [{ "type": 42 }] @@ -198,7 +243,6 @@ func TestParseTemplate_Provisioners(t *testing.T) { data := ` { - "name": "my-image", "provisioners": [ { "type": "shell" @@ -220,7 +264,6 @@ func TestTemplate_BuildNames(t *testing.T) { data := ` { - "name": "my-image", "builders": [ { "name": "bob", @@ -247,7 +290,6 @@ func TestTemplate_BuildUnknown(t *testing.T) { data := ` { - "name": "my-image", "builders": [ { "name": "test1", @@ -270,7 +312,6 @@ func TestTemplate_BuildUnknownBuilder(t *testing.T) { data := ` { - "name": "my-image", "builders": [ { "name": "test1", @@ -295,7 +336,6 @@ func TestTemplate_Build_NilBuilderFunc(t *testing.T) { data := ` { - "name": "my-image", "builders": [ { "name": "test1", @@ -331,7 +371,6 @@ func TestTemplate_Build_NilProvisionerFunc(t *testing.T) { data := ` { - "name": "my-image", "builders": [ { "name": "test1", @@ -369,7 +408,6 @@ func TestTemplate_Build_NilProvisionerFunc_WithNoProvisioners(t *testing.T) { data := ` { - "name": "my-image", "builders": [ { "name": "test1", @@ -394,7 +432,6 @@ func TestTemplate_Build(t *testing.T) { data := ` { - "name": "my-image", "builders": [ { "name": "test1", @@ -452,7 +489,6 @@ func TestTemplate_Build_ProvisionerOverride(t *testing.T) { data := ` { - "name": "my-image", "builders": [ { "name": "test1",