package packer import ( "io/ioutil" "os" "reflect" "sort" "testing" ) func testTemplateComponentFinder() *ComponentFinder { builder := new(MockBuilder) pp := new(TestPostProcessor) provisioner := &MockProvisioner{} builderMap := map[string]Builder{ "test-builder": builder, } ppMap := map[string]PostProcessor{ "test-pp": pp, } provisionerMap := map[string]Provisioner{ "test-prov": provisioner, } builderFactory := func(n string) (Builder, error) { return builderMap[n], nil } ppFactory := func(n string) (PostProcessor, error) { return ppMap[n], nil } provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil } return &ComponentFinder{ Builder: builderFactory, PostProcessor: ppFactory, Provisioner: provFactory, } } func TestParseTemplateFile_basic(t *testing.T) { data := ` { "builders": [{"type": "something"}] } ` tf, err := ioutil.TempFile("", "packer") if err != nil { t.Fatalf("err: %s", err) } tf.Write([]byte(data)) tf.Close() result, err := ParseTemplateFile(tf.Name()) if err != nil { t.Fatalf("err: %s", err) } if len(result.Builders) != 1 { t.Fatalf("bad: %#v", result.Builders) } } func TestParseTemplateFile_stdin(t *testing.T) { data := ` { "builders": [{"type": "something"}] } ` tf, err := ioutil.TempFile("", "packer") if err != nil { t.Fatalf("err: %s", err) } defer tf.Close() tf.Write([]byte(data)) // Sync and seek to the beginning so that we can re-read the contents tf.Sync() tf.Seek(0, 0) // Set stdin to something we control oldStdin := os.Stdin defer func() { os.Stdin = oldStdin }() os.Stdin = tf result, err := ParseTemplateFile("-") if err != nil { t.Fatalf("err: %s", err) } if len(result.Builders) != 1 { t.Fatalf("bad: %#v", result.Builders) } } func TestParseTemplate_Basic(t *testing.T) { data := ` { "builders": [{"type": "something"}] } ` result, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } if result == nil { t.Fatal("should have result") } if len(result.Builders) != 1 { t.Fatalf("bad: %#v", result.Builders) } } func TestParseTemplate_Invalid(t *testing.T) { // Note there is an extra comma below for a purposeful // syntax error in the JSON. data := ` { "builders": [], } ` result, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("shold have error") } if result != nil { t.Fatal("should not have result") } } func TestParseTemplate_InvalidKeys(t *testing.T) { // 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)) if err == nil { t.Fatal("should have error") } if result != nil { t.Fatal("should not have result") } } func TestParseTemplate_BuilderWithoutType(t *testing.T) { data := ` { "builders": [{}] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestParseTemplate_BuilderWithNonStringType(t *testing.T) { data := ` { "builders": [{ "type": 42 }] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestParseTemplate_BuilderWithoutName(t *testing.T) { data := ` { "builders": [ { "type": "amazon-ebs" } ] } ` result, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } if result == nil { t.Fatal("should have result") } if len(result.Builders) != 1 { t.Fatalf("bad: %#v", result.Builders) } builder, ok := result.Builders["amazon-ebs"] if !ok { t.Fatal("should be ok") } if builder.Type != "amazon-ebs" { t.Fatalf("bad: %#v", builder.Type) } } func TestParseTemplate_BuilderWithName(t *testing.T) { data := ` { "builders": [ { "name": "bob", "type": "amazon-ebs" } ] } ` result, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } if result == nil { t.Fatal("should have result") } if len(result.Builders) != 1 { t.Fatalf("bad: %#v", result.Builders) } builder, ok := result.Builders["bob"] if !ok { t.Fatal("should be ok") } if builder.Type != "amazon-ebs" { t.Fatalf("bad: %#v", builder.Type) } RawConfig := builder.RawConfig if RawConfig == nil { t.Fatal("missing builder raw config") } expected := map[string]interface{}{ "type": "amazon-ebs", } if !reflect.DeepEqual(RawConfig, expected) { t.Fatalf("bad raw: %#v", RawConfig) } } func TestParseTemplate_BuilderWithConflictingName(t *testing.T) { data := ` { "builders": [ { "name": "bob", "type": "amazon-ebs" }, { "name": "bob", "type": "foo", } ] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestParseTemplate_Hooks(t *testing.T) { data := ` { "builders": [{"type": "foo"}], "hooks": { "event": ["foo", "bar"] } } ` result, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } if result == nil { t.Fatal("should have result") } if len(result.Hooks) != 1 { t.Fatalf("bad: %#v", result.Hooks) } hooks, ok := result.Hooks["event"] if !ok { t.Fatal("should be okay") } if !reflect.DeepEqual(hooks, []string{"foo", "bar"}) { t.Fatalf("bad: %#v", hooks) } } func TestParseTemplate_PostProcessors(t *testing.T) { data := ` { "builders": [{"type": "foo"}], "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) { data := ` { "builders": [{"type": "foo"}], "provisioners": [{}] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("err should not be nil") } } func TestParseTemplate_ProvisionerWithNonStringType(t *testing.T) { data := ` { "builders": [{"type": "foo"}], "provisioners": [{ "type": 42 }] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestParseTemplate_Provisioners(t *testing.T) { data := ` { "builders": [{"type": "foo"}], "provisioners": [ { "type": "shell" } ] } ` result, err := ParseTemplate([]byte(data)) if err != nil { t.Fatal("err: %s", err) } if result == nil { t.Fatal("should have result") } if len(result.Provisioners) != 1 { t.Fatalf("bad: %#v", result.Provisioners) } if result.Provisioners[0].Type != "shell" { t.Fatalf("bad: %#v", result.Provisioners[0].Type) } if result.Provisioners[0].RawConfig == nil { t.Fatal("should have raw config") } } func TestParseTemplate_Variables(t *testing.T) { data := ` { "variables": { "foo": "bar", "bar": null, "baz": 27 }, "builders": [{"type": "something"}] } ` result, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } if result.Variables == nil || len(result.Variables) != 3 { t.Fatalf("bad vars: %#v", result.Variables) } if result.Variables["foo"].Default != "bar" { t.Fatal("foo default is not right") } if result.Variables["foo"].Required { t.Fatal("foo should not be required") } if result.Variables["bar"].Default != "" { t.Fatal("default should be empty") } if !result.Variables["bar"].Required { t.Fatal("bar should be required") } if result.Variables["baz"].Default != "27" { t.Fatal("default should be empty") } if result.Variables["baz"].Required { t.Fatal("baz should not be required") } } func TestParseTemplate_variablesBadDefault(t *testing.T) { data := ` { "variables": { "foo": 7, }, "builders": [{"type": "something"}] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestTemplate_BuildNames(t *testing.T) { data := ` { "builders": [ { "name": "bob", "type": "amazon-ebs" }, { "name": "chris", "type": "another" } ] } ` result, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } buildNames := result.BuildNames() sort.Strings(buildNames) if !reflect.DeepEqual(buildNames, []string{"bob", "chris"}) { t.Fatalf("bad: %#v", buildNames) } } func TestTemplate_BuildUnknown(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" } ] } ` template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("bad: %s", err) } build, err := template.Build("nope", nil) if build != nil { t.Fatalf("build should be nil: %#v", build) } if err == nil { t.Fatal("should have error") } } func TestTemplate_BuildUnknownBuilder(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" } ] } ` template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } builderFactory := func(string) (Builder, error) { return nil, nil } components := &ComponentFinder{Builder: builderFactory} build, err := template.Build("test1", components) if err == nil { t.Fatal("should have error") } if build != nil { t.Fatalf("bad: %#v", build) } } func TestTemplate_Build_NilBuilderFunc(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" } ], "provisioners": [ { "type": "test-prov" } ] } ` template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } defer func() { p := recover() if p == nil { t.Fatal("should panic") } if p.(string) != "no builder function" { t.Fatalf("bad panic: %s", p.(string)) } }() template.Build("test1", &ComponentFinder{}) } func TestTemplate_Build_NilProvisionerFunc(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" } ], "provisioners": [ { "type": "test-prov" } ] } ` template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } defer func() { p := recover() if p == nil { t.Fatal("should panic") } if p.(string) != "no provisioner function" { t.Fatalf("bad panic: %s", p.(string)) } }() template.Build("test1", &ComponentFinder{ Builder: func(string) (Builder, error) { return nil, nil }, }) } func TestTemplate_Build_NilProvisionerFunc_WithNoProvisioners(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" } ], "provisioners": [] } ` template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } template.Build("test1", &ComponentFinder{ Builder: func(string) (Builder, error) { return nil, nil }, }) } func TestTemplate_Build(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" } ], "provisioners": [ { "type": "test-prov" } ], "post-processors": [ "simple", [ "simple", { "type": "simple", "keep_input_artifact": true } ] ] } ` expectedConfig := map[string]interface{}{ "type": "test-builder", } template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } builder := new(MockBuilder) builderMap := map[string]Builder{ "test-builder": builder, } provisioner := &MockProvisioner{} provisionerMap := map[string]Provisioner{ "test-prov": provisioner, } pp := new(TestPostProcessor) ppMap := map[string]PostProcessor{ "simple": pp, } builderFactory := func(n string) (Builder, error) { return builderMap[n], nil } ppFactory := func(n string) (PostProcessor, error) { return ppMap[n], nil } provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil } components := &ComponentFinder{ Builder: builderFactory, PostProcessor: ppFactory, Provisioner: provFactory, } // Get the build, verifying we can get it without issue, but also // that the proper builder was looked up and used for the build. build, err := template.Build("test1", components) if err != nil { t.Fatalf("err: %s", err) } coreBuild, ok := build.(*coreBuild) if !ok { t.Fatal("should be ok") } if coreBuild.builder != builder { t.Fatalf("bad: %#v", coreBuild.builder) } if !reflect.DeepEqual(coreBuild.builderConfig, expectedConfig) { t.Fatalf("bad: %#v", coreBuild.builderConfig) } if len(coreBuild.provisioners) != 1 { t.Fatalf("bad: %#v", coreBuild.provisioners) } if len(coreBuild.postProcessors) != 2 { t.Fatalf("bad: %#v", coreBuild.postProcessors) } if len(coreBuild.postProcessors[0]) != 1 { t.Fatalf("bad: %#v", coreBuild.postProcessors[0]) } if len(coreBuild.postProcessors[1]) != 2 { t.Fatalf("bad: %#v", coreBuild.postProcessors[1]) } if coreBuild.postProcessors[1][0].keepInputArtifact { t.Fatal("postProcessors[1][0] should not keep input artifact") } if !coreBuild.postProcessors[1][1].keepInputArtifact { t.Fatal("postProcessors[1][1] should keep input artifact") } config := coreBuild.postProcessors[1][1].config if _, ok := config["keep_input_artifact"]; ok { t.Fatal("should not have keep_input_artifact") } } func TestTemplateBuild_exceptOnlyPP(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" }, { "name": "test2", "type": "test-builder" } ], "post-processors": [ { "type": "test-pp", "except": ["test1"], "only": ["test1"] } ] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestTemplateBuild_exceptOnlyProv(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" }, { "name": "test2", "type": "test-builder" } ], "provisioners": [ { "type": "test-prov", "except": ["test1"], "only": ["test1"] } ] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestTemplateBuild_exceptPPInvalid(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" }, { "name": "test2", "type": "test-builder" } ], "post-processors": [ { "type": "test-pp", "except": ["test5"] } ] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestTemplateBuild_exceptPP(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" }, { "name": "test2", "type": "test-builder" } ], "post-processors": [ { "type": "test-pp", "except": ["test1"] } ] } ` template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } // Verify test1 has no post-processors build, err := template.Build("test1", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) } cbuild := build.(*coreBuild) if len(cbuild.postProcessors) > 0 { t.Fatal("should have no postProcessors") } // Verify test2 has no post-processors build, err = template.Build("test2", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) } cbuild = build.(*coreBuild) if len(cbuild.postProcessors) != 1 { t.Fatalf("invalid: %d", len(cbuild.postProcessors)) } } func TestTemplateBuild_exceptProvInvalid(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" }, { "name": "test2", "type": "test-builder" } ], "provisioners": [ { "type": "test-prov", "except": ["test5"] } ] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestTemplateBuild_exceptProv(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" }, { "name": "test2", "type": "test-builder" } ], "provisioners": [ { "type": "test-prov", "except": ["test1"] } ] } ` template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } // Verify test1 has no provisioners build, err := template.Build("test1", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) } cbuild := build.(*coreBuild) if len(cbuild.provisioners) > 0 { t.Fatal("should have no provisioners") } // Verify test2 has no provisioners build, err = template.Build("test2", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) } cbuild = build.(*coreBuild) if len(cbuild.provisioners) != 1 { t.Fatalf("invalid: %d", len(cbuild.provisioners)) } } func TestTemplateBuild_onlyPPInvalid(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" }, { "name": "test2", "type": "test-builder" } ], "post-processors": [ { "type": "test-pp", "only": ["test5"] } ] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestTemplateBuild_onlyPP(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" }, { "name": "test2", "type": "test-builder" } ], "post-processors": [ { "type": "test-pp", "only": ["test2"] } ] } ` template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } // Verify test1 has no post-processors build, err := template.Build("test1", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) } cbuild := build.(*coreBuild) if len(cbuild.postProcessors) > 0 { t.Fatal("should have no postProcessors") } // Verify test2 has no post-processors build, err = template.Build("test2", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) } cbuild = build.(*coreBuild) if len(cbuild.postProcessors) != 1 { t.Fatalf("invalid: %d", len(cbuild.postProcessors)) } } func TestTemplateBuild_onlyProvInvalid(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" }, { "name": "test2", "type": "test-builder" } ], "provisioners": [ { "type": "test-prov", "only": ["test5"] } ] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestTemplateBuild_onlyProv(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" }, { "name": "test2", "type": "test-builder" } ], "provisioners": [ { "type": "test-prov", "only": ["test2"] } ] } ` template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } // Verify test1 has no provisioners build, err := template.Build("test1", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) } cbuild := build.(*coreBuild) if len(cbuild.provisioners) > 0 { t.Fatal("should have no provisioners") } // Verify test2 has no provisioners build, err = template.Build("test2", testTemplateComponentFinder()) if err != nil { t.Fatalf("err: %s", err) } cbuild = build.(*coreBuild) if len(cbuild.provisioners) != 1 { t.Fatalf("invalid: %d", len(cbuild.provisioners)) } } func TestTemplate_Build_ProvisionerOverride(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" } ], "provisioners": [ { "type": "test-prov", "override": { "test1": {} } } ] } ` template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } RawConfig := template.Provisioners[0].RawConfig if RawConfig == nil { t.Fatal("missing provisioner raw config") } expected := map[string]interface{}{ "type": "test-prov", } if !reflect.DeepEqual(RawConfig, expected) { t.Fatalf("bad raw: %#v", RawConfig) } builder := new(MockBuilder) builderMap := map[string]Builder{ "test-builder": builder, } provisioner := &MockProvisioner{} provisionerMap := map[string]Provisioner{ "test-prov": provisioner, } builderFactory := func(n string) (Builder, error) { return builderMap[n], nil } provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil } components := &ComponentFinder{ Builder: builderFactory, Provisioner: provFactory, } // Get the build, verifying we can get it without issue, but also // that the proper builder was looked up and used for the build. build, err := template.Build("test1", components) if err != nil { t.Fatalf("err: %s", err) } coreBuild, ok := build.(*coreBuild) if !ok { t.Fatal("should be okay") } if len(coreBuild.provisioners) != 1 { t.Fatalf("bad: %#v", coreBuild.provisioners) } if len(coreBuild.provisioners[0].config) != 2 { t.Fatalf("bad: %#v", coreBuild.provisioners[0].config) } } func TestTemplate_Build_ProvisionerOverrideBad(t *testing.T) { data := ` { "builders": [ { "name": "test1", "type": "test-builder" } ], "provisioners": [ { "type": "test-prov", "override": { "testNope": {} } } ] } ` _, err := ParseTemplate([]byte(data)) if err == nil { t.Fatal("should have error") } } func TestTemplateBuild_variables(t *testing.T) { data := ` { "variables": { "foo": "bar" }, "builders": [ { "name": "test1", "type": "test-builder" } ] } ` template, err := ParseTemplate([]byte(data)) if err != nil { t.Fatalf("err: %s", err) } build, err := template.Build("test1", testComponentFinder()) if err != nil { t.Fatalf("err: %s", err) } coreBuild, ok := build.(*coreBuild) if !ok { t.Fatalf("couldn't convert!") } if len(coreBuild.variables) != 1 { t.Fatalf("bad vars: %#v", coreBuild.variables) } }