From ff6573ce10f96df2d51404d19591dadbb3574cdd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 15 May 2015 21:05:47 -0700 Subject: [PATCH 01/39] template/interpolate: basic + some funcs --- template/interpolate/funcs.go | 46 +++++++++++++++++++++ template/interpolate/funcs_test.go | 66 ++++++++++++++++++++++++++++++ template/interpolate/i.go | 38 +++++++++++++++++ template/interpolate/i_test.go | 32 +++++++++++++++ template/interpolate/parse.go | 42 +++++++++++++++++++ template/interpolate/parse_test.go | 39 ++++++++++++++++++ 6 files changed, 263 insertions(+) create mode 100644 template/interpolate/funcs.go create mode 100644 template/interpolate/funcs_test.go create mode 100644 template/interpolate/i.go create mode 100644 template/interpolate/i_test.go create mode 100644 template/interpolate/parse.go create mode 100644 template/interpolate/parse_test.go diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go new file mode 100644 index 000000000..f3c17d8b7 --- /dev/null +++ b/template/interpolate/funcs.go @@ -0,0 +1,46 @@ +package interpolate + +import ( + "errors" + "os" + "text/template" +) + +// Funcs are the interpolation funcs that are available within interpolations. +var FuncGens = map[string]FuncGenerator{ + "env": funcGenEnv, + "user": funcGenUser, +} + +// FuncGenerator is a function that given a context generates a template +// function for the template. +type FuncGenerator func(*Context) interface{} + +// Funcs returns the functions that can be used for interpolation given +// a context. +func Funcs(ctx *Context) template.FuncMap { + result := make(map[string]interface{}) + for k, v := range FuncGens { + result[k] = v(ctx) + } + + return template.FuncMap(result) +} + +func funcGenEnv(ctx *Context) interface{} { + return func(k string) (string, error) { + if ctx.DisableEnv { + // The error message doesn't have to be that detailed since + // semantic checks should catch this. + return "", errors.New("env vars are not allowed here") + } + + return os.Getenv(k), nil + } +} + +func funcGenUser(ctx *Context) interface{} { + return func() string { + return "" + } +} diff --git a/template/interpolate/funcs_test.go b/template/interpolate/funcs_test.go new file mode 100644 index 000000000..2bc70b0bf --- /dev/null +++ b/template/interpolate/funcs_test.go @@ -0,0 +1,66 @@ +package interpolate + +import ( + "os" + "testing" +) + +func TestFuncEnv(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + { + `{{env "PACKER_TEST_ENV"}}`, + `foo`, + }, + + { + `{{env "PACKER_TEST_ENV_NOPE"}}`, + ``, + }, + } + + os.Setenv("PACKER_TEST_ENV", "foo") + defer os.Setenv("PACKER_TEST_ENV", "") + + ctx := &Context{} + for _, tc := range cases { + i := &I{Value: tc.Input} + result, err := i.Render(ctx) + if err != nil { + t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err) + } + + if result != tc.Output { + t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result) + } + } +} + +func TestFuncEnv_disable(t *testing.T) { + cases := []struct { + Input string + Output string + Error bool + }{ + { + `{{env "PACKER_TEST_ENV"}}`, + "", + true, + }, + } + + ctx := &Context{DisableEnv: true} + for _, tc := range cases { + i := &I{Value: tc.Input} + result, err := i.Render(ctx) + if (err != nil) != tc.Error { + t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err) + } + + if result != tc.Output { + t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result) + } + } +} diff --git a/template/interpolate/i.go b/template/interpolate/i.go new file mode 100644 index 000000000..68095a03f --- /dev/null +++ b/template/interpolate/i.go @@ -0,0 +1,38 @@ +package interpolate + +import ( + "bytes" + "text/template" +) + +// Context is the context that an interpolation is done in. This defines +// things such as available variables. +type Context struct { + DisableEnv bool +} + +// I stands for "interpolation" and is the main interpolation struct +// in order to render values. +type I struct { + Value string +} + +// Render renders the interpolation with the given context. +func (i *I) Render(ctx *Context) (string, error) { + tpl, err := i.template(ctx) + if err != nil { + return "", err + } + + var result bytes.Buffer + data := map[string]interface{}{} + if err := tpl.Execute(&result, data); err != nil { + return "", err + } + + return result.String(), nil +} + +func (i *I) template(ctx *Context) (*template.Template, error) { + return template.New("root").Funcs(Funcs(ctx)).Parse(i.Value) +} diff --git a/template/interpolate/i_test.go b/template/interpolate/i_test.go new file mode 100644 index 000000000..a678afbc4 --- /dev/null +++ b/template/interpolate/i_test.go @@ -0,0 +1,32 @@ +package interpolate + +import ( + "testing" +) + +func TestIRender(t *testing.T) { + cases := map[string]struct { + Ctx *Context + Value string + Result string + }{ + "basic": { + nil, + "foo", + "foo", + }, + } + + for k, tc := range cases { + i := &I{Value: tc.Value} + result, err := i.Render(tc.Ctx) + if err != nil { + t.Fatalf("%s\n\ninput: %s\n\nerr: %s", k, tc.Value, err) + } + if result != tc.Result { + t.Fatalf( + "%s\n\ninput: %s\n\nexpected: %s\n\ngot: %s", + k, tc.Value, tc.Result, result) + } + } +} diff --git a/template/interpolate/parse.go b/template/interpolate/parse.go new file mode 100644 index 000000000..b18079510 --- /dev/null +++ b/template/interpolate/parse.go @@ -0,0 +1,42 @@ +package interpolate + +import ( + "fmt" + "text/template" + "text/template/parse" +) + +// functionsCalled returns a map (to be used as a set) of the functions +// that are called from the given text template. +func functionsCalled(t *template.Template) map[string]struct{} { + result := make(map[string]struct{}) + functionsCalledWalk(t.Tree.Root, result) + return result +} + +func functionsCalledWalk(raw parse.Node, r map[string]struct{}) { + switch node := raw.(type) { + case *parse.ActionNode: + functionsCalledWalk(node.Pipe, r) + case *parse.CommandNode: + if in, ok := node.Args[0].(*parse.IdentifierNode); ok { + r[in.Ident] = struct{}{} + } + + for _, n := range node.Args[1:] { + functionsCalledWalk(n, r) + } + case *parse.ListNode: + for _, n := range node.Nodes { + functionsCalledWalk(n, r) + } + case *parse.PipeNode: + for _, n := range node.Cmds { + functionsCalledWalk(n, r) + } + case *parse.StringNode, *parse.TextNode: + // Ignore + default: + panic(fmt.Sprintf("unknown type: %T", node)) + } +} diff --git a/template/interpolate/parse_test.go b/template/interpolate/parse_test.go new file mode 100644 index 000000000..3398ddbf1 --- /dev/null +++ b/template/interpolate/parse_test.go @@ -0,0 +1,39 @@ +package interpolate + +import ( + "reflect" + "testing" + "text/template" +) + +func TestFunctionsCalled(t *testing.T) { + cases := []struct { + Input string + Result map[string]struct{} + }{ + { + "foo", + map[string]struct{}{}, + }, + + { + "foo {{user `bar`}}", + map[string]struct{}{ + "user": struct{}{}, + }, + }, + } + + funcs := Funcs(&Context{}) + for _, tc := range cases { + tpl, err := template.New("root").Funcs(funcs).Parse(tc.Input) + if err != nil { + t.Fatalf("err parsing: %v\n\n%s", tc.Input, err) + } + + actual := functionsCalled(tpl) + if !reflect.DeepEqual(actual, tc.Result) { + t.Fatalf("bad: %v\n\ngot: %#v", tc.Input, actual) + } + } +} From 125369d1026718e1ac6bda3adce1dcbcf33e965a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 15 May 2015 21:08:46 -0700 Subject: [PATCH 02/39] template/interpolate: can specify template data --- template/interpolate/i.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/template/interpolate/i.go b/template/interpolate/i.go index 68095a03f..68c60d95c 100644 --- a/template/interpolate/i.go +++ b/template/interpolate/i.go @@ -8,6 +8,10 @@ import ( // Context is the context that an interpolation is done in. This defines // things such as available variables. type Context struct { + // Data is the data for the template that is available + Data interface{} + + // DisableEnv disables the env function DisableEnv bool } @@ -25,7 +29,10 @@ func (i *I) Render(ctx *Context) (string, error) { } var result bytes.Buffer - data := map[string]interface{}{} + var data interface{} + if ctx != nil { + data = ctx.Data + } if err := tpl.Execute(&result, data); err != nil { return "", err } From 5d205ec1fcabd860befe5ed11c04a6750d0323be Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 15 May 2015 21:10:12 -0700 Subject: [PATCH 03/39] template/interpolate: wd --- template/interpolate/funcs.go | 7 +++++++ template/interpolate/funcs_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go index f3c17d8b7..602702caf 100644 --- a/template/interpolate/funcs.go +++ b/template/interpolate/funcs.go @@ -9,6 +9,7 @@ import ( // Funcs are the interpolation funcs that are available within interpolations. var FuncGens = map[string]FuncGenerator{ "env": funcGenEnv, + "pwd": funcGenPwd, "user": funcGenUser, } @@ -39,6 +40,12 @@ func funcGenEnv(ctx *Context) interface{} { } } +func funcGenPwd(ctx *Context) interface{} { + return func() (string, error) { + return os.Getwd() + } +} + func funcGenUser(ctx *Context) interface{} { return func() string { return "" diff --git a/template/interpolate/funcs_test.go b/template/interpolate/funcs_test.go index 2bc70b0bf..7bd5c4647 100644 --- a/template/interpolate/funcs_test.go +++ b/template/interpolate/funcs_test.go @@ -64,3 +64,33 @@ func TestFuncEnv_disable(t *testing.T) { } } } + +func TestFuncPwd(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatalf("err: %s", err) + } + + cases := []struct { + Input string + Output string + }{ + { + `{{pwd}}`, + wd, + }, + } + + ctx := &Context{} + for _, tc := range cases { + i := &I{Value: tc.Input} + result, err := i.Render(ctx) + if err != nil { + t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err) + } + + if result != tc.Output { + t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result) + } + } +} From b84ec8da4b66d0b6513e6517043b8968e7e9e589 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 15 May 2015 21:12:54 -0700 Subject: [PATCH 04/39] template/interpolate: isotime --- template/interpolate/funcs.go | 23 ++++++++++++++++++++--- template/interpolate/funcs_test.go | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go index 602702caf..51fcd0911 100644 --- a/template/interpolate/funcs.go +++ b/template/interpolate/funcs.go @@ -2,15 +2,18 @@ package interpolate import ( "errors" + "fmt" "os" "text/template" + "time" ) // Funcs are the interpolation funcs that are available within interpolations. var FuncGens = map[string]FuncGenerator{ - "env": funcGenEnv, - "pwd": funcGenPwd, - "user": funcGenUser, + "env": funcGenEnv, + "isotime": funcGenIsotime, + "pwd": funcGenPwd, + "user": funcGenUser, } // FuncGenerator is a function that given a context generates a template @@ -40,6 +43,20 @@ func funcGenEnv(ctx *Context) interface{} { } } +func funcGenIsotime(ctx *Context) interface{} { + return func(format ...string) (string, error) { + if len(format) == 0 { + return time.Now().UTC().Format(time.RFC3339), nil + } + + if len(format) > 1 { + return "", fmt.Errorf("too many values, 1 needed: %v", format) + } + + return time.Now().UTC().Format(format[0]), nil + } +} + func funcGenPwd(ctx *Context) interface{} { return func() (string, error) { return os.Getwd() diff --git a/template/interpolate/funcs_test.go b/template/interpolate/funcs_test.go index 7bd5c4647..ef0753e4f 100644 --- a/template/interpolate/funcs_test.go +++ b/template/interpolate/funcs_test.go @@ -3,6 +3,7 @@ package interpolate import ( "os" "testing" + "time" ) func TestFuncEnv(t *testing.T) { @@ -65,6 +66,25 @@ func TestFuncEnv_disable(t *testing.T) { } } +func TestFuncIsotime(t *testing.T) { + ctx := &Context{} + i := &I{Value: "{{isotime}}"} + result, err := i.Render(ctx) + if err != nil { + t.Fatalf("err: %s", err) + } + + val, err := time.Parse(time.RFC3339, result) + if err != nil { + t.Fatalf("err: %s", err) + } + + currentTime := time.Now().UTC() + if currentTime.Sub(val) > 2*time.Second { + t.Fatalf("val: %d (current: %d)", val, currentTime) + } +} + func TestFuncPwd(t *testing.T) { wd, err := os.Getwd() if err != nil { From 7659a91445b1e63ea614dcb715d54dac008c307d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 15 May 2015 21:14:41 -0700 Subject: [PATCH 05/39] template/interpolate: timestamp --- template/interpolate/funcs.go | 25 +++++++++++++++++++++---- template/interpolate/funcs_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go index 51fcd0911..ead8eb95e 100644 --- a/template/interpolate/funcs.go +++ b/template/interpolate/funcs.go @@ -4,16 +4,27 @@ import ( "errors" "fmt" "os" + "strconv" "text/template" "time" ) +// InitTime is the UTC time when this package was initialized. It is +// used as the timestamp for all configuration templates so that they +// match for a single build. +var InitTime time.Time + +func init() { + InitTime = time.Now().UTC() +} + // Funcs are the interpolation funcs that are available within interpolations. var FuncGens = map[string]FuncGenerator{ - "env": funcGenEnv, - "isotime": funcGenIsotime, - "pwd": funcGenPwd, - "user": funcGenUser, + "env": funcGenEnv, + "isotime": funcGenIsotime, + "pwd": funcGenPwd, + "timestamp": funcGenTimestamp, + "user": funcGenUser, } // FuncGenerator is a function that given a context generates a template @@ -63,6 +74,12 @@ func funcGenPwd(ctx *Context) interface{} { } } +func funcGenTimestamp(ctx *Context) interface{} { + return func() string { + return strconv.FormatInt(InitTime.Unix(), 10) + } +} + func funcGenUser(ctx *Context) interface{} { return func() string { return "" diff --git a/template/interpolate/funcs_test.go b/template/interpolate/funcs_test.go index ef0753e4f..37dee1ad6 100644 --- a/template/interpolate/funcs_test.go +++ b/template/interpolate/funcs_test.go @@ -2,6 +2,7 @@ package interpolate import ( "os" + "strconv" "testing" "time" ) @@ -114,3 +115,30 @@ func TestFuncPwd(t *testing.T) { } } } + +func TestFuncTimestamp(t *testing.T) { + expected := strconv.FormatInt(InitTime.Unix(), 10) + + cases := []struct { + Input string + Output string + }{ + { + `{{timestamp}}`, + expected, + }, + } + + ctx := &Context{} + for _, tc := range cases { + i := &I{Value: tc.Input} + result, err := i.Render(ctx) + if err != nil { + t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err) + } + + if result != tc.Output { + t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result) + } + } +} From a4b5e08fe48dc366dbf493326caf17476d8cab07 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 15 May 2015 21:16:52 -0700 Subject: [PATCH 06/39] template/interpolate: upper/lower --- template/interpolate/funcs.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go index ead8eb95e..8b081966c 100644 --- a/template/interpolate/funcs.go +++ b/template/interpolate/funcs.go @@ -5,8 +5,11 @@ import ( "fmt" "os" "strconv" + "strings" "text/template" "time" + + "github.com/mitchellh/packer/common/uuid" ) // InitTime is the UTC time when this package was initialized. It is @@ -24,7 +27,11 @@ var FuncGens = map[string]FuncGenerator{ "isotime": funcGenIsotime, "pwd": funcGenPwd, "timestamp": funcGenTimestamp, + "uuid": funcGenUuid, "user": funcGenUser, + + "upper": funcGenPrimitive(strings.ToUpper), + "lower": funcGenPrimitive(strings.ToLower), } // FuncGenerator is a function that given a context generates a template @@ -68,6 +75,12 @@ func funcGenIsotime(ctx *Context) interface{} { } } +func funcGenPrimitive(value interface{}) FuncGenerator { + return func(ctx *Context) interface{} { + return value + } +} + func funcGenPwd(ctx *Context) interface{} { return func() (string, error) { return os.Getwd() @@ -85,3 +98,9 @@ func funcGenUser(ctx *Context) interface{} { return "" } } + +func funcGenUuid(ctx *Context) interface{} { + return func() string { + return uuid.TimeOrderedUUID() + } +} From 1e745d950885a1aafa0a41b18c578e76722fe12d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 15 May 2015 21:18:27 -0700 Subject: [PATCH 07/39] template/interpolate: user variables --- template/interpolate/funcs.go | 8 +++++-- template/interpolate/funcs_test.go | 34 ++++++++++++++++++++++++++++++ template/interpolate/i.go | 4 ++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go index 8b081966c..68592e046 100644 --- a/template/interpolate/funcs.go +++ b/template/interpolate/funcs.go @@ -94,8 +94,12 @@ func funcGenTimestamp(ctx *Context) interface{} { } func funcGenUser(ctx *Context) interface{} { - return func() string { - return "" + return func(k string) string { + if ctx == nil || ctx.UserVariables == nil { + return "" + } + + return ctx.UserVariables[k] } } diff --git a/template/interpolate/funcs_test.go b/template/interpolate/funcs_test.go index 37dee1ad6..7afa53447 100644 --- a/template/interpolate/funcs_test.go +++ b/template/interpolate/funcs_test.go @@ -142,3 +142,37 @@ func TestFuncTimestamp(t *testing.T) { } } } + +func TestFuncUser(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + { + `{{user "foo"}}`, + `foo`, + }, + + { + `{{user "what"}}`, + ``, + }, + } + + ctx := &Context{ + UserVariables: map[string]string{ + "foo": "foo", + }, + } + for _, tc := range cases { + i := &I{Value: tc.Input} + result, err := i.Render(ctx) + if err != nil { + t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err) + } + + if result != tc.Output { + t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result) + } + } +} diff --git a/template/interpolate/i.go b/template/interpolate/i.go index 68c60d95c..1033ad86a 100644 --- a/template/interpolate/i.go +++ b/template/interpolate/i.go @@ -11,6 +11,10 @@ type Context struct { // Data is the data for the template that is available Data interface{} + // UserVariables is the mapping of user variables that the + // "user" function reads from. + UserVariables map[string]string + // DisableEnv disables the env function DisableEnv bool } From 95890003b751e1a976f7cb2d87284dc3a0b18515 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 19 May 2015 15:25:56 -0600 Subject: [PATCH 08/39] template: builder parsing --- template/parse.go | 123 ++++++++++++++++++ template/parse_test.go | 55 ++++++++ template/template.go | 77 +++++++++++ template/template_test.go | 12 ++ template/test-fixtures/parse-basic.json | 3 + .../test-fixtures/parse-builder-no-type.json | 3 + .../test-fixtures/parse-builder-repeat.json | 6 + 7 files changed, 279 insertions(+) create mode 100644 template/parse.go create mode 100644 template/parse_test.go create mode 100644 template/template.go create mode 100644 template/template_test.go create mode 100644 template/test-fixtures/parse-basic.json create mode 100644 template/test-fixtures/parse-builder-no-type.json create mode 100644 template/test-fixtures/parse-builder-repeat.json diff --git a/template/parse.go b/template/parse.go new file mode 100644 index 000000000..3cb9e288e --- /dev/null +++ b/template/parse.go @@ -0,0 +1,123 @@ +package template + +import ( + "encoding/json" + "fmt" + "io" + "sort" + + "github.com/hashicorp/go-multierror" + "github.com/mitchellh/mapstructure" +) + +// rawTemplate is the direct JSON document format of the template file. +// This is what is decoded directly from the file, and then it is turned +// into a Template object thereafter. +type rawTemplate struct { + MinVersion string `mapstructure:"min_packer_version"` + Description string + + Builders []map[string]interface{} + Push map[string]interface{} + PostProcesors []interface{} `mapstructure:"post-processors"` + Provisioners []map[string]interface{} + Variables map[string]interface{} +} + +// Template returns the actual Template object built from this raw +// structure. +func (r *rawTemplate) Template() (*Template, error) { + var result Template + var errs error + + // Let's start by gathering all the builders + result.Builders = make(map[string]*Builder) + for i, rawB := range r.Builders { + var b Builder + if err := mapstructure.WeakDecode(rawB, &b); err != nil { + errs = multierror.Append(errs, fmt.Errorf( + "builder %d: %s", i+1, err)) + continue + } + + // Set the raw configuration and delete any special keys + b.Config = rawB + delete(b.Config, "name") + delete(b.Config, "type") + if len(b.Config) == 0 { + b.Config = nil + } + + // If there is no type set, it is an error + if b.Type == "" { + errs = multierror.Append(errs, fmt.Errorf( + "builder %d: missing 'type'", i+1)) + continue + } + + // The name defaults to the type if it isn't set + if b.Name == "" { + b.Name = b.Type + } + + // If this builder already exists, it is an error + if _, ok := result.Builders[b.Name]; ok { + errs = multierror.Append(errs, fmt.Errorf( + "builder %d: builder with name '%s' already exists", + i+1, b.Name)) + continue + } + + // Append the builders + result.Builders[b.Name] = &b + } + + // If we have errors, return those with a nil result + if errs != nil { + return nil, errs + } + + return &result, nil +} + +// Parse takes the given io.Reader and parses a Template object out of it. +func Parse(r io.Reader) (*Template, error) { + // First, decode the object into an interface{}. We do this instead of + // the rawTemplate directly because we'd rather use mapstructure to + // decode since it has richer errors. + var raw interface{} + if err := json.NewDecoder(r).Decode(&raw); err != nil { + return nil, err + } + + // Create our decoder + var md mapstructure.Metadata + var rawTpl rawTemplate + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Metadata: &md, + Result: &rawTpl, + }) + if err != nil { + return nil, err + } + + // Do the actual decode into our structure + if err := decoder.Decode(raw); err != nil { + return nil, err + } + + // Build an error if there are unused root level keys + if len(md.Unused) > 0 { + sort.Strings(md.Unused) + for _, unused := range md.Unused { + err = multierror.Append(err, fmt.Errorf( + "Unknown root level key in template: '%s'", unused)) + } + + // Return early for these errors + return nil, err + } + + // Return the template parsed from the raw structure + return rawTpl.Template() +} diff --git a/template/parse_test.go b/template/parse_test.go new file mode 100644 index 000000000..b7789298c --- /dev/null +++ b/template/parse_test.go @@ -0,0 +1,55 @@ +package template + +import ( + "os" + "reflect" + "testing" +) + +func TestParse(t *testing.T) { + cases := []struct { + File string + Result *Template + Err bool + }{ + { + "parse-basic.json", + &Template{ + Builders: map[string]*Builder{ + "something": &Builder{ + Name: "something", + Type: "something", + }, + }, + }, + false, + }, + { + "parse-builder-no-type.json", + nil, + true, + }, + { + "parse-builder-repeat.json", + nil, + true, + }, + } + + for _, tc := range cases { + f, err := os.Open(fixtureDir(tc.File)) + if err != nil { + t.Fatalf("err: %s", err) + } + + tpl, err := Parse(f) + f.Close() + if (err != nil) != tc.Err { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(tpl, tc.Result) { + t.Fatalf("bad: %#v", tpl) + } + } +} diff --git a/template/template.go b/template/template.go new file mode 100644 index 000000000..477a6d824 --- /dev/null +++ b/template/template.go @@ -0,0 +1,77 @@ +package template + +import ( + "fmt" + "time" +) + +// Template represents the parsed template that is used to configure +// Packer builds. +type Template struct { + Description string + MinVersion string + + Variables map[string]*Variable + Builders map[string]*Builder + Provisioners []*Provisioner + PostProcessors [][]*PostProcessor + Push *Push +} + +// Builder represents a builder configured in the template +type Builder struct { + Name string + Type string + Config map[string]interface{} +} + +// PostProcessor represents a post-processor within the template. +type PostProcessor struct { + OnlyExcept + + Type string + KeepInputArtifact bool + Config map[string]interface{} +} + +// Provisioner represents a provisioner within the template. +type Provisioner struct { + OnlyExcept + + Type string + Config map[string]interface{} + Override map[string]interface{} + PauseBefore time.Duration +} + +// Push represents the configuration for pushing the template to Atlas. +type Push struct { + Name string + Address string + BaseDir string `mapstructure:"base_dir"` + Include []string + Exclude []string + Token string + VCS bool +} + +// Variable represents a variable within the template +type Variable struct { + Default string + Required bool +} + +// OnlyExcept is a struct that is meant to be embedded that contains the +// logic required for "only" and "except" meta-parameters. +type OnlyExcept struct { + Only []string + Except []string +} + +//------------------------------------------------------------------- +// GoStringer +//------------------------------------------------------------------- + +func (b *Builder) GoString() string { + return fmt.Sprintf("*%#v", *b) +} diff --git a/template/template_test.go b/template/template_test.go new file mode 100644 index 000000000..2847bf9a2 --- /dev/null +++ b/template/template_test.go @@ -0,0 +1,12 @@ +package template + +import ( + "path/filepath" +) + +const FixturesDir = "./test-fixtures" + +// fixtureDir returns the path to a test fixtures directory +func fixtureDir(n string) string { + return filepath.Join(FixturesDir, n) +} diff --git a/template/test-fixtures/parse-basic.json b/template/test-fixtures/parse-basic.json new file mode 100644 index 000000000..43b7a7898 --- /dev/null +++ b/template/test-fixtures/parse-basic.json @@ -0,0 +1,3 @@ +{ + "builders": [{"type": "something"}] +} diff --git a/template/test-fixtures/parse-builder-no-type.json b/template/test-fixtures/parse-builder-no-type.json new file mode 100644 index 000000000..1729d0827 --- /dev/null +++ b/template/test-fixtures/parse-builder-no-type.json @@ -0,0 +1,3 @@ +{ + "builders": [{"foo": "something"}] +} diff --git a/template/test-fixtures/parse-builder-repeat.json b/template/test-fixtures/parse-builder-repeat.json new file mode 100644 index 000000000..258b75883 --- /dev/null +++ b/template/test-fixtures/parse-builder-repeat.json @@ -0,0 +1,6 @@ +{ + "builders": [ + {"type": "something"}, + {"type": "something"} + ] +} From 4583ed610809d7ea0f1373b93af3ed9dc1b676bc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 21 May 2015 13:34:44 -0600 Subject: [PATCH 09/39] template: parse provisioners --- template/parse.go | 54 +++++++++++- template/parse_test.go | 85 ++++++++++++++++++- template/template.go | 8 +- .../parse-provisioner-basic.json | 5 ++ .../parse-provisioner-except.json | 8 ++ .../parse-provisioner-no-type.json | 5 ++ .../test-fixtures/parse-provisioner-only.json | 8 ++ .../parse-provisioner-override.json | 10 +++ .../parse-provisioner-pause-before.json | 8 ++ 9 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 template/test-fixtures/parse-provisioner-basic.json create mode 100644 template/test-fixtures/parse-provisioner-except.json create mode 100644 template/test-fixtures/parse-provisioner-no-type.json create mode 100644 template/test-fixtures/parse-provisioner-only.json create mode 100644 template/test-fixtures/parse-provisioner-override.json create mode 100644 template/test-fixtures/parse-provisioner-pause-before.json diff --git a/template/parse.go b/template/parse.go index 3cb9e288e..a96117111 100644 --- a/template/parse.go +++ b/template/parse.go @@ -31,7 +31,9 @@ func (r *rawTemplate) Template() (*Template, error) { var errs error // Let's start by gathering all the builders - result.Builders = make(map[string]*Builder) + if len(r.Builders) > 0 { + result.Builders = make(map[string]*Builder, len(r.Builders)) + } for i, rawB := range r.Builders { var b Builder if err := mapstructure.WeakDecode(rawB, &b); err != nil { @@ -72,6 +74,39 @@ func (r *rawTemplate) Template() (*Template, error) { result.Builders[b.Name] = &b } + // Gather all the provisioners + if len(r.Provisioners) > 0 { + result.Provisioners = make([]*Provisioner, 0, len(r.Provisioners)) + } + for i, v := range r.Provisioners { + var p Provisioner + if err := r.decoder(&p, nil).Decode(v); err != nil { + errs = multierror.Append(errs, fmt.Errorf( + "provisioner %d: %s", i+1, err)) + continue + } + + // Type is required before any richer validation + if p.Type == "" { + errs = multierror.Append(errs, fmt.Errorf( + "provisioner %d: missing 'type'", i+1)) + continue + } + + // Copy the configuration + delete(v, "except") + delete(v, "only") + delete(v, "override") + delete(v, "pause_before") + delete(v, "type") + if len(v) > 0 { + p.Config = v + } + + // TODO: stuff + result.Provisioners = append(result.Provisioners, &p) + } + // If we have errors, return those with a nil result if errs != nil { return nil, errs @@ -80,6 +115,23 @@ func (r *rawTemplate) Template() (*Template, error) { return &result, nil } +func (r *rawTemplate) decoder( + result interface{}, + md *mapstructure.Metadata) *mapstructure.Decoder { + d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: mapstructure.StringToTimeDurationHookFunc(), + Metadata: md, + Result: result, + }) + if err != nil { + // This really shouldn't happen since we have firm control over + // all the arguments and they're all unit tested. So we use a + // panic here to note this would definitely be a bug. + panic(err) + } + return d +} + // Parse takes the given io.Reader and parses a Template object out of it. func Parse(r io.Reader) (*Template, error) { // First, decode the object into an interface{}. We do this instead of diff --git a/template/parse_test.go b/template/parse_test.go index b7789298c..2c5b8f735 100644 --- a/template/parse_test.go +++ b/template/parse_test.go @@ -4,6 +4,7 @@ import ( "os" "reflect" "testing" + "time" ) func TestParse(t *testing.T) { @@ -12,6 +13,9 @@ func TestParse(t *testing.T) { Result *Template Err bool }{ + /* + * Builders + */ { "parse-basic.json", &Template{ @@ -34,6 +38,85 @@ func TestParse(t *testing.T) { nil, true, }, + + /* + * Provisioners + */ + { + "parse-provisioner-basic.json", + &Template{ + Provisioners: []*Provisioner{ + &Provisioner{ + Type: "something", + }, + }, + }, + false, + }, + + { + "parse-provisioner-pause-before.json", + &Template{ + Provisioners: []*Provisioner{ + &Provisioner{ + Type: "something", + PauseBefore: 1 * time.Second, + }, + }, + }, + false, + }, + + { + "parse-provisioner-only.json", + &Template{ + Provisioners: []*Provisioner{ + &Provisioner{ + Type: "something", + OnlyExcept: OnlyExcept{ + Only: []string{"foo"}, + }, + }, + }, + }, + false, + }, + + { + "parse-provisioner-except.json", + &Template{ + Provisioners: []*Provisioner{ + &Provisioner{ + Type: "something", + OnlyExcept: OnlyExcept{ + Except: []string{"foo"}, + }, + }, + }, + }, + false, + }, + + { + "parse-provisioner-override.json", + &Template{ + Provisioners: []*Provisioner{ + &Provisioner{ + Type: "something", + Override: map[string]interface{}{ + "foo": map[string]interface{}{}, + }, + }, + }, + }, + false, + }, + + { + "parse-provisioner-no-type.json", + nil, + true, + }, } for _, tc := range cases { @@ -49,7 +132,7 @@ func TestParse(t *testing.T) { } if !reflect.DeepEqual(tpl, tc.Result) { - t.Fatalf("bad: %#v", tpl) + t.Fatalf("bad: %s\n\n%#v\n\n%#v", tc.File, tpl, tc.Result) } } } diff --git a/template/template.go b/template/template.go index 477a6d824..daee508fc 100644 --- a/template/template.go +++ b/template/template.go @@ -36,12 +36,12 @@ type PostProcessor struct { // Provisioner represents a provisioner within the template. type Provisioner struct { - OnlyExcept + OnlyExcept `mapstructure:",squash"` Type string Config map[string]interface{} Override map[string]interface{} - PauseBefore time.Duration + PauseBefore time.Duration `mapstructure:"pause_before"` } // Push represents the configuration for pushing the template to Atlas. @@ -75,3 +75,7 @@ type OnlyExcept struct { func (b *Builder) GoString() string { return fmt.Sprintf("*%#v", *b) } + +func (p *Provisioner) GoString() string { + return fmt.Sprintf("*%#v", *p) +} diff --git a/template/test-fixtures/parse-provisioner-basic.json b/template/test-fixtures/parse-provisioner-basic.json new file mode 100644 index 000000000..bf0d8d910 --- /dev/null +++ b/template/test-fixtures/parse-provisioner-basic.json @@ -0,0 +1,5 @@ +{ + "provisioners": [ + {"type": "something"} + ] +} diff --git a/template/test-fixtures/parse-provisioner-except.json b/template/test-fixtures/parse-provisioner-except.json new file mode 100644 index 000000000..8c7f0c8f5 --- /dev/null +++ b/template/test-fixtures/parse-provisioner-except.json @@ -0,0 +1,8 @@ +{ + "provisioners": [ + { + "type": "something", + "except": ["foo"] + } + ] +} diff --git a/template/test-fixtures/parse-provisioner-no-type.json b/template/test-fixtures/parse-provisioner-no-type.json new file mode 100644 index 000000000..40bc214d2 --- /dev/null +++ b/template/test-fixtures/parse-provisioner-no-type.json @@ -0,0 +1,5 @@ +{ + "provisioners": [ + {"foo": "something"} + ] +} diff --git a/template/test-fixtures/parse-provisioner-only.json b/template/test-fixtures/parse-provisioner-only.json new file mode 100644 index 000000000..3bbb534b2 --- /dev/null +++ b/template/test-fixtures/parse-provisioner-only.json @@ -0,0 +1,8 @@ +{ + "provisioners": [ + { + "type": "something", + "only": ["foo"] + } + ] +} diff --git a/template/test-fixtures/parse-provisioner-override.json b/template/test-fixtures/parse-provisioner-override.json new file mode 100644 index 000000000..5b55099ba --- /dev/null +++ b/template/test-fixtures/parse-provisioner-override.json @@ -0,0 +1,10 @@ +{ + "provisioners": [ + { + "type": "something", + "override": { + "foo": {} + } + } + ] +} diff --git a/template/test-fixtures/parse-provisioner-pause-before.json b/template/test-fixtures/parse-provisioner-pause-before.json new file mode 100644 index 000000000..70640847b --- /dev/null +++ b/template/test-fixtures/parse-provisioner-pause-before.json @@ -0,0 +1,8 @@ +{ + "provisioners": [ + { + "type": "something", + "pause_before": "1s" + } + ] +} From fbda5b119a68d091431ed06098b1fc83caaebd16 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 21 May 2015 13:40:33 -0600 Subject: [PATCH 10/39] template: variable parsing --- template/parse.go | 20 ++++++++++++++++ template/parse_test.go | 24 +++++++++++++++++++ .../test-fixtures/parse-variable-default.json | 5 ++++ .../parse-variable-required.json | 5 ++++ 4 files changed, 54 insertions(+) create mode 100644 template/test-fixtures/parse-variable-default.json create mode 100644 template/test-fixtures/parse-variable-required.json diff --git a/template/parse.go b/template/parse.go index a96117111..47d97effa 100644 --- a/template/parse.go +++ b/template/parse.go @@ -30,6 +30,26 @@ func (r *rawTemplate) Template() (*Template, error) { var result Template var errs error + // Gather the variables + if len(r.Variables) > 0 { + result.Variables = make(map[string]*Variable, len(r.Variables)) + } + for k, rawV := range r.Variables { + var v Variable + + // Variable is required if the value is exactly nil + v.Required = rawV == nil + + // Weak decode the default if we have one + if err := r.decoder(&v.Default, nil).Decode(rawV); err != nil { + errs = multierror.Append(errs, fmt.Errorf( + "variable %s: %s", k, err)) + continue + } + + result.Variables[k] = &v + } + // Let's start by gathering all the builders if len(r.Builders) > 0 { result.Builders = make(map[string]*Builder, len(r.Builders)) diff --git a/template/parse_test.go b/template/parse_test.go index 2c5b8f735..fcf7fcf29 100644 --- a/template/parse_test.go +++ b/template/parse_test.go @@ -117,6 +117,30 @@ func TestParse(t *testing.T) { nil, true, }, + + { + "parse-variable-default.json", + &Template{ + Variables: map[string]*Variable{ + "foo": &Variable{ + Default: "foo", + }, + }, + }, + false, + }, + + { + "parse-variable-required.json", + &Template{ + Variables: map[string]*Variable{ + "foo": &Variable{ + Required: true, + }, + }, + }, + false, + }, } for _, tc := range cases { diff --git a/template/test-fixtures/parse-variable-default.json b/template/test-fixtures/parse-variable-default.json new file mode 100644 index 000000000..05192b64d --- /dev/null +++ b/template/test-fixtures/parse-variable-default.json @@ -0,0 +1,5 @@ +{ + "variables": { + "foo": "foo" + } +} diff --git a/template/test-fixtures/parse-variable-required.json b/template/test-fixtures/parse-variable-required.json new file mode 100644 index 000000000..ca6458aaa --- /dev/null +++ b/template/test-fixtures/parse-variable-required.json @@ -0,0 +1,5 @@ +{ + "variables": { + "foo": null + } +} From 839784b044cf683e6d4dfb12b36e770c06d61d71 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 21 May 2015 14:32:22 -0600 Subject: [PATCH 11/39] template: parse post-processors --- template/parse.go | 92 ++++++++++++++++- template/parse_test.go | 102 +++++++++++++++++++ template/template.go | 12 ++- template/test-fixtures/parse-pp-basic.json | 6 ++ template/test-fixtures/parse-pp-keep.json | 6 ++ template/test-fixtures/parse-pp-map.json | 5 + template/test-fixtures/parse-pp-multi.json | 5 + template/test-fixtures/parse-pp-no-type.json | 5 + template/test-fixtures/parse-pp-slice.json | 5 + template/test-fixtures/parse-pp-string.json | 3 + 10 files changed, 234 insertions(+), 7 deletions(-) create mode 100644 template/test-fixtures/parse-pp-basic.json create mode 100644 template/test-fixtures/parse-pp-keep.json create mode 100644 template/test-fixtures/parse-pp-map.json create mode 100644 template/test-fixtures/parse-pp-multi.json create mode 100644 template/test-fixtures/parse-pp-no-type.json create mode 100644 template/test-fixtures/parse-pp-slice.json create mode 100644 template/test-fixtures/parse-pp-string.json diff --git a/template/parse.go b/template/parse.go index 47d97effa..f9dc7cba3 100644 --- a/template/parse.go +++ b/template/parse.go @@ -17,11 +17,11 @@ type rawTemplate struct { MinVersion string `mapstructure:"min_packer_version"` Description string - Builders []map[string]interface{} - Push map[string]interface{} - PostProcesors []interface{} `mapstructure:"post-processors"` - Provisioners []map[string]interface{} - Variables map[string]interface{} + Builders []map[string]interface{} + Push map[string]interface{} + PostProcessors []interface{} `mapstructure:"post-processors"` + Provisioners []map[string]interface{} + Variables map[string]interface{} } // Template returns the actual Template object built from this raw @@ -94,6 +94,49 @@ func (r *rawTemplate) Template() (*Template, error) { result.Builders[b.Name] = &b } + // Gather all the post-processors + if len(r.PostProcessors) > 0 { + result.PostProcessors = make([][]*PostProcessor, 0, len(r.PostProcessors)) + } + for i, v := range r.PostProcessors { + // Parse the configurations. We need to do this because post-processors + // can take three different formats. + configs, err := r.parsePostProcessor(i, v) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + + // Parse the PostProcessors out of the configs + pps := make([]*PostProcessor, 0, len(configs)) + for j, c := range configs { + var pp PostProcessor + if err := r.decoder(&pp, nil).Decode(c); err != nil { + errs = multierror.Append(errs, fmt.Errorf( + "post-processor %d.%d: %s", i+1, j+1, err)) + continue + } + + // Type is required + if pp.Type == "" { + errs = multierror.Append(errs, fmt.Errorf( + "post-processor %d.%d: type is required", i+1, j+1)) + continue + } + + // Set the configuration + delete(c, "keep_input_artifact") + delete(c, "type") + if len(c) > 0 { + pp.Config = c + } + + pps = append(pps, &pp) + } + + result.PostProcessors = append(result.PostProcessors, pps) + } + // Gather all the provisioners if len(r.Provisioners) > 0 { result.Provisioners = make([]*Provisioner, 0, len(r.Provisioners)) @@ -152,6 +195,45 @@ func (r *rawTemplate) decoder( return d } +func (r *rawTemplate) parsePostProcessor( + i int, raw interface{}) ([]map[string]interface{}, error) { + switch v := raw.(type) { + case string: + return []map[string]interface{}{ + {"type": v}, + }, nil + case map[string]interface{}: + return []map[string]interface{}{v}, nil + case []interface{}: + var err error + result := make([]map[string]interface{}, len(v)) + for j, innerRaw := range v { + switch innerV := innerRaw.(type) { + case string: + result[j] = map[string]interface{}{"type": innerV} + case map[string]interface{}: + result[j] = innerV + case []interface{}: + err = multierror.Append(err, fmt.Errorf( + "post-processor %d.%d: sequence not allowed to be nested in a sequence", + i+1, j+1)) + default: + err = multierror.Append(err, fmt.Errorf( + "post-processor %d.%d: unknown format", + i+1, j+1)) + } + } + + if err != nil { + return nil, err + } + + return result, nil + default: + return nil, fmt.Errorf("post-processor %d: bad format", i+1) + } +} + // Parse takes the given io.Reader and parses a Template object out of it. func Parse(r io.Reader) (*Template, error) { // First, decode the object into an interface{}. We do this instead of diff --git a/template/parse_test.go b/template/parse_test.go index fcf7fcf29..b28670e40 100644 --- a/template/parse_test.go +++ b/template/parse_test.go @@ -141,6 +141,108 @@ func TestParse(t *testing.T) { }, false, }, + + { + "parse-pp-basic.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + Config: map[string]interface{}{ + "foo": "bar", + }, + }, + }, + }, + }, + false, + }, + + { + "parse-pp-keep.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + KeepInputArtifact: true, + }, + }, + }, + }, + false, + }, + + { + "parse-pp-string.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + }, + }, + }, + }, + false, + }, + + { + "parse-pp-map.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + }, + }, + }, + }, + false, + }, + + { + "parse-pp-slice.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + }, + }, + []*PostProcessor{ + &PostProcessor{ + Type: "bar", + }, + }, + }, + }, + false, + }, + + { + "parse-pp-multi.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + }, + &PostProcessor{ + Type: "bar", + }, + }, + }, + }, + false, + }, + + { + "parse-pp-no-type.json", + nil, + true, + }, } for _, tc := range cases { diff --git a/template/template.go b/template/template.go index daee508fc..8de30b1a9 100644 --- a/template/template.go +++ b/template/template.go @@ -27,10 +27,10 @@ type Builder struct { // PostProcessor represents a post-processor within the template. type PostProcessor struct { - OnlyExcept + OnlyExcept `mapstructure:",squash"` Type string - KeepInputArtifact bool + KeepInputArtifact bool `mapstructure:"keep_input_artifact"` Config map[string]interface{} } @@ -79,3 +79,11 @@ func (b *Builder) GoString() string { func (p *Provisioner) GoString() string { return fmt.Sprintf("*%#v", *p) } + +func (p *PostProcessor) GoString() string { + return fmt.Sprintf("*%#v", *p) +} + +func (v *Variable) GoString() string { + return fmt.Sprintf("*%#v", *v) +} diff --git a/template/test-fixtures/parse-pp-basic.json b/template/test-fixtures/parse-pp-basic.json new file mode 100644 index 000000000..56a1145e6 --- /dev/null +++ b/template/test-fixtures/parse-pp-basic.json @@ -0,0 +1,6 @@ +{ + "post-processors": [{ + "type": "foo", + "foo": "bar" + }] +} diff --git a/template/test-fixtures/parse-pp-keep.json b/template/test-fixtures/parse-pp-keep.json new file mode 100644 index 000000000..d0bd513e5 --- /dev/null +++ b/template/test-fixtures/parse-pp-keep.json @@ -0,0 +1,6 @@ +{ + "post-processors": [{ + "type": "foo", + "keep_input_artifact": true + }] +} diff --git a/template/test-fixtures/parse-pp-map.json b/template/test-fixtures/parse-pp-map.json new file mode 100644 index 000000000..43cc6abb8 --- /dev/null +++ b/template/test-fixtures/parse-pp-map.json @@ -0,0 +1,5 @@ +{ + "post-processors": [{ + "type": "foo" + }] +} diff --git a/template/test-fixtures/parse-pp-multi.json b/template/test-fixtures/parse-pp-multi.json new file mode 100644 index 000000000..32a60fa34 --- /dev/null +++ b/template/test-fixtures/parse-pp-multi.json @@ -0,0 +1,5 @@ +{ + "post-processors": [[{ + "type": "foo" + }, "bar"]] +} diff --git a/template/test-fixtures/parse-pp-no-type.json b/template/test-fixtures/parse-pp-no-type.json new file mode 100644 index 000000000..f4dda63e8 --- /dev/null +++ b/template/test-fixtures/parse-pp-no-type.json @@ -0,0 +1,5 @@ +{ + "post-processors": [{ + "keep_input_artifact": true + }] +} diff --git a/template/test-fixtures/parse-pp-slice.json b/template/test-fixtures/parse-pp-slice.json new file mode 100644 index 000000000..94c3a5247 --- /dev/null +++ b/template/test-fixtures/parse-pp-slice.json @@ -0,0 +1,5 @@ +{ + "post-processors": [{ + "type": "foo" + }, "bar"] +} diff --git a/template/test-fixtures/parse-pp-string.json b/template/test-fixtures/parse-pp-string.json new file mode 100644 index 000000000..8e77358ea --- /dev/null +++ b/template/test-fixtures/parse-pp-string.json @@ -0,0 +1,3 @@ +{ + "post-processors": ["foo"] +} From 43fbd26dc91a4d78897e3b8845b5b2e00937b47e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 21 May 2015 14:41:33 -0600 Subject: [PATCH 12/39] template: copy some description and min vesrion --- template/parse.go | 4 ++++ template/parse_test.go | 16 ++++++++++++++++ template/test-fixtures/parse-description.json | 3 +++ template/test-fixtures/parse-min-version.json | 3 +++ 4 files changed, 26 insertions(+) create mode 100644 template/test-fixtures/parse-description.json create mode 100644 template/test-fixtures/parse-min-version.json diff --git a/template/parse.go b/template/parse.go index f9dc7cba3..df29e8af0 100644 --- a/template/parse.go +++ b/template/parse.go @@ -30,6 +30,10 @@ func (r *rawTemplate) Template() (*Template, error) { var result Template var errs error + // Copy some literals + result.Description = r.Description + result.MinVersion = r.MinVersion + // Gather the variables if len(r.Variables) > 0 { result.Variables = make(map[string]*Variable, len(r.Variables)) diff --git a/template/parse_test.go b/template/parse_test.go index b28670e40..3e6847604 100644 --- a/template/parse_test.go +++ b/template/parse_test.go @@ -243,6 +243,22 @@ func TestParse(t *testing.T) { nil, true, }, + + { + "parse-description.json", + &Template{ + Description: "foo", + }, + false, + }, + + { + "parse-min-version.json", + &Template{ + MinVersion: "1.2", + }, + false, + }, } for _, tc := range cases { diff --git a/template/test-fixtures/parse-description.json b/template/test-fixtures/parse-description.json new file mode 100644 index 000000000..c72a24eb8 --- /dev/null +++ b/template/test-fixtures/parse-description.json @@ -0,0 +1,3 @@ +{ + "description": "foo" +} diff --git a/template/test-fixtures/parse-min-version.json b/template/test-fixtures/parse-min-version.json new file mode 100644 index 000000000..f98101efb --- /dev/null +++ b/template/test-fixtures/parse-min-version.json @@ -0,0 +1,3 @@ +{ + "min_packer_version": "1.2" +} From 2e4dd639124ce787a59f4e7dd915f09810cc85a4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 21 May 2015 14:44:29 -0600 Subject: [PATCH 13/39] template: parse push --- template/parse.go | 11 +++++++++++ template/parse_test.go | 10 ++++++++++ template/test-fixtures/parse-push.json | 5 +++++ 3 files changed, 26 insertions(+) create mode 100644 template/test-fixtures/parse-push.json diff --git a/template/parse.go b/template/parse.go index df29e8af0..c0e21b1c4 100644 --- a/template/parse.go +++ b/template/parse.go @@ -174,6 +174,17 @@ func (r *rawTemplate) Template() (*Template, error) { result.Provisioners = append(result.Provisioners, &p) } + // Push + if len(r.Push) > 0 { + var p Push + if err := r.decoder(&p, nil).Decode(r.Push); err != nil { + errs = multierror.Append(errs, fmt.Errorf( + "push: %s", err)) + } + + result.Push = &p + } + // If we have errors, return those with a nil result if errs != nil { return nil, errs diff --git a/template/parse_test.go b/template/parse_test.go index 3e6847604..023c3d537 100644 --- a/template/parse_test.go +++ b/template/parse_test.go @@ -259,6 +259,16 @@ func TestParse(t *testing.T) { }, false, }, + + { + "parse-push.json", + &Template{ + Push: &Push{ + Name: "foo", + }, + }, + false, + }, } for _, tc := range cases { diff --git a/template/test-fixtures/parse-push.json b/template/test-fixtures/parse-push.json new file mode 100644 index 000000000..2529eedc4 --- /dev/null +++ b/template/test-fixtures/parse-push.json @@ -0,0 +1,5 @@ +{ + "push": { + "name": "foo" + } +} From 2f7e95cc462f6b6bfc74651e80b967cedd6a184a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 21 May 2015 15:29:45 -0600 Subject: [PATCH 14/39] template: Validate --- template/template.go | 35 ++++++++++++++++ template/template_test.go | 42 +++++++++++++++++++ .../test-fixtures/validate-bad-override.json | 12 ++++++ .../test-fixtures/validate-good-override.json | 12 ++++++ .../test-fixtures/validate-no-builders.json | 1 + 5 files changed, 102 insertions(+) create mode 100644 template/test-fixtures/validate-bad-override.json create mode 100644 template/test-fixtures/validate-good-override.json create mode 100644 template/test-fixtures/validate-no-builders.json diff --git a/template/template.go b/template/template.go index 8de30b1a9..0ea2a4eb7 100644 --- a/template/template.go +++ b/template/template.go @@ -1,8 +1,11 @@ package template import ( + "errors" "fmt" "time" + + "github.com/hashicorp/go-multierror" ) // Template represents the parsed template that is used to configure @@ -68,6 +71,38 @@ type OnlyExcept struct { Except []string } +//------------------------------------------------------------------- +// Functions +//------------------------------------------------------------------- + +// Validate does some basic validation of the template on top of the +// validation that occurs while parsing. If possible, we try to defer +// validation to here. The validation errors that occur during parsing +// are the minimal necessary to make sure parsing builds a reasonable +// Template structure. +func (t *Template) Validate() error { + var err error + + // At least one builder must be defined + if len(t.Builders) == 0 { + err = multierror.Append(err, errors.New( + "at least one builder must be defined")) + } + + // Verify that the provisioner overrides target builders that exist + for i, p := range t.Provisioners { + for name, _ := range p.Override { + if _, ok := t.Builders[name]; !ok { + err = multierror.Append(err, fmt.Errorf( + "provisioner %d: override '%s' doesn't exist", + i+1, name)) + } + } + } + + return err +} + //------------------------------------------------------------------- // GoStringer //------------------------------------------------------------------- diff --git a/template/template_test.go b/template/template_test.go index 2847bf9a2..e8a0ff2fe 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -1,7 +1,9 @@ package template import ( + "os" "path/filepath" + "testing" ) const FixturesDir = "./test-fixtures" @@ -10,3 +12,43 @@ const FixturesDir = "./test-fixtures" func fixtureDir(n string) string { return filepath.Join(FixturesDir, n) } + +func TestTemplateValidate(t *testing.T) { + cases := []struct { + File string + Err bool + }{ + { + "validate-no-builders.json", + true, + }, + + { + "validate-bad-override.json", + true, + }, + + { + "validate-good-override.json", + false, + }, + } + + for _, tc := range cases { + f, err := os.Open(fixtureDir(tc.File)) + if err != nil { + t.Fatalf("err: %s", err) + } + + tpl, err := Parse(f) + f.Close() + if err != nil { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + + err = tpl.Validate() + if (err != nil) != tc.Err { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + } +} diff --git a/template/test-fixtures/validate-bad-override.json b/template/test-fixtures/validate-bad-override.json new file mode 100644 index 000000000..7f6c64588 --- /dev/null +++ b/template/test-fixtures/validate-bad-override.json @@ -0,0 +1,12 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "override": { + "bar": {} + } + }] +} diff --git a/template/test-fixtures/validate-good-override.json b/template/test-fixtures/validate-good-override.json new file mode 100644 index 000000000..4d7e0f757 --- /dev/null +++ b/template/test-fixtures/validate-good-override.json @@ -0,0 +1,12 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "override": { + "foo": {} + } + }] +} diff --git a/template/test-fixtures/validate-no-builders.json b/template/test-fixtures/validate-no-builders.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/template/test-fixtures/validate-no-builders.json @@ -0,0 +1 @@ +{} From 637fabc1c7684b1d0ad37a9cc95636bff7f51398 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 21 May 2015 15:39:32 -0600 Subject: [PATCH 15/39] template: validate only/except --- template/template.go | 32 +++++++++++++++++++ template/template_test.go | 20 ++++++++++++ .../validate-bad-prov-except.json | 10 ++++++ .../test-fixtures/validate-bad-prov-only.json | 10 ++++++ .../validate-good-prov-except.json | 10 ++++++ .../validate-good-prov-only.json | 10 ++++++ 6 files changed, 92 insertions(+) create mode 100644 template/test-fixtures/validate-bad-prov-except.json create mode 100644 template/test-fixtures/validate-bad-prov-only.json create mode 100644 template/test-fixtures/validate-good-prov-except.json create mode 100644 template/test-fixtures/validate-good-prov-only.json diff --git a/template/template.go b/template/template.go index 0ea2a4eb7..0d2671ca0 100644 --- a/template/template.go +++ b/template/template.go @@ -91,6 +91,15 @@ func (t *Template) Validate() error { // Verify that the provisioner overrides target builders that exist for i, p := range t.Provisioners { + // Validate only/except + if verr := p.OnlyExcept.Validate(t); verr != nil { + for _, e := range multierror.Append(verr).Errors { + err = multierror.Append(err, fmt.Errorf( + "provisioner %d: %s", i+1, e)) + } + } + + // Validate overrides for name, _ := range p.Override { if _, ok := t.Builders[name]; !ok { err = multierror.Append(err, fmt.Errorf( @@ -103,6 +112,29 @@ func (t *Template) Validate() error { return err } +// Validate validates that the OnlyExcept settings are correct for a thing. +func (o *OnlyExcept) Validate(t *Template) error { + if len(o.Only) > 0 && len(o.Except) > 0 { + return errors.New("only one of 'only' or 'except' may be specified") + } + + var err error + for _, n := range o.Only { + if _, ok := t.Builders[n]; !ok { + err = multierror.Append(err, fmt.Errorf( + "'only' specified builder '%s' not found", n)) + } + } + for _, n := range o.Except { + if _, ok := t.Builders[n]; !ok { + err = multierror.Append(err, fmt.Errorf( + "'except' specified builder '%s' not found", n)) + } + } + + return err +} + //------------------------------------------------------------------- // GoStringer //------------------------------------------------------------------- diff --git a/template/template_test.go b/template/template_test.go index e8a0ff2fe..dbb2cf33d 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -32,6 +32,26 @@ func TestTemplateValidate(t *testing.T) { "validate-good-override.json", false, }, + + { + "validate-bad-prov-only.json", + true, + }, + + { + "validate-good-prov-only.json", + false, + }, + + { + "validate-bad-prov-except.json", + true, + }, + + { + "validate-good-prov-except.json", + false, + }, } for _, tc := range cases { diff --git a/template/test-fixtures/validate-bad-prov-except.json b/template/test-fixtures/validate-bad-prov-except.json new file mode 100644 index 000000000..0a24bf58e --- /dev/null +++ b/template/test-fixtures/validate-bad-prov-except.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "except": ["bar"] + }] +} diff --git a/template/test-fixtures/validate-bad-prov-only.json b/template/test-fixtures/validate-bad-prov-only.json new file mode 100644 index 000000000..fa8c9ccab --- /dev/null +++ b/template/test-fixtures/validate-bad-prov-only.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "only": ["bar"] + }] +} diff --git a/template/test-fixtures/validate-good-prov-except.json b/template/test-fixtures/validate-good-prov-except.json new file mode 100644 index 000000000..e075d09ca --- /dev/null +++ b/template/test-fixtures/validate-good-prov-except.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "except": ["foo"] + }] +} diff --git a/template/test-fixtures/validate-good-prov-only.json b/template/test-fixtures/validate-good-prov-only.json new file mode 100644 index 000000000..db162fa28 --- /dev/null +++ b/template/test-fixtures/validate-good-prov-only.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "only": ["foo"] + }] +} From 28dc1c2aedc5271175dc17b178bdd4dd6162ba9c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 21 May 2015 15:42:12 -0600 Subject: [PATCH 16/39] template: validate post-processor only/except --- template/template.go | 13 ++++++++++++ template/template_test.go | 20 +++++++++++++++++++ .../test-fixtures/validate-bad-pp-except.json | 10 ++++++++++ .../test-fixtures/validate-bad-pp-only.json | 10 ++++++++++ .../validate-good-pp-except.json | 10 ++++++++++ .../test-fixtures/validate-good-pp-only.json | 10 ++++++++++ 6 files changed, 73 insertions(+) create mode 100644 template/test-fixtures/validate-bad-pp-except.json create mode 100644 template/test-fixtures/validate-bad-pp-only.json create mode 100644 template/test-fixtures/validate-good-pp-except.json create mode 100644 template/test-fixtures/validate-good-pp-only.json diff --git a/template/template.go b/template/template.go index 0d2671ca0..17d808029 100644 --- a/template/template.go +++ b/template/template.go @@ -109,6 +109,19 @@ func (t *Template) Validate() error { } } + // Verify post-processors + for i, chain := range t.PostProcessors { + for j, p := range chain { + // Validate only/except + if verr := p.OnlyExcept.Validate(t); verr != nil { + for _, e := range multierror.Append(verr).Errors { + err = multierror.Append(err, fmt.Errorf( + "post-processor %d.%d: %s", i+1, j+1, e)) + } + } + } + } + return err } diff --git a/template/template_test.go b/template/template_test.go index dbb2cf33d..d14682728 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -52,6 +52,26 @@ func TestTemplateValidate(t *testing.T) { "validate-good-prov-except.json", false, }, + + { + "validate-bad-pp-only.json", + true, + }, + + { + "validate-good-pp-only.json", + false, + }, + + { + "validate-bad-pp-except.json", + true, + }, + + { + "validate-good-pp-except.json", + false, + }, } for _, tc := range cases { diff --git a/template/test-fixtures/validate-bad-pp-except.json b/template/test-fixtures/validate-bad-pp-except.json new file mode 100644 index 000000000..3f66cd8ca --- /dev/null +++ b/template/test-fixtures/validate-bad-pp-except.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "post-processors": [{ + "type": "bar", + "except": ["bar"] + }] +} diff --git a/template/test-fixtures/validate-bad-pp-only.json b/template/test-fixtures/validate-bad-pp-only.json new file mode 100644 index 000000000..a79edcb80 --- /dev/null +++ b/template/test-fixtures/validate-bad-pp-only.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "post-processors": [{ + "type": "bar", + "only": ["bar"] + }] +} diff --git a/template/test-fixtures/validate-good-pp-except.json b/template/test-fixtures/validate-good-pp-except.json new file mode 100644 index 000000000..79a1b2a24 --- /dev/null +++ b/template/test-fixtures/validate-good-pp-except.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "post-processors": [{ + "type": "bar", + "except": ["foo"] + }] +} diff --git a/template/test-fixtures/validate-good-pp-only.json b/template/test-fixtures/validate-good-pp-only.json new file mode 100644 index 000000000..24ef7c95d --- /dev/null +++ b/template/test-fixtures/validate-good-pp-only.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "post-processors": [{ + "type": "bar", + "only": ["foo"] + }] +} From ded13a8b10b4cbe53a75c9eb968e099fc79a524b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 23 May 2015 14:48:07 -0700 Subject: [PATCH 17/39] packer: Core, and template validate --- packer/core.go | 77 +++++++++++++++++++ packer/core_test.go | 60 +++++++++++++++ packer/packer_test.go | 11 +++ .../test-fixtures/validate-dup-builder.json | 10 +++ .../test-fixtures/validate-req-variable.json | 9 +++ 5 files changed, 167 insertions(+) create mode 100644 packer/core.go create mode 100644 packer/core_test.go create mode 100644 packer/packer_test.go create mode 100644 packer/test-fixtures/validate-dup-builder.json create mode 100644 packer/test-fixtures/validate-req-variable.json diff --git a/packer/core.go b/packer/core.go new file mode 100644 index 000000000..de6fe4169 --- /dev/null +++ b/packer/core.go @@ -0,0 +1,77 @@ +package packer + +import ( + "fmt" + "os" + + "github.com/hashicorp/go-multierror" + "github.com/mitchellh/packer/template" +) + +// Core is the main executor of Packer. If Packer is being used as a +// library, this is the struct you'll want to instantiate to get anything done. +type Core struct { + cache Cache + components ComponentFinder + ui Ui + template *template.Template + variables map[string]string +} + +// CoreConfig is the structure for initializing a new Core. Once a CoreConfig +// is used to initialize a Core, it shouldn't be re-used or modified again. +type CoreConfig struct { + Cache Cache + Components ComponentFinder + Ui Ui + Template *template.Template + Variables map[string]string +} + +// NewCore creates a new Core. +func NewCore(c *CoreConfig) (*Core, error) { + if c.Ui == nil { + c.Ui = &BasicUi{ + Reader: os.Stdin, + Writer: os.Stdout, + ErrorWriter: os.Stdout, + } + } + + return &Core{ + cache: c.Cache, + components: c.Components, + ui: c.Ui, + template: c.Template, + variables: c.Variables, + }, nil +} + +// Validate does a full validation of the template. +// +// This will automatically call template.Validate() in addition to doing +// richer semantic checks around variables and so on. +func (c *Core) Validate() error { + // First validate the template in general, we can't do anything else + // unless the template itself is valid. + if err := c.template.Validate(); err != nil { + return err + } + + // Validate variables are set + var err error + for n, v := range c.template.Variables { + if v.Required { + if _, ok := c.variables[n]; !ok { + err = multierror.Append(err, fmt.Errorf( + "required variable not set: %s", n)) + } + } + } + + // TODO: validate all builders exist + // TODO: ^^ provisioner + // TODO: ^^ post-processor + + return err +} diff --git a/packer/core_test.go b/packer/core_test.go new file mode 100644 index 000000000..dc7880302 --- /dev/null +++ b/packer/core_test.go @@ -0,0 +1,60 @@ +package packer + +import ( + "os" + "testing" + + "github.com/mitchellh/packer/template" +) + +func TestCoreValidate(t *testing.T) { + cases := []struct { + File string + Vars map[string]string + Err bool + }{ + { + "validate-dup-builder.json", + nil, + true, + }, + + // Required variable not set + { + "validate-req-variable.json", + nil, + true, + }, + + { + "validate-req-variable.json", + map[string]string{"foo": "bar"}, + false, + }, + } + + for _, tc := range cases { + f, err := os.Open(fixtureDir(tc.File)) + if err != nil { + t.Fatalf("err: %s", err) + } + + tpl, err := template.Parse(f) + f.Close() + if err != nil { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + + core, err := NewCore(&CoreConfig{ + Template: tpl, + Variables: tc.Vars, + }) + if err != nil { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + + if err := core.Validate(); (err != nil) != tc.Err { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + } +} diff --git a/packer/packer_test.go b/packer/packer_test.go new file mode 100644 index 000000000..ec536e2c3 --- /dev/null +++ b/packer/packer_test.go @@ -0,0 +1,11 @@ +package packer + +import ( + "path/filepath" +) + +const FixtureDir = "./test-fixtures" + +func fixtureDir(n string) string { + return filepath.Join(FixtureDir, n) +} diff --git a/packer/test-fixtures/validate-dup-builder.json b/packer/test-fixtures/validate-dup-builder.json new file mode 100644 index 000000000..21d206d35 --- /dev/null +++ b/packer/test-fixtures/validate-dup-builder.json @@ -0,0 +1,10 @@ +{ + "builders": [ + {"type": "foo"} + ], + + "provisioners": [{ + "type": "foo", + "only": ["bar"] + }] +} diff --git a/packer/test-fixtures/validate-req-variable.json b/packer/test-fixtures/validate-req-variable.json new file mode 100644 index 000000000..796d0b669 --- /dev/null +++ b/packer/test-fixtures/validate-req-variable.json @@ -0,0 +1,9 @@ +{ + "variables": { + "foo": null + }, + + "builders": [{ + "type": "foo" + }] +} From d74dacc4c02cd1808f71930fa5fac05e5f1c35e6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 23 May 2015 15:08:50 -0700 Subject: [PATCH 18/39] packer: Core.Build --- packer/core.go | 29 +++++++++++++++++++++++++++++ packer/testing.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 packer/testing.go diff --git a/packer/core.go b/packer/core.go index de6fe4169..caa18a196 100644 --- a/packer/core.go +++ b/packer/core.go @@ -47,6 +47,35 @@ func NewCore(c *CoreConfig) (*Core, error) { }, nil } +// Build returns the Build object for the given name. +func (c *Core) Build(n string) (Build, error) { + // Setup the builder + configBuilder, ok := c.template.Builders[n] + if !ok { + return nil, fmt.Errorf("no such build found: %s", n) + } + builder, err := c.components.Builder(configBuilder.Type) + if err != nil { + return nil, fmt.Errorf( + "error initializing builder '%s': %s", + configBuilder.Type, err) + } + if builder == nil { + return nil, fmt.Errorf( + "builder type not found: %s", configBuilder.Type) + } + + // TODO: template process name + + return &coreBuild{ + name: n, + builder: builder, + builderConfig: configBuilder.Config, + builderType: configBuilder.Type, + variables: c.variables, + }, nil +} + // Validate does a full validation of the template. // // This will automatically call template.Validate() in addition to doing diff --git a/packer/testing.go b/packer/testing.go new file mode 100644 index 000000000..099119180 --- /dev/null +++ b/packer/testing.go @@ -0,0 +1,44 @@ +package packer + +import ( + "bytes" + "io/ioutil" + "os" + "testing" +) + +func TestCoreConfig(t *testing.T) *CoreConfig { + // Create a UI that is effectively /dev/null everywhere + var buf bytes.Buffer + ui := &BasicUi{ + Reader: &buf, + Writer: ioutil.Discard, + ErrorWriter: ioutil.Discard, + } + + // Create some test components + components := ComponentFinder{ + Builder: func(n string) (Builder, error) { + if n != "test" { + return nil, nil + } + + return &MockBuilder{}, nil + }, + } + + return &CoreConfig{ + Cache: &FileCache{CacheDir: os.TempDir()}, + Components: components, + Ui: ui, + } +} + +func TestCore(t *testing.T, c *CoreConfig) *Core { + core, err := NewCore(c) + if err != nil { + t.Fatalf("err: %s", err) + } + + return core +} From 97a48e35bb4bb579843833a8f118124c2cf2c110 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 23 May 2015 15:44:54 -0700 Subject: [PATCH 19/39] template: ParseFile --- template/parse.go | 13 +++++++++++++ template/parse_test.go | 9 +-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/template/parse.go b/template/parse.go index c0e21b1c4..a46adc594 100644 --- a/template/parse.go +++ b/template/parse.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "os" "sort" "github.com/hashicorp/go-multierror" @@ -290,3 +291,15 @@ func Parse(r io.Reader) (*Template, error) { // Return the template parsed from the raw structure return rawTpl.Template() } + +// ParseFile is the same as Parse but is a helper to automatically open +// a file for parsing. +func ParseFile(path string) (*Template, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + return Parse(f) +} diff --git a/template/parse_test.go b/template/parse_test.go index 023c3d537..2cca68b88 100644 --- a/template/parse_test.go +++ b/template/parse_test.go @@ -1,7 +1,6 @@ package template import ( - "os" "reflect" "testing" "time" @@ -272,13 +271,7 @@ func TestParse(t *testing.T) { } for _, tc := range cases { - f, err := os.Open(fixtureDir(tc.File)) - if err != nil { - t.Fatalf("err: %s", err) - } - - tpl, err := Parse(f) - f.Close() + tpl, err := ParseFile(fixtureDir(tc.File)) if (err != nil) != tc.Err { t.Fatalf("err: %s", err) } From 47b570a2d2510e007afeb222c6724fe161540b96 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 23 May 2015 16:06:11 -0700 Subject: [PATCH 20/39] template/interpolate: flip disable to enableenv --- template/interpolate/funcs.go | 2 +- template/interpolate/funcs_test.go | 4 ++-- template/interpolate/i.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go index 68592e046..1ddbbe167 100644 --- a/template/interpolate/funcs.go +++ b/template/interpolate/funcs.go @@ -51,7 +51,7 @@ func Funcs(ctx *Context) template.FuncMap { func funcGenEnv(ctx *Context) interface{} { return func(k string) (string, error) { - if ctx.DisableEnv { + if !ctx.EnableEnv { // The error message doesn't have to be that detailed since // semantic checks should catch this. return "", errors.New("env vars are not allowed here") diff --git a/template/interpolate/funcs_test.go b/template/interpolate/funcs_test.go index 7afa53447..aad05d376 100644 --- a/template/interpolate/funcs_test.go +++ b/template/interpolate/funcs_test.go @@ -26,7 +26,7 @@ func TestFuncEnv(t *testing.T) { os.Setenv("PACKER_TEST_ENV", "foo") defer os.Setenv("PACKER_TEST_ENV", "") - ctx := &Context{} + ctx := &Context{EnableEnv: true} for _, tc := range cases { i := &I{Value: tc.Input} result, err := i.Render(ctx) @@ -53,7 +53,7 @@ func TestFuncEnv_disable(t *testing.T) { }, } - ctx := &Context{DisableEnv: true} + ctx := &Context{EnableEnv: false} for _, tc := range cases { i := &I{Value: tc.Input} result, err := i.Render(ctx) diff --git a/template/interpolate/i.go b/template/interpolate/i.go index 1033ad86a..5f70ed82a 100644 --- a/template/interpolate/i.go +++ b/template/interpolate/i.go @@ -15,8 +15,8 @@ type Context struct { // "user" function reads from. UserVariables map[string]string - // DisableEnv disables the env function - DisableEnv bool + // EnableEnv enables the env function + EnableEnv bool } // I stands for "interpolation" and is the main interpolation struct From 3ebfe06ec8dcfbe2da1d7d7fde02d9fd4c61193d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 23 May 2015 16:12:32 -0700 Subject: [PATCH 21/39] packer: render build names --- packer/core.go | 31 ++++++++++++++++ packer/core_test.go | 41 +++++++++++++++++++++ packer/test-fixtures/build-names-basic.json | 5 +++ packer/test-fixtures/build-names-func.json | 5 +++ template/interpolate/i.go | 5 +++ 5 files changed, 87 insertions(+) create mode 100644 packer/test-fixtures/build-names-basic.json create mode 100644 packer/test-fixtures/build-names-func.json diff --git a/packer/core.go b/packer/core.go index caa18a196..21ea4b8f9 100644 --- a/packer/core.go +++ b/packer/core.go @@ -3,9 +3,11 @@ package packer import ( "fmt" "os" + "sort" "github.com/hashicorp/go-multierror" "github.com/mitchellh/packer/template" + "github.com/mitchellh/packer/template/interpolate" ) // Core is the main executor of Packer. If Packer is being used as a @@ -16,6 +18,7 @@ type Core struct { ui Ui template *template.Template variables map[string]string + builds map[string]*template.Builder } // CoreConfig is the structure for initializing a new Core. Once a CoreConfig @@ -38,15 +41,43 @@ func NewCore(c *CoreConfig) (*Core, error) { } } + // Go through and interpolate all the build names. We shuld be able + // to do this at this point with the variables. + builds := make(map[string]*template.Builder) + for _, b := range c.Template.Builders { + v, err := interpolate.Render(b.Name, &interpolate.Context{ + UserVariables: c.Variables, + }) + if err != nil { + return nil, fmt.Errorf( + "Error interpolating builder '%s': %s", + b.Name, err) + } + + builds[v] = b + } + return &Core{ cache: c.Cache, components: c.Components, ui: c.Ui, template: c.Template, variables: c.Variables, + builds: builds, }, nil } +// BuildNames returns the builds that are available in this configured core. +func (c *Core) BuildNames() []string { + r := make([]string, 0, len(c.builds)) + for n, _ := range c.builds { + r = append(r, n) + } + sort.Strings(r) + + return r +} + // Build returns the Build object for the given name. func (c *Core) Build(n string) (Build, error) { // Setup the builder diff --git a/packer/core_test.go b/packer/core_test.go index dc7880302..d3f338d12 100644 --- a/packer/core_test.go +++ b/packer/core_test.go @@ -2,11 +2,52 @@ package packer import ( "os" + "reflect" "testing" "github.com/mitchellh/packer/template" ) +func TestCoreBuildNames(t *testing.T) { + cases := []struct { + File string + Vars map[string]string + Result []string + }{ + { + "build-names-basic.json", + nil, + []string{"something"}, + }, + + { + "build-names-func.json", + nil, + []string{"TUBES"}, + }, + } + + for _, tc := range cases { + tpl, err := template.ParseFile(fixtureDir(tc.File)) + if err != nil { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + + core, err := NewCore(&CoreConfig{ + Template: tpl, + Variables: tc.Vars, + }) + if err != nil { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + + names := core.BuildNames() + if !reflect.DeepEqual(names, tc.Result) { + t.Fatalf("err: %s\n\n%#v", tc.File, names) + } + } +} + func TestCoreValidate(t *testing.T) { cases := []struct { File string diff --git a/packer/test-fixtures/build-names-basic.json b/packer/test-fixtures/build-names-basic.json new file mode 100644 index 000000000..1b0162551 --- /dev/null +++ b/packer/test-fixtures/build-names-basic.json @@ -0,0 +1,5 @@ +{ + "builders": [ + {"type": "something"} + ] +} diff --git a/packer/test-fixtures/build-names-func.json b/packer/test-fixtures/build-names-func.json new file mode 100644 index 000000000..feb28cf37 --- /dev/null +++ b/packer/test-fixtures/build-names-func.json @@ -0,0 +1,5 @@ +{ + "builders": [ + {"type": "{{upper `tubes`}}"} + ] +} diff --git a/template/interpolate/i.go b/template/interpolate/i.go index 5f70ed82a..d52653fcf 100644 --- a/template/interpolate/i.go +++ b/template/interpolate/i.go @@ -19,6 +19,11 @@ type Context struct { EnableEnv bool } +// Render is shorthand for constructing an I and calling Render. +func Render(v string, ctx *Context) (string, error) { + return (&I{Value: v}).Render(ctx) +} + // I stands for "interpolation" and is the main interpolation struct // in order to render values. type I struct { From 9d89ca8e07be91d81a6c3365011cb412d226c2bd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 23 May 2015 16:30:45 -0700 Subject: [PATCH 22/39] command: build should be converted to new API, compiles --- TODO.txt | 2 + command/build.go | 114 +++++++++++--------------- command/command_test.go | 18 ++-- command/meta.go | 145 ++++++++++++++++++++++++++++++++- command/push.go | 2 +- command/version.go | 6 +- commands.go | 5 +- config.go | 3 + helper/flag-kv/flag.go | 29 +++++++ helper/flag-kv/flag_test.go | 56 +++++++++++++ helper/flag-slice/flag.go | 16 ++++ helper/flag-slice/flag_test.go | 33 ++++++++ main.go | 7 ++ 13 files changed, 355 insertions(+), 81 deletions(-) create mode 100644 TODO.txt create mode 100644 helper/flag-kv/flag.go create mode 100644 helper/flag-kv/flag_test.go create mode 100644 helper/flag-slice/flag.go create mode 100644 helper/flag-slice/flag_test.go diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 000000000..031ec3ae3 --- /dev/null +++ b/TODO.txt @@ -0,0 +1,2 @@ +- var-file doesn't work +- prov/post-processors/hooks don't work diff --git a/command/build.go b/command/build.go index a0b33e530..ec6f70555 100644 --- a/command/build.go +++ b/command/build.go @@ -2,16 +2,16 @@ package command import ( "bytes" - "flag" "fmt" - cmdcommon "github.com/mitchellh/packer/common/command" - "github.com/mitchellh/packer/packer" "log" "os" "os/signal" "strconv" "strings" "sync" + + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template" ) type BuildCommand struct { @@ -20,71 +20,52 @@ type BuildCommand struct { func (c BuildCommand) Run(args []string) int { var cfgColor, cfgDebug, cfgForce, cfgParallel bool - buildOptions := new(cmdcommon.BuildOptions) - - env, err := c.Meta.Environment() - if err != nil { - c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err)) + flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars) + flags.Usage = func() { c.Ui.Say(c.Help()) } + flags.BoolVar(&cfgColor, "color", true, "") + flags.BoolVar(&cfgDebug, "debug", false, "") + flags.BoolVar(&cfgForce, "force", false, "") + flags.BoolVar(&cfgParallel, "parallel", true, "") + if err := flags.Parse(args); err != nil { return 1 } - cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError) - cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } - cmdFlags.BoolVar(&cfgColor, "color", true, "enable or disable color") - cmdFlags.BoolVar(&cfgDebug, "debug", false, "debug mode for builds") - cmdFlags.BoolVar(&cfgForce, "force", false, "force a build if artifacts exist") - cmdFlags.BoolVar(&cfgParallel, "parallel", true, "enable/disable parallelization") - cmdcommon.BuildOptionFlags(cmdFlags, buildOptions) - if err := cmdFlags.Parse(args); err != nil { - return 1 - } - - args = cmdFlags.Args() + args = flags.Args() if len(args) != 1 { - cmdFlags.Usage() + flags.Usage() return 1 } - if err := buildOptions.Validate(); err != nil { - env.Ui().Error(err.Error()) - env.Ui().Error("") - env.Ui().Error(c.Help()) - return 1 - } - - userVars, err := buildOptions.AllUserVars() + // Parse the template + tpl, err := template.ParseFile(args[0]) if err != nil { - env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err)) - env.Ui().Error("") - env.Ui().Error(c.Help()) + c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } - // Read the file into a byte array so that we can parse the template - log.Printf("Reading template: %s", args[0]) - tpl, err := packer.ParseTemplateFile(args[0], userVars) + // Get the core + core, err := c.Meta.Core(tpl) if err != nil { - env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err)) + c.Ui.Error(err.Error()) return 1 } - // The component finder for our builds - components := &packer.ComponentFinder{ - Builder: env.Builder, - Hook: env.Hook, - PostProcessor: env.PostProcessor, - Provisioner: env.Provisioner, - } + // Get the builds we care about + buildNames := c.Meta.BuildNames(core) + builds := make([]packer.Build, 0, len(buildNames)) + for _, n := range buildNames { + b, err := core.Build(n) + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Failed to initialize build '%s': %s", + n, err)) + } - // Go through each builder and compile the builds that we care about - builds, err := buildOptions.Builds(tpl, components) - if err != nil { - env.Ui().Error(err.Error()) - return 1 + builds = append(builds, b) } if cfgDebug { - env.Ui().Say("Debug mode enabled. Builds will not be parallelized.") + c.Ui.Say("Debug mode enabled. Builds will not be parallelized.") } // Compile all the UIs for the builds @@ -95,24 +76,23 @@ func (c BuildCommand) Run(args []string) int { packer.UiColorYellow, packer.UiColorBlue, } - buildUis := make(map[string]packer.Ui) - for i, b := range builds { + for i, b := range buildNames { var ui packer.Ui - ui = env.Ui() + ui = c.Ui if cfgColor { ui = &packer.ColoredUi{ Color: colors[i%len(colors)], - Ui: env.Ui(), + Ui: ui, } } - buildUis[b.Name()] = ui - ui.Say(fmt.Sprintf("%s output will be in this color.", b.Name())) + buildUis[b] = ui + ui.Say(fmt.Sprintf("%s output will be in this color.", b)) } // Add a newline between the color output and the actual output - env.Ui().Say("") + c.Ui.Say("") log.Printf("Build debug mode: %v", cfgDebug) log.Printf("Force build: %v", cfgForce) @@ -125,7 +105,7 @@ func (c BuildCommand) Run(args []string) int { warnings, err := b.Prepare() if err != nil { - env.Ui().Error(err.Error()) + c.Ui.Error(err.Error()) return 1 } if len(warnings) > 0 { @@ -169,7 +149,7 @@ func (c BuildCommand) Run(args []string) int { name := b.Name() log.Printf("Starting build run: %s", name) ui := buildUis[name] - runArtifacts, err := b.Run(ui, env.Cache()) + runArtifacts, err := b.Run(ui, c.CoreConfig.Cache) if err != nil { ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err)) @@ -205,34 +185,34 @@ func (c BuildCommand) Run(args []string) int { interruptWg.Wait() if interrupted { - env.Ui().Say("Cleanly cancelled builds after being interrupted.") + c.Ui.Say("Cleanly cancelled builds after being interrupted.") return 1 } if len(errors) > 0 { - env.Ui().Machine("error-count", strconv.FormatInt(int64(len(errors)), 10)) + c.Ui.Machine("error-count", strconv.FormatInt(int64(len(errors)), 10)) - env.Ui().Error("\n==> Some builds didn't complete successfully and had errors:") + c.Ui.Error("\n==> Some builds didn't complete successfully and had errors:") for name, err := range errors { // Create a UI for the machine readable stuff to be targetted ui := &packer.TargettedUi{ Target: name, - Ui: env.Ui(), + Ui: c.Ui, } ui.Machine("error", err.Error()) - env.Ui().Error(fmt.Sprintf("--> %s: %s", name, err)) + c.Ui.Error(fmt.Sprintf("--> %s: %s", name, err)) } } if len(artifacts) > 0 { - env.Ui().Say("\n==> Builds finished. The artifacts of successful builds are:") + c.Ui.Say("\n==> Builds finished. The artifacts of successful builds are:") for name, buildArtifacts := range artifacts { // Create a UI for the machine readable stuff to be targetted ui := &packer.TargettedUi{ Target: name, - Ui: env.Ui(), + Ui: c.Ui, } // Machine-readable helpful @@ -267,11 +247,11 @@ func (c BuildCommand) Run(args []string) int { } ui.Machine("artifact", iStr, "end") - env.Ui().Say(message.String()) + c.Ui.Say(message.String()) } } } else { - env.Ui().Say("\n==> Builds finished but no artifacts were created.") + c.Ui.Say("\n==> Builds finished but no artifacts were created.") } if len(errors) > 0 { diff --git a/command/command_test.go b/command/command_test.go index 500ea7f9e..49e0c7276 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -1,20 +1,23 @@ package command import ( + "bytes" "path/filepath" "testing" - "github.com/mitchellh/cli" + "github.com/mitchellh/packer/packer" ) const fixturesDir = "./test-fixtures" func fatalCommand(t *testing.T, m Meta) { - ui := m.Ui.(*cli.MockUi) + ui := m.Ui.(*packer.BasicUi) + out := ui.Writer.(*bytes.Buffer) + err := ui.ErrorWriter.(*bytes.Buffer) t.Fatalf( "Bad exit code.\n\nStdout:\n\n%s\n\nStderr:\n\n%s", - ui.OutputWriter.String(), - ui.ErrorWriter.String()) + out.String(), + err.String()) } func testFixture(n string) string { @@ -22,7 +25,12 @@ func testFixture(n string) string { } func testMeta(t *testing.T) Meta { + var out, err bytes.Buffer + return Meta{ - Ui: new(cli.MockUi), + Ui: &packer.BasicUi{ + Writer: &out, + ErrorWriter: &err, + }, } } diff --git a/command/meta.go b/command/meta.go index 9c2f7f921..bb059da35 100644 --- a/command/meta.go +++ b/command/meta.go @@ -1,13 +1,152 @@ package command import ( - "github.com/mitchellh/cli" + "bufio" + "flag" + "fmt" + "io" + + "github.com/mitchellh/packer/helper/flag-kv" + "github.com/mitchellh/packer/helper/flag-slice" "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template" ) +// FlagSetFlags is an enum to define what flags are present in the +// default FlagSet returned by Meta.FlagSet +type FlagSetFlags uint + +const ( + FlagSetNone FlagSetFlags = 0 + FlagSetBuildFilter FlagSetFlags = 1 << iota + FlagSetVars +) + +// Meta contains the meta-options and functionality that nearly every +// Packer command inherits. type Meta struct { - EnvConfig *packer.EnvironmentConfig - Ui cli.Ui + CoreConfig *packer.CoreConfig + EnvConfig *packer.EnvironmentConfig + Ui packer.Ui + + // These are set by command-line flags + flagBuildExcept []string + flagBuildOnly []string + flagVars map[string]string + flagVarFiles []string +} + +// Core returns the core for the given template given the configured +// CoreConfig and user variables on this Meta. +func (m *Meta) Core(tpl *template.Template) (*packer.Core, error) { + // Copy the config so we don't modify it + config := *m.CoreConfig + config.Template = tpl + config.Variables = m.flagVars + + // Init the core + core, err := packer.NewCore(&config) + if err != nil { + return nil, fmt.Errorf("Error initializing core: %s", err) + } + + // Validate it + if err := core.Validate(); err != nil { + return nil, err + } + + return core, nil +} + +// BuildNames returns the list of builds that are in the given core +// that we care about taking into account the only and except flags. +func (m *Meta) BuildNames(c *packer.Core) []string { + // TODO: test + + // Filter the "only" + if len(m.flagBuildOnly) > 0 { + // Build a set of all the available names + nameSet := make(map[string]struct{}) + for _, n := range c.BuildNames() { + nameSet[n] = struct{}{} + } + + // Build our result set which we pre-allocate some sane number + result := make([]string, 0, len(m.flagBuildOnly)) + for _, n := range m.flagBuildOnly { + if _, ok := nameSet[n]; ok { + result = append(result, n) + } + } + + return result + } + + // Filter the "except" + if len(m.flagBuildExcept) > 0 { + // Build a set of the things we don't want + nameSet := make(map[string]struct{}) + for _, n := range m.flagBuildExcept { + nameSet[n] = struct{}{} + } + + // Build our result set which is the names of all builds except + // those in the given set. + names := c.BuildNames() + result := make([]string, 0, len(names)) + for _, n := range names { + if _, ok := nameSet[n]; !ok { + result = append(result, n) + } + } + return result + } + + // We care about everything + return c.BuildNames() +} + +// FlagSet returns a FlagSet with the common flags that every +// command implements. The exact behavior of FlagSet can be configured +// using the flags as the second parameter, for example to disable +// build settings on the commands that don't handle builds. +func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet { + f := flag.NewFlagSet(n, flag.ContinueOnError) + + // FlagSetBuildFilter tells us to enable the settings for selecting + // builds we care about. + if fs&FlagSetBuildFilter != 0 { + f.Var((*sliceflag.StringFlag)(&m.flagBuildExcept), "except", "") + f.Var((*sliceflag.StringFlag)(&m.flagBuildOnly), "only", "") + } + + // FlagSetVars tells us what variables to use + if fs&FlagSetVars != 0 { + f.Var((*kvflag.Flag)(&m.flagVars), "var", "") + f.Var((*sliceflag.StringFlag)(&m.flagVarFiles), "var-file", "") + } + + // Create an io.Writer that writes to our Ui properly for errors. + // This is kind of a hack, but it does the job. Basically: create + // a pipe, use a scanner to break it into lines, and output each line + // to the UI. Do this forever. + errR, errW := io.Pipe() + errScanner := bufio.NewScanner(errR) + go func() { + for errScanner.Scan() { + m.Ui.Error(errScanner.Text()) + } + }() + f.SetOutput(errW) + + return f +} + +// ValidateFlags should be called after parsing flags to validate the +// given flags +func (m *Meta) ValidateFlags() error { + // TODO + return nil } func (m *Meta) Environment() (packer.Environment, error) { diff --git a/command/push.go b/command/push.go index 74915de3f..ef0f42924 100644 --- a/command/push.go +++ b/command/push.go @@ -221,7 +221,7 @@ func (c *PushCommand) Run(args []string) int { return 1 } - c.Ui.Output(fmt.Sprintf("Push successful to '%s'", tpl.Push.Name)) + c.Ui.Say(fmt.Sprintf("Push successful to '%s'", tpl.Push.Name)) return 0 } diff --git a/command/version.go b/command/version.go index 689614e60..d9358b3a6 100644 --- a/command/version.go +++ b/command/version.go @@ -53,13 +53,13 @@ func (c *VersionCommand) Run(args []string) int { } } - c.Ui.Output(versionString.String()) + c.Ui.Say(versionString.String()) // If we have a version check function, then let's check for // the latest version as well. if c.CheckFunc != nil { // Separate the prior output with a newline - c.Ui.Output("") + c.Ui.Say("") // Check the latest version info, err := c.CheckFunc() @@ -68,7 +68,7 @@ func (c *VersionCommand) Run(args []string) int { "Error checking latest version: %s", err)) } if info.Outdated { - c.Ui.Output(fmt.Sprintf( + c.Ui.Say(fmt.Sprintf( "Your version of Packer is out of date! The latest version\n"+ "is %s. You can update by downloading from www.packer.io", info.Latest)) diff --git a/commands.go b/commands.go index 9c6458f64..24bdc2b04 100644 --- a/commands.go +++ b/commands.go @@ -27,8 +27,9 @@ func init() { } meta := command.Meta{ - EnvConfig: &EnvConfig, - Ui: Ui, + CoreConfig: &CoreConfig, + EnvConfig: &EnvConfig, + Ui: Ui, } Commands = map[string]cli.CommandFactory{ diff --git a/config.go b/config.go index 4acb3c3b1..34cfdcb40 100644 --- a/config.go +++ b/config.go @@ -13,6 +13,9 @@ import ( "github.com/mitchellh/packer/packer/plugin" ) +// CoreConfig is the global CoreConfig we use to initialize the CLI. +var CoreConfig packer.CoreConfig + // EnvConfig is the global EnvironmentConfig we use to initialize the CLI. var EnvConfig packer.EnvironmentConfig diff --git a/helper/flag-kv/flag.go b/helper/flag-kv/flag.go new file mode 100644 index 000000000..0bf4b0086 --- /dev/null +++ b/helper/flag-kv/flag.go @@ -0,0 +1,29 @@ +package kvflag + +import ( + "fmt" + "strings" +) + +// Flag is a flag.Value implementation for parsing user variables +// from the command-line in the format of '-var key=value'. +type Flag map[string]string + +func (v *Flag) String() string { + return "" +} + +func (v *Flag) Set(raw string) error { + idx := strings.Index(raw, "=") + if idx == -1 { + return fmt.Errorf("No '=' value in arg: %s", raw) + } + + if *v == nil { + *v = make(map[string]string) + } + + key, value := raw[0:idx], raw[idx+1:] + (*v)[key] = value + return nil +} diff --git a/helper/flag-kv/flag_test.go b/helper/flag-kv/flag_test.go new file mode 100644 index 000000000..9f81d5192 --- /dev/null +++ b/helper/flag-kv/flag_test.go @@ -0,0 +1,56 @@ +package kvflag + +import ( + "flag" + "reflect" + "testing" +) + +func TestFlag_impl(t *testing.T) { + var _ flag.Value = new(Flag) +} + +func TestFlag(t *testing.T) { + cases := []struct { + Input string + Output map[string]string + Error bool + }{ + { + "key=value", + map[string]string{"key": "value"}, + false, + }, + + { + "key=", + map[string]string{"key": ""}, + false, + }, + + { + "key=foo=bar", + map[string]string{"key": "foo=bar"}, + false, + }, + + { + "key", + nil, + true, + }, + } + + for _, tc := range cases { + f := new(Flag) + err := f.Set(tc.Input) + if (err != nil) != tc.Error { + t.Fatalf("bad error. Input: %#v", tc.Input) + } + + actual := map[string]string(*f) + if !reflect.DeepEqual(actual, tc.Output) { + t.Fatalf("bad: %#v", actual) + } + } +} diff --git a/helper/flag-slice/flag.go b/helper/flag-slice/flag.go new file mode 100644 index 000000000..da75149dc --- /dev/null +++ b/helper/flag-slice/flag.go @@ -0,0 +1,16 @@ +package sliceflag + +import "strings" + +// StringFlag implements the flag.Value interface and allows multiple +// calls to the same variable to append a list. +type StringFlag []string + +func (s *StringFlag) String() string { + return strings.Join(*s, ",") +} + +func (s *StringFlag) Set(value string) error { + *s = append(*s, value) + return nil +} diff --git a/helper/flag-slice/flag_test.go b/helper/flag-slice/flag_test.go new file mode 100644 index 000000000..f72e1d960 --- /dev/null +++ b/helper/flag-slice/flag_test.go @@ -0,0 +1,33 @@ +package sliceflag + +import ( + "flag" + "reflect" + "testing" +) + +func TestStringFlag_implements(t *testing.T) { + var raw interface{} + raw = new(StringFlag) + if _, ok := raw.(flag.Value); !ok { + t.Fatalf("StringFlag should be a Value") + } +} + +func TestStringFlagSet(t *testing.T) { + sv := new(StringFlag) + err := sv.Set("foo") + if err != nil { + t.Fatalf("err: %s", err) + } + + err = sv.Set("bar") + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := []string{"foo", "bar"} + if !reflect.DeepEqual([]string(*sv), expected) { + t.Fatalf("Bad: %#v", sv) + } +} diff --git a/main.go b/main.go index 2bebafb9e..8616a8e2d 100644 --- a/main.go +++ b/main.go @@ -159,6 +159,13 @@ func wrappedMain() int { } } + // Create the core configuration + CoreConfig = packer.CoreConfig{ + Cache: EnvConfig.Cache, + Components: EnvConfig.Components, + Ui: EnvConfig.Ui, + } + //setupSignalHandlers(env) cli := &cli.CLI{ From ba359394b11059ed9af60a974eca1f210320e4af Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 23 May 2015 16:32:36 -0700 Subject: [PATCH 23/39] fix compilation --- commands.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/commands.go b/commands.go index 24bdc2b04..d3a458f16 100644 --- a/commands.go +++ b/commands.go @@ -6,6 +6,7 @@ import ( "github.com/mitchellh/cli" "github.com/mitchellh/packer/command" + "github.com/mitchellh/packer/packer" ) // Commands is the mapping of all the available Terraform commands. @@ -18,18 +19,14 @@ const ErrorPrefix = "e:" const OutputPrefix = "o:" func init() { - Ui = &cli.PrefixedUi{ - AskPrefix: OutputPrefix, - OutputPrefix: OutputPrefix, - InfoPrefix: OutputPrefix, - ErrorPrefix: ErrorPrefix, - Ui: &cli.BasicUi{Writer: os.Stdout}, - } - meta := command.Meta{ CoreConfig: &CoreConfig, EnvConfig: &EnvConfig, - Ui: Ui, + Ui: &packer.BasicUi{ + Reader: os.Stdin, + Writer: os.Stdout, + ErrorWriter: os.Stdout, + }, } Commands = map[string]cli.CommandFactory{ From dc74ec56127e64640906ab850cd2b99dfd49fc16 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 25 May 2015 17:29:10 -0700 Subject: [PATCH 24/39] packer: remove Environment --- command/fix.go | 29 ++- command/inspect.go | 17 +- command/meta.go | 5 - command/validate.go | 100 +++++------ command/version.go | 12 +- commands.go | 1 - config.go | 3 - main.go | 20 +-- packer/core.go | 22 +++ packer/core_test.go | 11 ++ packer/environment.go | 183 ------------------- packer/environment_test.go | 310 --------------------------------- packer/rpc/client.go | 7 - packer/rpc/environment.go | 178 ------------------- packer/rpc/environment_test.go | 124 ------------- packer/rpc/server.go | 8 - signal.go | 6 +- 17 files changed, 102 insertions(+), 934 deletions(-) delete mode 100644 packer/environment.go delete mode 100644 packer/environment_test.go delete mode 100644 packer/rpc/environment.go delete mode 100644 packer/rpc/environment_test.go diff --git a/command/fix.go b/command/fix.go index aac0b3916..e908dc52e 100644 --- a/command/fix.go +++ b/command/fix.go @@ -3,7 +3,6 @@ package command import ( "bytes" "encoding/json" - "flag" "fmt" "log" "os" @@ -17,28 +16,22 @@ type FixCommand struct { } func (c *FixCommand) Run(args []string) int { - env, err := c.Meta.Environment() - if err != nil { - c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err)) + flags := c.Meta.FlagSet("fix", FlagSetNone) + flags.Usage = func() { c.Ui.Say(c.Help()) } + if err := flags.Parse(args); err != nil { return 1 } - cmdFlags := flag.NewFlagSet("fix", flag.ContinueOnError) - cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } - if err := cmdFlags.Parse(args); err != nil { - return 1 - } - - args = cmdFlags.Args() + args = flags.Args() if len(args) != 1 { - cmdFlags.Usage() + flags.Usage() return 1 } // Read the file for decoding tplF, err := os.Open(args[0]) if err != nil { - env.Ui().Error(fmt.Sprintf("Error opening template: %s", err)) + c.Ui.Error(fmt.Sprintf("Error opening template: %s", err)) return 1 } defer tplF.Close() @@ -47,7 +40,7 @@ func (c *FixCommand) Run(args []string) int { var templateData map[string]interface{} decoder := json.NewDecoder(tplF) if err := decoder.Decode(&templateData); err != nil { - env.Ui().Error(fmt.Sprintf("Error parsing template: %s", err)) + c.Ui.Error(fmt.Sprintf("Error parsing template: %s", err)) return 1 } @@ -65,7 +58,7 @@ func (c *FixCommand) Run(args []string) int { log.Printf("Running fixer: %s", name) input, err = fixer.Fix(input) if err != nil { - env.Ui().Error(fmt.Sprintf("Error fixing: %s", err)) + c.Ui.Error(fmt.Sprintf("Error fixing: %s", err)) return 1 } } @@ -73,20 +66,20 @@ func (c *FixCommand) Run(args []string) int { var output bytes.Buffer encoder := json.NewEncoder(&output) if err := encoder.Encode(input); err != nil { - env.Ui().Error(fmt.Sprintf("Error encoding: %s", err)) + c.Ui.Error(fmt.Sprintf("Error encoding: %s", err)) return 1 } var indented bytes.Buffer if err := json.Indent(&indented, output.Bytes(), "", " "); err != nil { - env.Ui().Error(fmt.Sprintf("Error encoding: %s", err)) + c.Ui.Error(fmt.Sprintf("Error encoding: %s", err)) return 1 } result := indented.String() result = strings.Replace(result, `\u003c`, "<", -1) result = strings.Replace(result, `\u003e`, ">", -1) - env.Ui().Say(result) + c.Ui.Say(result) return 0 } diff --git a/command/inspect.go b/command/inspect.go index 8a9fd9569..2574615fb 100644 --- a/command/inspect.go +++ b/command/inspect.go @@ -1,7 +1,6 @@ package command import ( - "flag" "fmt" "github.com/mitchellh/packer/packer" "log" @@ -9,19 +8,13 @@ import ( "strings" ) -type InspectCommand struct{ +type InspectCommand struct { Meta } func (c *InspectCommand) Run(args []string) int { - env, err := c.Meta.Environment() - if err != nil { - c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err)) - return 1 - } - - flags := flag.NewFlagSet("inspect", flag.ContinueOnError) - flags.Usage = func() { env.Ui().Say(c.Help()) } + flags := c.Meta.FlagSet("build", FlagSetNone) + flags.Usage = func() { c.Ui.Say(c.Help()) } if err := flags.Parse(args); err != nil { return 1 } @@ -36,12 +29,12 @@ func (c *InspectCommand) Run(args []string) int { log.Printf("Reading template: %#v", args[0]) tpl, err := packer.ParseTemplateFile(args[0], nil) if err != nil { - env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err)) + c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } // Convenience... - ui := env.Ui() + ui := c.Ui // Description if tpl.Description != "" { diff --git a/command/meta.go b/command/meta.go index bb059da35..e62577df9 100644 --- a/command/meta.go +++ b/command/meta.go @@ -26,7 +26,6 @@ const ( // Packer command inherits. type Meta struct { CoreConfig *packer.CoreConfig - EnvConfig *packer.EnvironmentConfig Ui packer.Ui // These are set by command-line flags @@ -148,7 +147,3 @@ func (m *Meta) ValidateFlags() error { // TODO return nil } - -func (m *Meta) Environment() (packer.Environment, error) { - return packer.NewEnvironment(m.EnvConfig) -} diff --git a/command/validate.go b/command/validate.go index a63019a9c..5d7e16c5d 100644 --- a/command/validate.go +++ b/command/validate.go @@ -1,12 +1,12 @@ package command import ( - "flag" "fmt" - cmdcommon "github.com/mitchellh/packer/common/command" - "github.com/mitchellh/packer/packer" "log" "strings" + + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template" ) type ValidateCommand struct { @@ -15,72 +15,54 @@ type ValidateCommand struct { func (c *ValidateCommand) Run(args []string) int { var cfgSyntaxOnly bool - buildOptions := new(cmdcommon.BuildOptions) - - env, err := c.Meta.Environment() - if err != nil { - c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err)) + flags := c.Meta.FlagSet("validate", FlagSetBuildFilter|FlagSetVars) + flags.Usage = func() { c.Ui.Say(c.Help()) } + flags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only") + if err := flags.Parse(args); err != nil { return 1 } - cmdFlags := flag.NewFlagSet("validate", flag.ContinueOnError) - cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } - cmdFlags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only") - cmdcommon.BuildOptionFlags(cmdFlags, buildOptions) - if err := cmdFlags.Parse(args); err != nil { - return 1 - } - - args = cmdFlags.Args() + args = flags.Args() if len(args) != 1 { - cmdFlags.Usage() + flags.Usage() return 1 } - if err := buildOptions.Validate(); err != nil { - env.Ui().Error(err.Error()) - env.Ui().Error("") - env.Ui().Error(c.Help()) - return 1 - } - - userVars, err := buildOptions.AllUserVars() + // Parse the template + tpl, err := template.ParseFile(args[0]) if err != nil { - env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err)) - env.Ui().Error("") - env.Ui().Error(c.Help()) - return 1 - } - - // Parse the template into a machine-usable format - log.Printf("Reading template: %s", args[0]) - tpl, err := packer.ParseTemplateFile(args[0], userVars) - if err != nil { - env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err)) + c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } + // If we're only checking syntax, then we're done already if cfgSyntaxOnly { - env.Ui().Say("Syntax-only check passed. Everything looks okay.") + c.Ui.Say("Syntax-only check passed. Everything looks okay.") return 0 } + // Get the core + core, err := c.Meta.Core(tpl) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + errs := make([]error, 0) warnings := make(map[string][]string) - // The component finder for our builds - components := &packer.ComponentFinder{ - Builder: env.Builder, - Hook: env.Hook, - PostProcessor: env.PostProcessor, - Provisioner: env.Provisioner, - } + // Get the builds we care about + buildNames := c.Meta.BuildNames(core) + builds := make([]packer.Build, 0, len(buildNames)) + for _, n := range buildNames { + b, err := core.Build(n) + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Failed to initialize build '%s': %s", + n, err)) + } - // Otherwise, get all the builds - builds, err := buildOptions.Builds(tpl, components) - if err != nil { - env.Ui().Error(err.Error()) - return 1 + builds = append(builds, b) } // Check the configuration of all builds @@ -96,12 +78,12 @@ func (c *ValidateCommand) Run(args []string) int { } if len(errs) > 0 { - env.Ui().Error("Template validation failed. Errors are shown below.\n") + c.Ui.Error("Template validation failed. Errors are shown below.\n") for i, err := range errs { - env.Ui().Error(err.Error()) + c.Ui.Error(err.Error()) if (i + 1) < len(errs) { - env.Ui().Error("") + c.Ui.Error("") } } @@ -109,21 +91,21 @@ func (c *ValidateCommand) Run(args []string) int { } if len(warnings) > 0 { - env.Ui().Say("Template validation succeeded, but there were some warnings.") - env.Ui().Say("These are ONLY WARNINGS, and Packer will attempt to build the") - env.Ui().Say("template despite them, but they should be paid attention to.\n") + c.Ui.Say("Template validation succeeded, but there were some warnings.") + c.Ui.Say("These are ONLY WARNINGS, and Packer will attempt to build the") + c.Ui.Say("template despite them, but they should be paid attention to.\n") for build, warns := range warnings { - env.Ui().Say(fmt.Sprintf("Warnings for build '%s':\n", build)) + c.Ui.Say(fmt.Sprintf("Warnings for build '%s':\n", build)) for _, warning := range warns { - env.Ui().Say(fmt.Sprintf("* %s", warning)) + c.Ui.Say(fmt.Sprintf("* %s", warning)) } } return 0 } - env.Ui().Say("Template validated successfully.") + c.Ui.Say("Template validated successfully.") return 0 } diff --git a/command/version.go b/command/version.go index d9358b3a6..cd170f2df 100644 --- a/command/version.go +++ b/command/version.go @@ -33,15 +33,9 @@ func (c *VersionCommand) Help() string { } func (c *VersionCommand) Run(args []string) int { - env, err := c.Meta.Environment() - if err != nil { - c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err)) - return 1 - } - - env.Ui().Machine("version", c.Version) - env.Ui().Machine("version-prelease", c.VersionPrerelease) - env.Ui().Machine("version-commit", c.Revision) + c.Ui.Machine("version", c.Version) + c.Ui.Machine("version-prelease", c.VersionPrerelease) + c.Ui.Machine("version-commit", c.Revision) var versionString bytes.Buffer fmt.Fprintf(&versionString, "Packer v%s", c.Version) diff --git a/commands.go b/commands.go index d3a458f16..e0f313957 100644 --- a/commands.go +++ b/commands.go @@ -21,7 +21,6 @@ const OutputPrefix = "o:" func init() { meta := command.Meta{ CoreConfig: &CoreConfig, - EnvConfig: &EnvConfig, Ui: &packer.BasicUi{ Reader: os.Stdin, Writer: os.Stdout, diff --git a/config.go b/config.go index 34cfdcb40..a9c07043f 100644 --- a/config.go +++ b/config.go @@ -16,9 +16,6 @@ import ( // CoreConfig is the global CoreConfig we use to initialize the CLI. var CoreConfig packer.CoreConfig -// EnvConfig is the global EnvironmentConfig we use to initialize the CLI. -var EnvConfig packer.EnvironmentConfig - type config struct { DisableCheckpoint bool `json:"disable_checkpoint"` DisableCheckpointSignature bool `json:"disable_checkpoint_signature"` diff --git a/main.go b/main.go index 8616a8e2d..73d4b88cf 100644 --- a/main.go +++ b/main.go @@ -140,14 +140,13 @@ func wrappedMain() int { defer plugin.CleanupClients() // Create the environment configuration - EnvConfig = *packer.DefaultEnvironmentConfig() - EnvConfig.Cache = cache - EnvConfig.Components.Builder = config.LoadBuilder - EnvConfig.Components.Hook = config.LoadHook - EnvConfig.Components.PostProcessor = config.LoadPostProcessor - EnvConfig.Components.Provisioner = config.LoadProvisioner + CoreConfig.Cache = cache + CoreConfig.Components.Builder = config.LoadBuilder + CoreConfig.Components.Hook = config.LoadHook + CoreConfig.Components.PostProcessor = config.LoadPostProcessor + CoreConfig.Components.Provisioner = config.LoadProvisioner if machineReadable { - EnvConfig.Ui = &packer.MachineReadableUi{ + CoreConfig.Ui = &packer.MachineReadableUi{ Writer: os.Stdout, } @@ -159,13 +158,6 @@ func wrappedMain() int { } } - // Create the core configuration - CoreConfig = packer.CoreConfig{ - Cache: EnvConfig.Cache, - Components: EnvConfig.Components, - Ui: EnvConfig.Ui, - } - //setupSignalHandlers(env) cli := &cli.CLI{ diff --git a/packer/core.go b/packer/core.go index 21ea4b8f9..4c4292ca7 100644 --- a/packer/core.go +++ b/packer/core.go @@ -31,6 +31,28 @@ type CoreConfig struct { Variables map[string]string } +// The function type used to lookup Builder implementations. +type BuilderFunc func(name string) (Builder, error) + +// The function type used to lookup Hook implementations. +type HookFunc func(name string) (Hook, error) + +// The function type used to lookup PostProcessor implementations. +type PostProcessorFunc func(name string) (PostProcessor, error) + +// The function type used to lookup Provisioner implementations. +type ProvisionerFunc func(name string) (Provisioner, error) + +// ComponentFinder is a struct that contains the various function +// pointers necessary to look up components of Packer such as builders, +// commands, etc. +type ComponentFinder struct { + Builder BuilderFunc + Hook HookFunc + PostProcessor PostProcessorFunc + Provisioner ProvisionerFunc +} + // NewCore creates a new Core. func NewCore(c *CoreConfig) (*Core, error) { if c.Ui == nil { diff --git a/packer/core_test.go b/packer/core_test.go index d3f338d12..d66a7786e 100644 --- a/packer/core_test.go +++ b/packer/core_test.go @@ -99,3 +99,14 @@ func TestCoreValidate(t *testing.T) { } } } + +func testComponentFinder() *ComponentFinder { + builderFactory := func(n string) (Builder, error) { return new(MockBuilder), nil } + ppFactory := func(n string) (PostProcessor, error) { return new(TestPostProcessor), nil } + provFactory := func(n string) (Provisioner, error) { return new(MockProvisioner), nil } + return &ComponentFinder{ + Builder: builderFactory, + PostProcessor: ppFactory, + Provisioner: provFactory, + } +} diff --git a/packer/environment.go b/packer/environment.go deleted file mode 100644 index 58585ffcf..000000000 --- a/packer/environment.go +++ /dev/null @@ -1,183 +0,0 @@ -// The packer package contains the core components of Packer. -package packer - -import ( - "errors" - "fmt" - "os" -) - -// The function type used to lookup Builder implementations. -type BuilderFunc func(name string) (Builder, error) - -// The function type used to lookup Hook implementations. -type HookFunc func(name string) (Hook, error) - -// The function type used to lookup PostProcessor implementations. -type PostProcessorFunc func(name string) (PostProcessor, error) - -// The function type used to lookup Provisioner implementations. -type ProvisionerFunc func(name string) (Provisioner, error) - -// ComponentFinder is a struct that contains the various function -// pointers necessary to look up components of Packer such as builders, -// commands, etc. -type ComponentFinder struct { - Builder BuilderFunc - Hook HookFunc - PostProcessor PostProcessorFunc - Provisioner ProvisionerFunc -} - -// The environment interface provides access to the configuration and -// state of a single Packer run. -// -// It allows for things such as executing CLI commands, getting the -// list of available builders, and more. -type Environment interface { - Builder(string) (Builder, error) - Cache() Cache - Hook(string) (Hook, error) - PostProcessor(string) (PostProcessor, error) - Provisioner(string) (Provisioner, error) - Ui() Ui -} - -// An implementation of an Environment that represents the Packer core -// environment. -type coreEnvironment struct { - cache Cache - components ComponentFinder - ui Ui -} - -// This struct configures new environments. -type EnvironmentConfig struct { - Cache Cache - Components ComponentFinder - Ui Ui -} - -// DefaultEnvironmentConfig returns a default EnvironmentConfig that can -// be used to create a new enviroment with NewEnvironment with sane defaults. -func DefaultEnvironmentConfig() *EnvironmentConfig { - config := &EnvironmentConfig{} - config.Ui = &BasicUi{ - Reader: os.Stdin, - Writer: os.Stdout, - ErrorWriter: os.Stdout, - } - - return config -} - -// This creates a new environment -func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error) { - if config == nil { - err = errors.New("config must be given to initialize environment") - return - } - - env := &coreEnvironment{} - env.cache = config.Cache - env.components = config.Components - env.ui = config.Ui - - // We want to make sure the components have valid function pointers. - // If a function pointer was not given, we assume that the function - // will just return a nil component. - if env.components.Builder == nil { - env.components.Builder = func(string) (Builder, error) { return nil, nil } - } - - if env.components.Hook == nil { - env.components.Hook = func(string) (Hook, error) { return nil, nil } - } - - if env.components.PostProcessor == nil { - env.components.PostProcessor = func(string) (PostProcessor, error) { return nil, nil } - } - - if env.components.Provisioner == nil { - env.components.Provisioner = func(string) (Provisioner, error) { return nil, nil } - } - - // The default cache is just the system temporary directory - if env.cache == nil { - env.cache = &FileCache{CacheDir: os.TempDir()} - } - - resultEnv = env - return -} - -// Returns a builder of the given name that is registered with this -// environment. -func (e *coreEnvironment) Builder(name string) (b Builder, err error) { - b, err = e.components.Builder(name) - if err != nil { - return - } - - if b == nil { - err = fmt.Errorf("No builder returned for name: %s", name) - } - - return -} - -// Returns the cache for this environment -func (e *coreEnvironment) Cache() Cache { - return e.cache -} - -// Returns a hook of the given name that is registered with this -// environment. -func (e *coreEnvironment) Hook(name string) (h Hook, err error) { - h, err = e.components.Hook(name) - if err != nil { - return - } - - if h == nil { - err = fmt.Errorf("No hook returned for name: %s", name) - } - - return -} - -// Returns a PostProcessor for the given name that is registered with this -// environment. -func (e *coreEnvironment) PostProcessor(name string) (p PostProcessor, err error) { - p, err = e.components.PostProcessor(name) - if err != nil { - return - } - - if p == nil { - err = fmt.Errorf("No post processor found for name: %s", name) - } - - return -} - -// Returns a provisioner for the given name that is registered with this -// environment. -func (e *coreEnvironment) Provisioner(name string) (p Provisioner, err error) { - p, err = e.components.Provisioner(name) - if err != nil { - return - } - - if p == nil { - err = fmt.Errorf("No provisioner returned for name: %s", name) - } - - return -} - -// Returns the UI for the environment. The UI is the interface that should -// be used for all communication with the outside world. -func (e *coreEnvironment) Ui() Ui { - return e.ui -} diff --git a/packer/environment_test.go b/packer/environment_test.go deleted file mode 100644 index 80edab58e..000000000 --- a/packer/environment_test.go +++ /dev/null @@ -1,310 +0,0 @@ -package packer - -import ( - "bytes" - "errors" - "io/ioutil" - "log" - "os" - "testing" -) - -func init() { - // Disable log output for tests - log.SetOutput(ioutil.Discard) -} - -func testComponentFinder() *ComponentFinder { - builderFactory := func(n string) (Builder, error) { return new(MockBuilder), nil } - ppFactory := func(n string) (PostProcessor, error) { return new(TestPostProcessor), nil } - provFactory := func(n string) (Provisioner, error) { return new(MockProvisioner), nil } - return &ComponentFinder{ - Builder: builderFactory, - PostProcessor: ppFactory, - Provisioner: provFactory, - } -} - -func testEnvironment() Environment { - config := DefaultEnvironmentConfig() - config.Ui = &BasicUi{ - Reader: new(bytes.Buffer), - Writer: new(bytes.Buffer), - ErrorWriter: new(bytes.Buffer), - } - - env, err := NewEnvironment(config) - if err != nil { - panic(err) - } - - return env -} - -func TestEnvironment_DefaultConfig_Ui(t *testing.T) { - config := DefaultEnvironmentConfig() - if config.Ui == nil { - t.Fatal("config.Ui should not be nil") - } - - rwUi, ok := config.Ui.(*BasicUi) - if !ok { - t.Fatal("default UI should be BasicUi") - } - if rwUi.Writer != os.Stdout { - t.Fatal("default UI should go to stdout") - } - if rwUi.Reader != os.Stdin { - t.Fatal("default UI reader should go to stdin") - } -} - -func TestNewEnvironment_NoConfig(t *testing.T) { - env, err := NewEnvironment(nil) - if env != nil { - t.Fatal("env should be nil") - } - if err == nil { - t.Fatal("should have error") - } -} - -func TestEnvironment_NilComponents(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components = *new(ComponentFinder) - - env, err := NewEnvironment(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - // All of these should not cause panics... so we don't assert - // anything but if there is a panic in the test then yeah, something - // went wrong. - env.Builder("foo") - env.Hook("foo") - env.PostProcessor("foo") - env.Provisioner("foo") -} - -func TestEnvironment_Builder(t *testing.T) { - builder := &MockBuilder{} - builders := make(map[string]Builder) - builders["foo"] = builder - - config := DefaultEnvironmentConfig() - config.Components.Builder = func(n string) (Builder, error) { return builders[n], nil } - - env, _ := NewEnvironment(config) - returnedBuilder, err := env.Builder("foo") - if err != nil { - t.Fatalf("err: %s", err) - } - if returnedBuilder != builder { - t.Fatalf("bad: %#v", returnedBuilder) - } -} - -func TestEnvironment_Builder_NilError(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Builder = func(n string) (Builder, error) { return nil, nil } - - env, _ := NewEnvironment(config) - returnedBuilder, err := env.Builder("foo") - if err == nil { - t.Fatal("should have error") - } - if returnedBuilder != nil { - t.Fatalf("bad: %#v", returnedBuilder) - } -} - -func TestEnvironment_Builder_Error(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Builder = func(n string) (Builder, error) { return nil, errors.New("foo") } - - env, _ := NewEnvironment(config) - returnedBuilder, err := env.Builder("foo") - if err == nil { - t.Fatal("should have error") - } - if err.Error() != "foo" { - t.Fatalf("bad err: %s", err) - } - if returnedBuilder != nil { - t.Fatalf("should be nil: %#v", returnedBuilder) - } -} - -func TestEnvironment_Cache(t *testing.T) { - config := DefaultEnvironmentConfig() - env, _ := NewEnvironment(config) - if env.Cache() == nil { - t.Fatal("cache should not be nil") - } -} - -func TestEnvironment_Hook(t *testing.T) { - hook := &MockHook{} - hooks := make(map[string]Hook) - hooks["foo"] = hook - - config := DefaultEnvironmentConfig() - config.Components.Hook = func(n string) (Hook, error) { return hooks[n], nil } - - env, _ := NewEnvironment(config) - returned, err := env.Hook("foo") - if err != nil { - t.Fatalf("err: %s", err) - } - if returned != hook { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_Hook_NilError(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Hook = func(n string) (Hook, error) { return nil, nil } - - env, _ := NewEnvironment(config) - returned, err := env.Hook("foo") - if err == nil { - t.Fatal("should have error") - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_Hook_Error(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Hook = func(n string) (Hook, error) { return nil, errors.New("foo") } - - env, _ := NewEnvironment(config) - returned, err := env.Hook("foo") - if err == nil { - t.Fatal("should have error") - } - if err.Error() != "foo" { - t.Fatalf("err: %s", err) - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_PostProcessor(t *testing.T) { - pp := &TestPostProcessor{} - pps := make(map[string]PostProcessor) - pps["foo"] = pp - - config := DefaultEnvironmentConfig() - config.Components.PostProcessor = func(n string) (PostProcessor, error) { return pps[n], nil } - - env, _ := NewEnvironment(config) - returned, err := env.PostProcessor("foo") - if err != nil { - t.Fatalf("err: %s", err) - } - if returned != pp { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_PostProcessor_NilError(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.PostProcessor = func(n string) (PostProcessor, error) { return nil, nil } - - env, _ := NewEnvironment(config) - returned, err := env.PostProcessor("foo") - if err == nil { - t.Fatal("should have error") - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_PostProcessor_Error(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.PostProcessor = func(n string) (PostProcessor, error) { return nil, errors.New("foo") } - - env, _ := NewEnvironment(config) - returned, err := env.PostProcessor("foo") - if err == nil { - t.Fatal("should be an error") - } - if err.Error() != "foo" { - t.Fatalf("bad err: %s", err) - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironmentProvisioner(t *testing.T) { - p := &MockProvisioner{} - ps := make(map[string]Provisioner) - ps["foo"] = p - - config := DefaultEnvironmentConfig() - config.Components.Provisioner = func(n string) (Provisioner, error) { return ps[n], nil } - - env, _ := NewEnvironment(config) - returned, err := env.Provisioner("foo") - if err != nil { - t.Fatalf("err: %s", err) - } - if returned != p { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironmentProvisioner_NilError(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Provisioner = func(n string) (Provisioner, error) { return nil, nil } - - env, _ := NewEnvironment(config) - returned, err := env.Provisioner("foo") - if err == nil { - t.Fatal("should have error") - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironmentProvisioner_Error(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Provisioner = func(n string) (Provisioner, error) { - return nil, errors.New("foo") - } - - env, _ := NewEnvironment(config) - returned, err := env.Provisioner("foo") - if err == nil { - t.Fatal("should have error") - } - if err.Error() != "foo" { - t.Fatalf("err: %s", err) - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_SettingUi(t *testing.T) { - ui := &BasicUi{ - Reader: new(bytes.Buffer), - Writer: new(bytes.Buffer), - } - - config := &EnvironmentConfig{} - config.Ui = ui - - env, _ := NewEnvironment(config) - - if env.Ui() != ui { - t.Fatalf("UI should be equal: %#v", env.Ui()) - } -} diff --git a/packer/rpc/client.go b/packer/rpc/client.go index 0e0140028..2f682f47a 100644 --- a/packer/rpc/client.go +++ b/packer/rpc/client.go @@ -100,13 +100,6 @@ func (c *Client) Communicator() packer.Communicator { } } -func (c *Client) Environment() packer.Environment { - return &Environment{ - client: c.client, - mux: c.mux, - } -} - func (c *Client) Hook() packer.Hook { return &hook{ client: c.client, diff --git a/packer/rpc/environment.go b/packer/rpc/environment.go deleted file mode 100644 index 4e2b73da8..000000000 --- a/packer/rpc/environment.go +++ /dev/null @@ -1,178 +0,0 @@ -package rpc - -import ( - "github.com/mitchellh/packer/packer" - "log" - "net/rpc" -) - -// A Environment is an implementation of the packer.Environment interface -// where the actual environment is executed over an RPC connection. -type Environment struct { - client *rpc.Client - mux *muxBroker -} - -// A EnvironmentServer wraps a packer.Environment and makes it exportable -// as part of a Golang RPC server. -type EnvironmentServer struct { - env packer.Environment - mux *muxBroker -} - -func (e *Environment) Builder(name string) (b packer.Builder, err error) { - var streamId uint32 - err = e.client.Call("Environment.Builder", name, &streamId) - if err != nil { - return - } - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - return nil, err - } - b = client.Builder() - return -} - -func (e *Environment) Cache() packer.Cache { - var streamId uint32 - if err := e.client.Call("Environment.Cache", new(interface{}), &streamId); err != nil { - panic(err) - } - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - log.Printf("[ERR] Error getting cache client: %s", err) - return nil - } - return client.Cache() -} - -func (e *Environment) Hook(name string) (h packer.Hook, err error) { - var streamId uint32 - err = e.client.Call("Environment.Hook", name, &streamId) - if err != nil { - return - } - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - return nil, err - } - return client.Hook(), nil -} - -func (e *Environment) PostProcessor(name string) (p packer.PostProcessor, err error) { - var streamId uint32 - err = e.client.Call("Environment.PostProcessor", name, &streamId) - if err != nil { - return - } - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - return nil, err - } - p = client.PostProcessor() - return -} - -func (e *Environment) Provisioner(name string) (p packer.Provisioner, err error) { - var streamId uint32 - err = e.client.Call("Environment.Provisioner", name, &streamId) - if err != nil { - return - } - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - return nil, err - } - p = client.Provisioner() - return -} - -func (e *Environment) Ui() packer.Ui { - var streamId uint32 - e.client.Call("Environment.Ui", new(interface{}), &streamId) - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - log.Printf("[ERR] Error connecting to Ui: %s", err) - return nil - } - return client.Ui() -} - -func (e *EnvironmentServer) Builder(name string, reply *uint32) error { - builder, err := e.env.Builder(name) - if err != nil { - return NewBasicError(err) - } - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterBuilder(builder) - go server.Serve() - return nil -} - -func (e *EnvironmentServer) Cache(args *interface{}, reply *uint32) error { - cache := e.env.Cache() - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterCache(cache) - go server.Serve() - return nil -} - -func (e *EnvironmentServer) Hook(name string, reply *uint32) error { - hook, err := e.env.Hook(name) - if err != nil { - return NewBasicError(err) - } - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterHook(hook) - go server.Serve() - return nil -} - -func (e *EnvironmentServer) PostProcessor(name string, reply *uint32) error { - pp, err := e.env.PostProcessor(name) - if err != nil { - return NewBasicError(err) - } - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterPostProcessor(pp) - go server.Serve() - return nil -} - -func (e *EnvironmentServer) Provisioner(name string, reply *uint32) error { - prov, err := e.env.Provisioner(name) - if err != nil { - return NewBasicError(err) - } - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterProvisioner(prov) - go server.Serve() - return nil -} - -func (e *EnvironmentServer) Ui(args *interface{}, reply *uint32) error { - ui := e.env.Ui() - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterUi(ui) - go server.Serve() - return nil -} diff --git a/packer/rpc/environment_test.go b/packer/rpc/environment_test.go deleted file mode 100644 index a5085d0ef..000000000 --- a/packer/rpc/environment_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package rpc - -import ( - "github.com/mitchellh/packer/packer" - "testing" -) - -var testEnvBuilder = &packer.MockBuilder{} -var testEnvCache = &testCache{} -var testEnvUi = &testUi{} - -type testEnvironment struct { - builderCalled bool - builderName string - cliCalled bool - cliArgs []string - hookCalled bool - hookName string - ppCalled bool - ppName string - provCalled bool - provName string - uiCalled bool -} - -func (e *testEnvironment) Builder(name string) (packer.Builder, error) { - e.builderCalled = true - e.builderName = name - return testEnvBuilder, nil -} - -func (e *testEnvironment) Cache() packer.Cache { - return testEnvCache -} - -func (e *testEnvironment) Cli(args []string) (int, error) { - e.cliCalled = true - e.cliArgs = args - return 42, nil -} - -func (e *testEnvironment) Hook(name string) (packer.Hook, error) { - e.hookCalled = true - e.hookName = name - return nil, nil -} - -func (e *testEnvironment) PostProcessor(name string) (packer.PostProcessor, error) { - e.ppCalled = true - e.ppName = name - return nil, nil -} - -func (e *testEnvironment) Provisioner(name string) (packer.Provisioner, error) { - e.provCalled = true - e.provName = name - return nil, nil -} - -func (e *testEnvironment) Ui() packer.Ui { - e.uiCalled = true - return testEnvUi -} - -func TestEnvironmentRPC(t *testing.T) { - // Create the interface to test - e := &testEnvironment{} - - // Start the server - client, server := testClientServer(t) - defer client.Close() - defer server.Close() - server.RegisterEnvironment(e) - eClient := client.Environment() - - // Test Builder - builder, _ := eClient.Builder("foo") - if !e.builderCalled { - t.Fatal("builder should be called") - } - if e.builderName != "foo" { - t.Fatalf("bad: %#v", e.builderName) - } - - builder.Prepare(nil) - if !testEnvBuilder.PrepareCalled { - t.Fatal("should be called") - } - - // Test Cache - cache := eClient.Cache() - cache.Lock("foo") - if !testEnvCache.lockCalled { - t.Fatal("should be called") - } - - // Test Provisioner - _, _ = eClient.Provisioner("foo") - if !e.provCalled { - t.Fatal("should be called") - } - if e.provName != "foo" { - t.Fatalf("bad: %s", e.provName) - } - - // Test Ui - ui := eClient.Ui() - if !e.uiCalled { - t.Fatal("should be called") - } - - // Test calls on the Ui - ui.Say("format") - if !testEnvUi.sayCalled { - t.Fatal("should be called") - } - if testEnvUi.sayMessage != "format" { - t.Fatalf("bad: %#v", testEnvUi.sayMessage) - } -} - -func TestEnvironment_ImplementsEnvironment(t *testing.T) { - var _ packer.Environment = new(Environment) -} diff --git a/packer/rpc/server.go b/packer/rpc/server.go index 1f3e7eef2..b6d17dacf 100644 --- a/packer/rpc/server.go +++ b/packer/rpc/server.go @@ -19,7 +19,6 @@ const ( DefaultCacheEndpoint = "Cache" DefaultCommandEndpoint = "Command" DefaultCommunicatorEndpoint = "Communicator" - DefaultEnvironmentEndpoint = "Environment" DefaultHookEndpoint = "Hook" DefaultPostProcessorEndpoint = "PostProcessor" DefaultProvisionerEndpoint = "Provisioner" @@ -95,13 +94,6 @@ func (s *Server) RegisterCommunicator(c packer.Communicator) { }) } -func (s *Server) RegisterEnvironment(b packer.Environment) { - s.server.RegisterName(DefaultEnvironmentEndpoint, &EnvironmentServer{ - env: b, - mux: s.mux, - }) -} - func (s *Server) RegisterHook(h packer.Hook) { s.server.RegisterName(DefaultHookEndpoint, &HookServer{ hook: h, diff --git a/signal.go b/signal.go index b198558d7..e63dd2fe5 100644 --- a/signal.go +++ b/signal.go @@ -10,7 +10,7 @@ import ( // Prepares the signal handlers so that we handle interrupts properly. // The signal handler exists in a goroutine. -func setupSignalHandlers(env packer.Environment) { +func setupSignalHandlers(ui packer.Ui) { ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt) @@ -20,13 +20,13 @@ func setupSignalHandlers(env packer.Environment) { <-ch log.Println("First interrupt. Ignoring to allow plugins to clean up.") - env.Ui().Error("Interrupt signal received. Cleaning up...") + ui.Error("Interrupt signal received. Cleaning up...") // Second interrupt. Go down hard. <-ch log.Println("Second interrupt. Exiting now.") - env.Ui().Error("Interrupt signal received twice. Forcefully exiting now.") + ui.Error("Interrupt signal received twice. Forcefully exiting now.") // Force kill all the plugins, but mark that we're killing them // first so that we don't get panics everywhere. From 547d9e759e5695496c0d2e17cfa6fe31918562e3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 25 May 2015 17:58:59 -0700 Subject: [PATCH 25/39] packer: test Build --- packer/core_test.go | 39 +++++++++++++++++++++++++++ packer/test-fixtures/build-basic.json | 5 ++++ packer/testing.go | 16 +++++++++++ 3 files changed, 60 insertions(+) create mode 100644 packer/test-fixtures/build-basic.json diff --git a/packer/core_test.go b/packer/core_test.go index d66a7786e..5935b1407 100644 --- a/packer/core_test.go +++ b/packer/core_test.go @@ -48,6 +48,36 @@ func TestCoreBuildNames(t *testing.T) { } } +func TestCoreBuild_basic(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-basic.json")) + b := TestBuilder(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } +} + func TestCoreValidate(t *testing.T) { cases := []struct { File string @@ -110,3 +140,12 @@ func testComponentFinder() *ComponentFinder { Provisioner: provFactory, } } + +func testCoreTemplate(t *testing.T, c *CoreConfig, p string) { + tpl, err := template.ParseFile(p) + if err != nil { + t.Fatalf("err: %s\n\n%s", p, err) + } + + c.Template = tpl +} diff --git a/packer/test-fixtures/build-basic.json b/packer/test-fixtures/build-basic.json new file mode 100644 index 000000000..d14f6cad3 --- /dev/null +++ b/packer/test-fixtures/build-basic.json @@ -0,0 +1,5 @@ +{ + "builders": [{ + "type": "test" + }] +} diff --git a/packer/testing.go b/packer/testing.go index 099119180..389b02f90 100644 --- a/packer/testing.go +++ b/packer/testing.go @@ -42,3 +42,19 @@ func TestCore(t *testing.T, c *CoreConfig) *Core { return core } + +// TestBuilder sets the builder with the name n to the component finder +// and returns the mock. +func TestBuilder(t *testing.T, c *CoreConfig, n string) *MockBuilder { + var b MockBuilder + + c.Components.Builder = func(actual string) (Builder, error) { + if actual != n { + return nil, nil + } + + return &b, nil + } + + return &b +} From c12072ecad14e0aee4c7524ca941f7afdb6d8e09 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 25 May 2015 18:15:07 -0700 Subject: [PATCH 26/39] packer: tests around interpolated names --- packer/core.go | 2 +- packer/core_test.go | 42 +++++++++++++++++++ .../build-basic-interpolated.json | 6 +++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 packer/test-fixtures/build-basic-interpolated.json diff --git a/packer/core.go b/packer/core.go index 4c4292ca7..cabf10359 100644 --- a/packer/core.go +++ b/packer/core.go @@ -103,7 +103,7 @@ func (c *Core) BuildNames() []string { // Build returns the Build object for the given name. func (c *Core) Build(n string) (Build, error) { // Setup the builder - configBuilder, ok := c.template.Builders[n] + configBuilder, ok := c.builds[n] if !ok { return nil, fmt.Errorf("no such build found: %s", n) } diff --git a/packer/core_test.go b/packer/core_test.go index 5935b1407..31ee34218 100644 --- a/packer/core_test.go +++ b/packer/core_test.go @@ -78,6 +78,48 @@ func TestCoreBuild_basic(t *testing.T) { } } +func TestCoreBuild_basicInterpolated(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-basic-interpolated.json")) + b := TestBuilder(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("NAME") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } +} + +func TestCoreBuild_nonExist(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-basic.json")) + TestBuilder(t, config, "test") + core := TestCore(t, config) + + _, err := core.Build("nope") + if err == nil { + t.Fatal("should error") + } +} + func TestCoreValidate(t *testing.T) { cases := []struct { File string diff --git a/packer/test-fixtures/build-basic-interpolated.json b/packer/test-fixtures/build-basic-interpolated.json new file mode 100644 index 000000000..c70677c52 --- /dev/null +++ b/packer/test-fixtures/build-basic-interpolated.json @@ -0,0 +1,6 @@ +{ + "builders": [{ + "name": "{{upper `name`}}", + "type": "test" + }] +} From b5f4ffa56c3d369d22cf321ef0371fc2d03c67cb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:07:16 -0700 Subject: [PATCH 27/39] template: OnlyExcept skipping --- template/template.go | 25 +++++++++++++++++++++++ template/template_test.go | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/template/template.go b/template/template.go index 17d808029..52f50089f 100644 --- a/template/template.go +++ b/template/template.go @@ -125,6 +125,31 @@ func (t *Template) Validate() error { return err } +// Skip says whether or not to skip the build with the given name. +func (o *OnlyExcept) Skip(n string) bool { + if len(o.Only) > 0 { + for _, v := range o.Only { + if v == n { + return false + } + } + + return true + } + + if len(o.Except) > 0 { + for _, v := range o.Except { + if v == n { + return true + } + } + + return false + } + + return false +} + // Validate validates that the OnlyExcept settings are correct for a thing. func (o *OnlyExcept) Validate(t *Template) error { if len(o.Only) > 0 && len(o.Except) > 0 { diff --git a/template/template_test.go b/template/template_test.go index d14682728..6fa39ab88 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -92,3 +92,46 @@ func TestTemplateValidate(t *testing.T) { } } } + +func TestOnlyExceptSkip(t *testing.T) { + cases := []struct { + Only, Except []string + Input string + Result bool + }{ + { + []string{"foo"}, + nil, + "foo", + false, + }, + + { + nil, + []string{"foo"}, + "foo", + true, + }, + + { + nil, + nil, + "foo", + false, + }, + } + + for _, tc := range cases { + oe := &OnlyExcept{ + Only: tc.Only, + Except: tc.Except, + } + + actual := oe.Skip(tc.Input) + if actual != tc.Result { + t.Fatalf( + "bad: %#v\n\n%#v\n\n%#v\n\n%#v", + actual, tc.Only, tc.Except, tc.Input) + } + } +} From b25ae21e13996f9f2bf3a5b5f1558d30c156c64a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:14:29 -0700 Subject: [PATCH 28/39] packer: run provisioners --- packer/builder_mock.go | 6 ++++ packer/core.go | 44 +++++++++++++++++++++++++++- packer/core_test.go | 34 +++++++++++++++++++++ packer/test-fixtures/build-prov.json | 9 ++++++ packer/testing.go | 16 ++++++++++ 5 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 packer/test-fixtures/build-prov.json diff --git a/packer/builder_mock.go b/packer/builder_mock.go index bfa0a0e47..9cb016963 100644 --- a/packer/builder_mock.go +++ b/packer/builder_mock.go @@ -42,6 +42,12 @@ func (tb *MockBuilder) Run(ui Ui, h Hook, c Cache) (Artifact, error) { return nil, nil } + if h != nil { + if err := h.Run(HookProvision, ui, nil, nil); err != nil { + return nil, err + } + } + return &MockArtifact{ IdValue: tb.ArtifactId, }, nil diff --git a/packer/core.go b/packer/core.go index cabf10359..4a0bf6a4d 100644 --- a/packer/core.go +++ b/packer/core.go @@ -118,13 +118,55 @@ func (c *Core) Build(n string) (Build, error) { "builder type not found: %s", configBuilder.Type) } - // TODO: template process name + // rawName is the uninterpolated name that we use for various lookups + rawName := configBuilder.Name + + // Setup the provisioners for this build + provisioners := make([]coreBuildProvisioner, 0, len(c.template.Provisioners)) + for _, rawP := range c.template.Provisioners { + // If we're skipping this, then ignore it + if rawP.Skip(rawName) { + continue + } + + // Get the provisioner + provisioner, err := c.components.Provisioner(rawP.Type) + if err != nil { + return nil, fmt.Errorf( + "error initializing provisioner '%s': %s", + rawP.Type, err) + } + if provisioner == nil { + return nil, fmt.Errorf( + "provisioner type not found: %s", rawP.Type) + } + + // Get the configuration + config := make([]interface{}, 1, 2) + config[0] = rawP.Config + + // TODO override + + // If we're pausing, we wrap the provisioner in a special pauser. + if rawP.PauseBefore > 0 { + provisioner = &PausedProvisioner{ + PauseBefore: rawP.PauseBefore, + Provisioner: provisioner, + } + } + + provisioners = append(provisioners, coreBuildProvisioner{ + provisioner: provisioner, + config: config, + }) + } return &coreBuild{ name: n, builder: builder, builderConfig: configBuilder.Config, builderType: configBuilder.Type, + provisioners: provisioners, variables: c.variables, }, nil } diff --git a/packer/core_test.go b/packer/core_test.go index 31ee34218..c8cdfbfb8 100644 --- a/packer/core_test.go +++ b/packer/core_test.go @@ -120,6 +120,40 @@ func TestCoreBuild_nonExist(t *testing.T) { } } +func TestCoreBuild_prov(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-prov.json")) + b := TestBuilder(t, config, "test") + p := TestProvisioner(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } + if !p.ProvCalled { + t.Fatal("provisioner not called") + } +} + func TestCoreValidate(t *testing.T) { cases := []struct { File string diff --git a/packer/test-fixtures/build-prov.json b/packer/test-fixtures/build-prov.json new file mode 100644 index 000000000..332c28b1d --- /dev/null +++ b/packer/test-fixtures/build-prov.json @@ -0,0 +1,9 @@ +{ + "builders": [{ + "type": "test" + }], + + "provisioners": [{ + "type": "test" + }] +} diff --git a/packer/testing.go b/packer/testing.go index 389b02f90..30b95c6e4 100644 --- a/packer/testing.go +++ b/packer/testing.go @@ -58,3 +58,19 @@ func TestBuilder(t *testing.T, c *CoreConfig, n string) *MockBuilder { return &b } + +// TestProvisioner sets the prov. with the name n to the component finder +// and returns the mock. +func TestProvisioner(t *testing.T, c *CoreConfig, n string) *MockProvisioner { + var b MockProvisioner + + c.Components.Provisioner = func(actual string) (Provisioner, error) { + if actual != n { + return nil, nil + } + + return &b, nil + } + + return &b +} From 85e615bbe22e22613807c764d1c0e0e637f3b28f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:16:39 -0700 Subject: [PATCH 29/39] packer: a lot more provisioner tests --- packer/core_test.go | 68 +++++++++++++++++++ .../build-prov-skip-include.json | 10 +++ packer/test-fixtures/build-prov-skip.json | 10 +++ 3 files changed, 88 insertions(+) create mode 100644 packer/test-fixtures/build-prov-skip-include.json create mode 100644 packer/test-fixtures/build-prov-skip.json diff --git a/packer/core_test.go b/packer/core_test.go index c8cdfbfb8..5ef96dc96 100644 --- a/packer/core_test.go +++ b/packer/core_test.go @@ -154,6 +154,74 @@ func TestCoreBuild_prov(t *testing.T) { } } +func TestCoreBuild_provSkip(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-prov-skip.json")) + b := TestBuilder(t, config, "test") + p := TestProvisioner(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } + if p.ProvCalled { + t.Fatal("provisioner should not be called") + } +} + +func TestCoreBuild_provSkipInclude(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-prov-skip-include.json")) + b := TestBuilder(t, config, "test") + p := TestProvisioner(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } + if !p.ProvCalled { + t.Fatal("provisioner should be called") + } +} + func TestCoreValidate(t *testing.T) { cases := []struct { File string diff --git a/packer/test-fixtures/build-prov-skip-include.json b/packer/test-fixtures/build-prov-skip-include.json new file mode 100644 index 000000000..2ba5e77de --- /dev/null +++ b/packer/test-fixtures/build-prov-skip-include.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "test" + }], + + "provisioners": [{ + "type": "test", + "only": ["test"] + }] +} diff --git a/packer/test-fixtures/build-prov-skip.json b/packer/test-fixtures/build-prov-skip.json new file mode 100644 index 000000000..bd9fa5072 --- /dev/null +++ b/packer/test-fixtures/build-prov-skip.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "test" + }], + + "provisioners": [{ + "type": "test", + "only": ["foo"] + }] +} From 26c7ac2d9046459b2ea89ff9874536228f9cdead Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:28:59 -0700 Subject: [PATCH 30/39] packer: post-processors --- packer/build_test.go | 30 ++++++++--------- packer/core.go | 53 ++++++++++++++++++++++++++---- packer/core_test.go | 38 ++++++++++++++++++++- packer/post_processor_mock.go | 33 +++++++++++++++++++ packer/post_processor_test.go | 24 -------------- packer/template_test.go | 4 +-- packer/test-fixtures/build-pp.json | 7 ++++ packer/testing.go | 35 +++++++++++++++----- 8 files changed, 167 insertions(+), 57 deletions(-) create mode 100644 packer/post_processor_mock.go delete mode 100644 packer/post_processor_test.go create mode 100644 packer/test-fixtures/build-pp.json diff --git a/packer/build_test.go b/packer/build_test.go index 5a073d39c..4f93e03a5 100644 --- a/packer/build_test.go +++ b/packer/build_test.go @@ -19,7 +19,7 @@ func testBuild() *coreBuild { }, postProcessors: [][]coreBuildPostProcessor{ []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp"}, "testPP", make(map[string]interface{}), true}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp"}, "testPP", make(map[string]interface{}), true}, }, }, variables: make(map[string]string), @@ -66,12 +66,12 @@ func TestBuild_Prepare(t *testing.T) { } corePP := build.postProcessors[0][0] - pp := corePP.processor.(*TestPostProcessor) - if !pp.configCalled { + pp := corePP.processor.(*MockPostProcessor) + if !pp.ConfigureCalled { t.Fatal("should be called") } - if !reflect.DeepEqual(pp.configVal, []interface{}{make(map[string]interface{}), packerConfig}) { - t.Fatalf("bad: %#v", pp.configVal) + if !reflect.DeepEqual(pp.ConfigureConfigs, []interface{}{make(map[string]interface{}), packerConfig}) { + t.Fatalf("bad: %#v", pp.ConfigureConfigs) } } @@ -208,8 +208,8 @@ func TestBuild_Run(t *testing.T) { } // Verify post-processor was run - pp := build.postProcessors[0][0].processor.(*TestPostProcessor) - if !pp.ppCalled { + pp := build.postProcessors[0][0].processor.(*MockPostProcessor) + if !pp.PostProcessCalled { t.Fatal("should be called") } } @@ -244,7 +244,7 @@ func TestBuild_Run_Artifacts(t *testing.T) { build = testBuild() build.postProcessors = [][]coreBuildPostProcessor{ []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp"}, "pp", make(map[string]interface{}), false}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp"}, "pp", make(map[string]interface{}), false}, }, } @@ -269,10 +269,10 @@ func TestBuild_Run_Artifacts(t *testing.T) { build = testBuild() build.postProcessors = [][]coreBuildPostProcessor{ []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1"}, "pp", make(map[string]interface{}), false}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp1"}, "pp", make(map[string]interface{}), false}, }, []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2"}, "pp", make(map[string]interface{}), true}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp2"}, "pp", make(map[string]interface{}), true}, }, } @@ -297,12 +297,12 @@ func TestBuild_Run_Artifacts(t *testing.T) { build = testBuild() build.postProcessors = [][]coreBuildPostProcessor{ []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1a"}, "pp", make(map[string]interface{}), false}, - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1b"}, "pp", make(map[string]interface{}), true}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp1a"}, "pp", make(map[string]interface{}), false}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp1b"}, "pp", make(map[string]interface{}), true}, }, []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2a"}, "pp", make(map[string]interface{}), false}, - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2b"}, "pp", make(map[string]interface{}), false}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp2a"}, "pp", make(map[string]interface{}), false}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp2b"}, "pp", make(map[string]interface{}), false}, }, } @@ -328,7 +328,7 @@ func TestBuild_Run_Artifacts(t *testing.T) { build.postProcessors = [][]coreBuildPostProcessor{ []coreBuildPostProcessor{ coreBuildPostProcessor{ - &TestPostProcessor{artifactId: "pp", keep: true}, "pp", make(map[string]interface{}), false, + &MockPostProcessor{ArtifactId: "pp", Keep: true}, "pp", make(map[string]interface{}), false, }, }, } diff --git a/packer/core.go b/packer/core.go index 4a0bf6a4d..a372c7ee8 100644 --- a/packer/core.go +++ b/packer/core.go @@ -161,13 +161,54 @@ func (c *Core) Build(n string) (Build, error) { }) } + // Setup the post-processors + postProcessors := make([][]coreBuildPostProcessor, 0, len(c.template.PostProcessors)) + for _, rawPs := range c.template.PostProcessors { + current := make([]coreBuildPostProcessor, 0, len(rawPs)) + for _, rawP := range rawPs { + // If we skip, ignore + if rawP.Skip(rawName) { + continue + } + + // Get the post-processor + postProcessor, err := c.components.PostProcessor(rawP.Type) + if err != nil { + return nil, fmt.Errorf( + "error initializing post-processor '%s': %s", + rawP.Type, err) + } + if postProcessor == nil { + return nil, fmt.Errorf( + "post-processor type not found: %s", rawP.Type) + } + + current = append(current, coreBuildPostProcessor{ + processor: postProcessor, + processorType: rawP.Type, + config: rawP.Config, + keepInputArtifact: rawP.KeepInputArtifact, + }) + } + + // If we have no post-processors in this chain, just continue. + if len(current) == 0 { + continue + } + + postProcessors = append(postProcessors, current) + } + + // TODO hooks one day + return &coreBuild{ - name: n, - builder: builder, - builderConfig: configBuilder.Config, - builderType: configBuilder.Type, - provisioners: provisioners, - variables: c.variables, + name: n, + builder: builder, + builderConfig: configBuilder.Config, + builderType: configBuilder.Type, + postProcessors: postProcessors, + provisioners: provisioners, + variables: c.variables, }, nil } diff --git a/packer/core_test.go b/packer/core_test.go index 5ef96dc96..712694766 100644 --- a/packer/core_test.go +++ b/packer/core_test.go @@ -222,6 +222,42 @@ func TestCoreBuild_provSkipInclude(t *testing.T) { } } +func TestCoreBuild_postProcess(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-pp.json")) + b := TestBuilder(t, config, "test") + p := TestPostProcessor(t, config, "test") + core := TestCore(t, config) + ui := TestUi(t) + + b.ArtifactId = "hello" + p.ArtifactId = "goodbye" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(ui, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != p.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } + if p.PostProcessArtifact.Id() != b.ArtifactId { + t.Fatalf("bad: %s", p.PostProcessArtifact.Id()) + } +} + func TestCoreValidate(t *testing.T) { cases := []struct { File string @@ -276,7 +312,7 @@ func TestCoreValidate(t *testing.T) { func testComponentFinder() *ComponentFinder { builderFactory := func(n string) (Builder, error) { return new(MockBuilder), nil } - ppFactory := func(n string) (PostProcessor, error) { return new(TestPostProcessor), nil } + ppFactory := func(n string) (PostProcessor, error) { return new(MockPostProcessor), nil } provFactory := func(n string) (Provisioner, error) { return new(MockProvisioner), nil } return &ComponentFinder{ Builder: builderFactory, diff --git a/packer/post_processor_mock.go b/packer/post_processor_mock.go new file mode 100644 index 000000000..591e4b876 --- /dev/null +++ b/packer/post_processor_mock.go @@ -0,0 +1,33 @@ +package packer + +// MockPostProcessor is an implementation of PostProcessor that can be +// used for tests. +type MockPostProcessor struct { + ArtifactId string + Keep bool + Error error + + ConfigureCalled bool + ConfigureConfigs []interface{} + ConfigureError error + + PostProcessCalled bool + PostProcessArtifact Artifact + PostProcessUi Ui +} + +func (t *MockPostProcessor) Configure(configs ...interface{}) error { + t.ConfigureCalled = true + t.ConfigureConfigs = configs + return t.ConfigureError +} + +func (t *MockPostProcessor) PostProcess(ui Ui, a Artifact) (Artifact, bool, error) { + t.PostProcessCalled = true + t.PostProcessArtifact = a + t.PostProcessUi = ui + + return &MockArtifact{ + IdValue: t.ArtifactId, + }, t.Keep, t.Error +} diff --git a/packer/post_processor_test.go b/packer/post_processor_test.go deleted file mode 100644 index fa6dbdbf9..000000000 --- a/packer/post_processor_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package packer - -type TestPostProcessor struct { - artifactId string - keep bool - configCalled bool - configVal []interface{} - ppCalled bool - ppArtifact Artifact - ppUi Ui -} - -func (pp *TestPostProcessor) Configure(v ...interface{}) error { - pp.configCalled = true - pp.configVal = v - return nil -} - -func (pp *TestPostProcessor) PostProcess(ui Ui, a Artifact) (Artifact, bool, error) { - pp.ppCalled = true - pp.ppArtifact = a - pp.ppUi = ui - return &TestArtifact{id: pp.artifactId}, pp.keep, nil -} diff --git a/packer/template_test.go b/packer/template_test.go index b676672b1..2d0949376 100644 --- a/packer/template_test.go +++ b/packer/template_test.go @@ -11,7 +11,7 @@ import ( func testTemplateComponentFinder() *ComponentFinder { builder := new(MockBuilder) - pp := new(TestPostProcessor) + pp := new(MockPostProcessor) provisioner := &MockProvisioner{} builderMap := map[string]Builder{ @@ -1018,7 +1018,7 @@ func TestTemplate_Build(t *testing.T) { "test-prov": provisioner, } - pp := new(TestPostProcessor) + pp := new(MockPostProcessor) ppMap := map[string]PostProcessor{ "simple": pp, } diff --git a/packer/test-fixtures/build-pp.json b/packer/test-fixtures/build-pp.json new file mode 100644 index 000000000..e2b32cfac --- /dev/null +++ b/packer/test-fixtures/build-pp.json @@ -0,0 +1,7 @@ +{ + "builders": [{ + "type": "test" + }], + + "post-processors": ["test"] +} diff --git a/packer/testing.go b/packer/testing.go index 30b95c6e4..7e7ad0b53 100644 --- a/packer/testing.go +++ b/packer/testing.go @@ -8,14 +8,6 @@ import ( ) func TestCoreConfig(t *testing.T) *CoreConfig { - // Create a UI that is effectively /dev/null everywhere - var buf bytes.Buffer - ui := &BasicUi{ - Reader: &buf, - Writer: ioutil.Discard, - ErrorWriter: ioutil.Discard, - } - // Create some test components components := ComponentFinder{ Builder: func(n string) (Builder, error) { @@ -30,7 +22,7 @@ func TestCoreConfig(t *testing.T) *CoreConfig { return &CoreConfig{ Cache: &FileCache{CacheDir: os.TempDir()}, Components: components, - Ui: ui, + Ui: TestUi(t), } } @@ -43,6 +35,15 @@ func TestCore(t *testing.T, c *CoreConfig) *Core { return core } +func TestUi(t *testing.T) Ui { + var buf bytes.Buffer + return &BasicUi{ + Reader: &buf, + Writer: ioutil.Discard, + ErrorWriter: ioutil.Discard, + } +} + // TestBuilder sets the builder with the name n to the component finder // and returns the mock. func TestBuilder(t *testing.T, c *CoreConfig, n string) *MockBuilder { @@ -74,3 +75,19 @@ func TestProvisioner(t *testing.T, c *CoreConfig, n string) *MockProvisioner { return &b } + +// TestPostProcessor sets the prov. with the name n to the component finder +// and returns the mock. +func TestPostProcessor(t *testing.T, c *CoreConfig, n string) *MockPostProcessor { + var b MockPostProcessor + + c.Components.PostProcessor = func(actual string) (PostProcessor, error) { + if actual != n { + return nil, nil + } + + return &b, nil + } + + return &b +} From 2fb08be192fcd3cd73b8c9d55ec213ddd22478f1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:38:02 -0700 Subject: [PATCH 31/39] template: store Rawcontents --- template/parse.go | 9 +++++++++ template/parse_test.go | 3 +++ template/template.go | 3 +++ 3 files changed, 15 insertions(+) diff --git a/template/parse.go b/template/parse.go index a46adc594..5566e31cc 100644 --- a/template/parse.go +++ b/template/parse.go @@ -1,6 +1,7 @@ package template import ( + "bytes" "encoding/json" "fmt" "io" @@ -23,6 +24,8 @@ type rawTemplate struct { PostProcessors []interface{} `mapstructure:"post-processors"` Provisioners []map[string]interface{} Variables map[string]interface{} + + RawContents []byte } // Template returns the actual Template object built from this raw @@ -34,6 +37,7 @@ func (r *rawTemplate) Template() (*Template, error) { // Copy some literals result.Description = r.Description result.MinVersion = r.MinVersion + result.RawContents = r.RawContents // Gather the variables if len(r.Variables) > 0 { @@ -252,6 +256,10 @@ func (r *rawTemplate) parsePostProcessor( // Parse takes the given io.Reader and parses a Template object out of it. func Parse(r io.Reader) (*Template, error) { + // Create a buffer to copy what we read + var buf bytes.Buffer + r = io.TeeReader(r, &buf) + // First, decode the object into an interface{}. We do this instead of // the rawTemplate directly because we'd rather use mapstructure to // decode since it has richer errors. @@ -263,6 +271,7 @@ func Parse(r io.Reader) (*Template, error) { // Create our decoder var md mapstructure.Metadata var rawTpl rawTemplate + rawTpl.RawContents = buf.Bytes() decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ Metadata: &md, Result: &rawTpl, diff --git a/template/parse_test.go b/template/parse_test.go index 2cca68b88..d5d9fcd8e 100644 --- a/template/parse_test.go +++ b/template/parse_test.go @@ -276,6 +276,9 @@ func TestParse(t *testing.T) { t.Fatalf("err: %s", err) } + if tpl != nil { + tpl.RawContents = nil + } if !reflect.DeepEqual(tpl, tc.Result) { t.Fatalf("bad: %s\n\n%#v\n\n%#v", tc.File, tpl, tc.Result) } diff --git a/template/template.go b/template/template.go index 52f50089f..1ab3c668e 100644 --- a/template/template.go +++ b/template/template.go @@ -19,6 +19,9 @@ type Template struct { Provisioners []*Provisioner PostProcessors [][]*PostProcessor Push *Push + + // RawContents is just the raw data for this template + RawContents []byte } // Builder represents a builder configured in the template From 946f74588177fe605dde79c1601e5065dedc0f49 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:38:09 -0700 Subject: [PATCH 32/39] command: don't use packer.Template --- command/inspect.go | 11 +++++------ command/push.go | 10 +++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/command/inspect.go b/command/inspect.go index 2574615fb..f564b2689 100644 --- a/command/inspect.go +++ b/command/inspect.go @@ -2,10 +2,10 @@ package command import ( "fmt" - "github.com/mitchellh/packer/packer" - "log" "sort" "strings" + + "github.com/mitchellh/packer/template" ) type InspectCommand struct { @@ -13,7 +13,7 @@ type InspectCommand struct { } func (c *InspectCommand) Run(args []string) int { - flags := c.Meta.FlagSet("build", FlagSetNone) + flags := c.Meta.FlagSet("inspect", FlagSetNone) flags.Usage = func() { c.Ui.Say(c.Help()) } if err := flags.Parse(args); err != nil { return 1 @@ -25,9 +25,8 @@ func (c *InspectCommand) Run(args []string) int { return 1 } - // Read the file into a byte array so that we can parse the template - log.Printf("Reading template: %#v", args[0]) - tpl, err := packer.ParseTemplateFile(args[0], nil) + // Parse the template + tpl, err := template.ParseFile(args[0]) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 diff --git a/command/push.go b/command/push.go index ef0f42924..1c53d8508 100644 --- a/command/push.go +++ b/command/push.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/atlas-go/archive" "github.com/hashicorp/atlas-go/v1" - "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template" ) // archiveTemplateEntry is the name the template always takes within the slug. @@ -58,15 +58,15 @@ func (c *PushCommand) Run(args []string) int { "longer used. It will be removed in the next version.")) } - // Read the template - tpl, err := packer.ParseTemplateFile(args[0], nil) + // Parse the template + tpl, err := template.ParseFile(args[0]) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } // Validate some things - if tpl.Push.Name == "" { + if tpl.Push == nil || tpl.Push.Name == "" { c.Ui.Error(fmt.Sprintf( "The 'push' section must be specified in the template with\n" + "at least the 'name' option set.")) @@ -131,7 +131,7 @@ func (c *PushCommand) Run(args []string) int { } // Find the Atlas post-processors, if possible - var atlasPPs []packer.RawPostProcessorConfig + var atlasPPs []*template.PostProcessor for _, list := range tpl.PostProcessors { for _, pp := range list { if pp.Type == "atlas" { From 99a93009ed0ff4427b5c1a67aa729482a47e0c9f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:38:24 -0700 Subject: [PATCH 33/39] packer: remove Template --- packer/template.go | 734 --------------- packer/template_test.go | 1914 --------------------------------------- 2 files changed, 2648 deletions(-) delete mode 100644 packer/template.go delete mode 100644 packer/template_test.go diff --git a/packer/template.go b/packer/template.go deleted file mode 100644 index 717ae7682..000000000 --- a/packer/template.go +++ /dev/null @@ -1,734 +0,0 @@ -package packer - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "os" - "sort" - "text/template" - "time" - - "github.com/hashicorp/go-version" - "github.com/mitchellh/mapstructure" - jsonutil "github.com/mitchellh/packer/common/json" -) - -// 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 { - MinimumPackerVersion string `mapstructure:"min_packer_version"` - - Description string - Builders []map[string]interface{} - Hooks map[string][]string - Push PushConfig - PostProcessors []interface{} `mapstructure:"post-processors"` - Provisioners []map[string]interface{} - Variables map[string]interface{} -} - -// 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 { - RawContents []byte - Description string - Variables map[string]RawVariable - Builders map[string]RawBuilderConfig - Hooks map[string][]string - Push *PushConfig - PostProcessors [][]RawPostProcessorConfig - Provisioners []RawProvisionerConfig -} - -// PushConfig is the configuration structure for the push settings. -type PushConfig struct { - Name string - Address string - BaseDir string `mapstructure:"base_dir"` - Include []string - Exclude []string - Token string - VCS bool -} - -// 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{} -} - -// 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 { - TemplateOnlyExcept `mapstructure:",squash"` - - Type string - KeepInputArtifact bool `mapstructure:"keep_input_artifact"` - RawConfig map[string]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. -type RawProvisionerConfig struct { - TemplateOnlyExcept `mapstructure:",squash"` - - Type string - Override map[string]interface{} - RawPauseBefore string `mapstructure:"pause_before"` - - RawConfig interface{} - - pauseBefore time.Duration -} - -// RawVariable represents a variable configuration within a template. -type RawVariable struct { - Default string // The default value for this variable - Required bool // If the variable is required or not - Value string // The set value for this variable - HasValue bool // True if the value was set -} - -// ParseTemplate takes a byte slice and parses a Template from it, returning -// 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. -// -// The second parameter, vars, are the values for a set of user variables. -func ParseTemplate(data []byte, vars map[string]string) (t *Template, err error) { - var rawTplInterface interface{} - err = jsonutil.Unmarshal(data, &rawTplInterface) - if err != nil { - 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 - } - - if rawTpl.MinimumPackerVersion != "" { - // TODO: NOPE! Replace this - Version := "1.0" - vCur, err := version.NewVersion(Version) - if err != nil { - panic(err) - } - vReq, err := version.NewVersion(rawTpl.MinimumPackerVersion) - if err != nil { - return nil, fmt.Errorf( - "'minimum_packer_version' error: %s", err) - } - - if vCur.LessThan(vReq) { - return nil, fmt.Errorf( - "Template requires Packer version %s. "+ - "Running version is %s.", - vReq, vCur) - } - } - - 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.RawContents = data - t.Description = rawTpl.Description - t.Variables = make(map[string]RawVariable) - t.Builders = make(map[string]RawBuilderConfig) - t.Hooks = rawTpl.Hooks - t.Push = &rawTpl.Push - t.PostProcessors = make([][]RawPostProcessorConfig, len(rawTpl.PostProcessors)) - t.Provisioners = make([]RawProvisionerConfig, len(rawTpl.Provisioners)) - - // Gather all the variables - for k, v := range rawTpl.Variables { - var variable RawVariable - variable.Required = v == nil - - // Create a new mapstructure decoder in order to decode the default - // value since this is the only value in the regular template that - // can be weakly typed. - decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &variable.Default, - WeaklyTypedInput: true, - }) - if err != nil { - // This should never happen. - panic(err) - } - - err = decoder.Decode(v) - if err != nil { - errors = append(errors, - fmt.Errorf("Error decoding default value for user var '%s': %s", k, err)) - continue - } - - // Set the value of this variable if we have it - if val, ok := vars[k]; ok { - variable.HasValue = true - variable.Value = val - delete(vars, k) - } - - t.Variables[k] = variable - } - - // 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 == "" { - errors = append(errors, fmt.Errorf("builder %d: missing 'type'", i+1)) - continue - } - - // 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 - } - - // 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 - } - - // Now that we have the name, remove it from the config - as the builder - // itself doesn't know about, and it will cause a validation error. - delete(v, "name") - - raw.RawConfig = v - - 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 := parsePostProcessor(i, rawV) - if err != nil { - errors = append(errors, err...) - continue - } - - configs := make([]RawPostProcessorConfig, 0, len(rawPP)) - 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 - } - - // Remove the input keep_input_artifact option - config.TemplateOnlyExcept.Prune(pp) - delete(pp, "keep_input_artifact") - - // Verify that the only settings are good - if errs := config.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 { - for _, err := range errs { - errors = append(errors, - fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err)) - } - - continue - } - - config.RawConfig = pp - - // Add it to the list of configs - configs = append(configs, config) - } - - t.PostProcessors[i] = configs - } - - // 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 - } - - // Delete the keys that we used - raw.TemplateOnlyExcept.Prune(v) - delete(v, "override") - - // Verify that the override keys exist... - for name, _ := range raw.Override { - if _, ok := t.Builders[name]; !ok { - errors = append( - errors, fmt.Errorf("provisioner %d: build '%s' not found for override", i+1, name)) - } - } - - // Verify that the only settings are good - if errs := raw.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 { - for _, err := range errs { - errors = append(errors, - fmt.Errorf("provisioner %d: %s", i+1, err)) - } - } - - // Setup the pause settings - if raw.RawPauseBefore != "" { - duration, err := time.ParseDuration(raw.RawPauseBefore) - if err != nil { - errors = append( - errors, fmt.Errorf( - "provisioner %d: pause_before invalid: %s", - i+1, err)) - } - - raw.pauseBefore = duration - } - - // Remove the pause_before setting if it is there so that we don't - // get template validation errors later. - delete(v, "pause_before") - - raw.RawConfig = v - } - - if len(t.Builders) == 0 { - errors = append(errors, fmt.Errorf("No builders are defined in the template.")) - } - - // Verify that all the variable sets were for real variables. - for k, _ := range vars { - errors = append(errors, fmt.Errorf("Unknown user variables: %s", k)) - } - - // If there were errors, we put it into a MultiError and return - if len(errors) > 0 { - err = &MultiError{errors} - t = nil - return - } - - return -} - -// ParseTemplateFile takes the given template file and parses it into -// a single template. -func ParseTemplateFile(path string, vars map[string]string) (*Template, error) { - var data []byte - - if path == "-" { - // Read from stdin... - buf := new(bytes.Buffer) - _, err := io.Copy(buf, os.Stdin) - if err != nil { - return nil, err - } - - data = buf.Bytes() - } else { - var err error - data, err = ioutil.ReadFile(path) - if err != nil { - return nil, err - } - } - - return ParseTemplate(data, vars) -} - -func parsePostProcessor(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 { - names := make([]string, 0, len(t.Builders)) - for name, _ := range t.Builders { - names = append(names, name) - } - - 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 - 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") - } - - // 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 - } - - if builder == nil { - err = fmt.Errorf("Builder type not found: %s", builderConfig.Type) - return - } - - // Process the name - tpl, variables, err := t.NewConfigTemplate() - if err != nil { - return nil, err - } - - rawName := name - name, err = tpl.Process(name, nil) - if err != nil { - return nil, err - } - - // 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 post-processors - postProcessors := make([][]coreBuildPostProcessor, 0, len(t.PostProcessors)) - for _, rawPPs := range t.PostProcessors { - current := make([]coreBuildPostProcessor, 0, len(rawPPs)) - for _, rawPP := range rawPPs { - if rawPP.TemplateOnlyExcept.Skip(rawName) { - continue - } - - pp, err := components.PostProcessor(rawPP.Type) - if err != nil { - return nil, err - } - - if pp == nil { - return nil, fmt.Errorf("PostProcessor type not found: %s", rawPP.Type) - } - - current = append(current, coreBuildPostProcessor{ - processor: pp, - processorType: rawPP.Type, - config: rawPP.RawConfig, - keepInputArtifact: rawPP.KeepInputArtifact, - }) - } - - // If we have no post-processors in this chain, just continue. - // This can happen if the post-processors skip certain builds. - if len(current) == 0 { - continue - } - - postProcessors = append(postProcessors, current) - } - - // Prepare the provisioners - provisioners := make([]coreBuildProvisioner, 0, len(t.Provisioners)) - for _, rawProvisioner := range t.Provisioners { - if rawProvisioner.TemplateOnlyExcept.Skip(rawName) { - continue - } - - 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) - } - } - - if rawProvisioner.pauseBefore > 0 { - provisioner = &PausedProvisioner{ - PauseBefore: rawProvisioner.pauseBefore, - Provisioner: provisioner, - } - } - - coreProv := coreBuildProvisioner{provisioner, configs} - provisioners = append(provisioners, coreProv) - } - - b = &coreBuild{ - name: name, - builder: builder, - builderConfig: builderConfig.RawConfig, - builderType: builderConfig.Type, - hooks: hooks, - postProcessors: postProcessors, - provisioners: provisioners, - variables: variables, - } - - return -} - -//Build a ConfigTemplate object populated by the values within a -//parsed template -func (t *Template) NewConfigTemplate() (c *ConfigTemplate, variables map[string]string, err error) { - - // Prepare the variable template processor, which is a bit unique - // because we don't allow user variable usage and we add a function - // to read from the environment. - varTpl, err := NewConfigTemplate() - if err != nil { - return nil, nil, err - } - varTpl.Funcs(template.FuncMap{ - "env": templateEnv, - "user": templateDisableUser, - }) - - // Prepare the variables - var varErrors []error - variables = make(map[string]string) - for k, v := range t.Variables { - if v.Required && !v.HasValue { - varErrors = append(varErrors, - fmt.Errorf("Required user variable '%s' not set", k)) - } - - var val string - if v.HasValue { - val = v.Value - } else { - val, err = varTpl.Process(v.Default, nil) - if err != nil { - varErrors = append(varErrors, - fmt.Errorf("Error processing user variable '%s': %s'", k, err)) - } - } - - variables[k] = val - } - - if len(varErrors) > 0 { - return nil, variables, &MultiError{varErrors} - } - - // Process the name - tpl, err := NewConfigTemplate() - if err != nil { - return nil, variables, err - } - tpl.UserVars = variables - - return tpl, variables, nil -} - -// TemplateOnlyExcept contains the logic required for "only" and "except" -// meta-parameters. -type TemplateOnlyExcept struct { - Only []string - Except []string -} - -// Prune will prune out the used values from the raw map. -func (t *TemplateOnlyExcept) Prune(raw map[string]interface{}) { - delete(raw, "except") - delete(raw, "only") -} - -// Skip tests if we should skip putting this item onto a build. -func (t *TemplateOnlyExcept) Skip(name string) bool { - if len(t.Only) > 0 { - onlyFound := false - for _, n := range t.Only { - if n == name { - onlyFound = true - break - } - } - - if !onlyFound { - // Skip this provisioner - return true - } - } - - // If the name is in the except list, then skip that - for _, n := range t.Except { - if n == name { - return true - } - } - - return false -} - -// Validates the only/except parameters. -func (t *TemplateOnlyExcept) Validate(b map[string]RawBuilderConfig) (e []error) { - if len(t.Only) > 0 && len(t.Except) > 0 { - e = append(e, - fmt.Errorf("Only one of 'only' or 'except' may be specified.")) - } - - if len(t.Only) > 0 { - for _, n := range t.Only { - if _, ok := b[n]; !ok { - e = append(e, - fmt.Errorf("'only' specified builder '%s' not found", n)) - } - } - } - - for _, n := range t.Except { - if _, ok := b[n]; !ok { - e = append(e, - fmt.Errorf("'except' specified builder '%s' not found", n)) - } - } - - return -} diff --git a/packer/template_test.go b/packer/template_test.go deleted file mode 100644 index 2d0949376..000000000 --- a/packer/template_test.go +++ /dev/null @@ -1,1914 +0,0 @@ -package packer - -import ( - "io/ioutil" - "os" - "reflect" - "sort" - "testing" - "time" -) - -func testTemplateComponentFinder() *ComponentFinder { - builder := new(MockBuilder) - pp := new(MockPostProcessor) - 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(), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(result.Builders) != 1 { - t.Fatalf("bad: %#v", result.Builders) - } - - if string(result.RawContents) != data { - t.Fatalf("expected %q to be %q", result.RawContents, data) - } -} - -func TestParseTemplateFile_minPackerVersionBad(t *testing.T) { - data := ` - { - "min_packer_version": "27.0.0", - "builders": [{"type": "something"}] - } - ` - - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - tf.Write([]byte(data)) - tf.Close() - - _, err = ParseTemplateFile(tf.Name(), nil) - if err == nil { - t.Fatal("expects error") - } -} - -func TestParseTemplateFile_minPackerVersionFormat(t *testing.T) { - data := ` - { - "min_packer_version": "NOPE NOPE NOPE", - "builders": [{"type": "something"}] - } - ` - - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - tf.Write([]byte(data)) - tf.Close() - - _, err = ParseTemplateFile(tf.Name(), nil) - if err == nil { - t.Fatal("expects error") - } -} - -func TestParseTemplateFile_minPackerVersionGood(t *testing.T) { - data := ` - { - "min_packer_version": "0.1", - "builders": [{"type": "something"}] - } - ` - - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - tf.Write([]byte(data)) - tf.Close() - - _, err = ParseTemplateFile(tf.Name(), nil) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -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("-", nil) - 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), nil) - 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_Description(t *testing.T) { - data := ` - { - "description": "Foo", - "builders": [{"type": "something"}] - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - if result == nil { - t.Fatal("should have result") - } - if result.Description != "Foo" { - t.Fatalf("bad: %#v", result.Description) - } -} - -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), nil) - 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), nil) - 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), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestParseTemplate_BuilderWithNonStringType(t *testing.T) { - data := ` - { - "builders": [{ - "type": 42 - }] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestParseTemplate_BuilderWithoutName(t *testing.T) { - data := ` - { - "builders": [ - { - "type": "amazon-ebs" - } - ] - } - ` - - result, err := ParseTemplate([]byte(data), nil) - 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), nil) - 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), nil) - 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), nil) - 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), nil) - 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), nil) - 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), nil) - 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), nil) - if err != nil { - t.Fatalf("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_ProvisionerPauseBefore(t *testing.T) { - data := ` - { - "builders": [{"type": "foo"}], - - "provisioners": [ - { - "type": "shell", - "pause_before": "10s" - } - ] - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("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].pauseBefore != 10*time.Second { - t.Fatalf("bad: %s", result.Provisioners[0].pauseBefore) - } -} - -func TestParseTemplateFile_push(t *testing.T) { - data := ` - { - "builders": [{"type": "something"}], - - "push": { - "name": "hello", - "include": ["one"], - "exclude": ["two"] - } - } - ` - - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - tf.Write([]byte(data)) - tf.Close() - - result, err := ParseTemplateFile(tf.Name(), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := &PushConfig{ - Name: "hello", - Include: []string{"one"}, - Exclude: []string{"two"}, - } - if !reflect.DeepEqual(result.Push, expected) { - t.Fatalf("bad: %#v", result.Push) - } -} - -func TestParseTemplate_Variables(t *testing.T) { - data := ` - { - "variables": { - "foo": "bar", - "bar": null, - "baz": 27 - }, - - "builders": [{"type": "something"}] - } - ` - - result, err := ParseTemplate([]byte(data), map[string]string{ - "bar": "bar", - }) - 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["foo"].HasValue { - t.Fatal("foo should not have value") - } - - 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["bar"].HasValue { - t.Fatal("bar should have value") - } - if result.Variables["bar"].Value != "bar" { - t.Fatal("bad value") - } - - 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_variablesSet(t *testing.T) { - data := ` - { - "variables": { - "foo": "bar" - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{ - "foo": "value", - }) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(template.Variables) != 1 { - t.Fatalf("bad vars: %#v", template.Variables) - } - if template.Variables["foo"].Value != "value" { - t.Fatalf("bad: %#v", template.Variables["foo"]) - } -} - -func TestParseTemplate_variablesSetUnknown(t *testing.T) { - data := ` - { - "variables": { - "foo": "bar" - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - _, err := ParseTemplate([]byte(data), map[string]string{ - "what": "value", - }) - if err == nil { - t.Fatal("should error") - } -} - -func TestParseTemplate_variablesBadDefault(t *testing.T) { - data := ` - { - "variables": { - "foo": 7, - }, - - "builders": [{"type": "something"}] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - 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), nil) - 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), nil) - 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), nil) - 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 TestTemplateBuild_envInVars(t *testing.T) { - data := ` - { - "variables": { - "foo": "{{env \"foo\"}}" - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - defer os.Setenv("foo", os.Getenv("foo")) - if err := os.Setenv("foo", "bar"); err != nil { - t.Fatalf("err: %s", err) - } - - template, err := ParseTemplate([]byte(data), map[string]string{}) - if err != nil { - t.Fatalf("err: %s", err) - } - - b, err := template.Build("test1", testComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - coreBuild, ok := b.(*coreBuild) - if !ok { - t.Fatal("should be ok") - } - - if coreBuild.variables["foo"] != "bar" { - t.Fatalf("bad: %#v", coreBuild.variables) - } -} - -func TestTemplateBuild_names(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2-{{user \"foo\"}}", - "type": "test-builder" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) - if err != nil { - t.Fatalf("err: %s", err) - } - - b, err := template.Build("test1", testComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - if b.Name() != "test1" { - t.Fatalf("bad: %#v", b.Name()) - } - - b, err = template.Build("test2-{{user \"foo\"}}", testComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - if b.Name() != "test2-bar" { - t.Fatalf("bad: %#v", b.Name()) - } -} - -func TestTemplate_Build_NilBuilderFunc(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - 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), nil) - 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), nil) - 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), nil) - 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(MockPostProcessor) - 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), nil) - 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), nil) - 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), nil) - 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), nil) - 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 one 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_exceptPPConfigTemplateName(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1-{{user \"foo\"}}", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "post-processors": [ - { - "type": "test-pp", - "except": ["test1-{{user \"foo\"}}"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) - if err != nil { - t.Fatalf("err: %s", err) - } - - // Verify test1 has no post-processors - build, err := template.Build("test1-{{user \"foo\"}}", 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 one 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), nil) - 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), nil) - 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 one 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_exceptProvConfigTemplateName(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1-{{user \"foo\"}}", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - "except": ["test1-{{user \"foo\"}}"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) - if err != nil { - t.Fatalf("err: %s", err) - } - - // Verify test1 has no provisioners - build, err := template.Build("test1-{{user \"foo\"}}", 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 one 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), nil) - 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), nil) - 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 one 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_onlyPPConfigTemplateName(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2-{{user \"foo\"}}", - "type": "test-builder" - } - ], - - "post-processors": [ - { - "type": "test-pp", - "only": ["test2-{{user \"foo\"}}"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) - 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 one post-processors - build, err = template.Build("test2-{{user \"foo\"}}", 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), nil) - 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), nil) - 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 one 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_onlyProvConfigTemplateName(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2-{{user \"foo\"}}", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - "only": ["test2-{{user \"foo\"}}"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) - 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 one provisioners - build, err = template.Build("test2-{{user \"foo\"}}", 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), nil) - 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), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestTemplateBuild_ProvisionerPauseBefore(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - "pause_before": "5s" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - 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, - } - - 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 pp, ok := coreBuild.provisioners[0].provisioner.(*PausedProvisioner); !ok { - t.Fatalf("should be paused provisioner") - } else { - if pp.PauseBefore != 5*time.Second { - t.Fatalf("bad: %#v", pp.PauseBefore) - } - } - - config := coreBuild.provisioners[0].config[0].(map[string]interface{}) - if _, ok := config["pause_before"]; ok { - t.Fatal("pause_before should be removed") - } -} - -func TestTemplateBuild_variables(t *testing.T) { - data := ` - { - "variables": { - "foo": "bar" - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - 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!") - } - - expected := map[string]string{"foo": "bar"} - if !reflect.DeepEqual(coreBuild.variables, expected) { - t.Fatalf("bad vars: %#v", coreBuild.variables) - } -} - -func TestTemplateBuild_variablesRequiredNotSet(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{}) - if err != nil { - t.Fatalf("err: %s", err) - } - - _, err = template.Build("test1", testComponentFinder()) - if err == nil { - t.Fatal("should error") - } -} From b9eea82a36cda8c90ef58b131cc7599b62e11c64 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:41:42 -0700 Subject: [PATCH 34/39] template: add tests for RawContents --- template/parse_test.go | 14 ++++++++++++++ template/test-fixtures/parse-contents.json | 1 + 2 files changed, 15 insertions(+) create mode 100644 template/test-fixtures/parse-contents.json diff --git a/template/parse_test.go b/template/parse_test.go index d5d9fcd8e..e99f35e51 100644 --- a/template/parse_test.go +++ b/template/parse_test.go @@ -2,6 +2,7 @@ package template import ( "reflect" + "strings" "testing" "time" ) @@ -284,3 +285,16 @@ func TestParse(t *testing.T) { } } } + +func TestParse_contents(t *testing.T) { + tpl, err := ParseFile(fixtureDir("parse-contents.json")) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(string(tpl.RawContents)) + expected := `{"builders":[{"type":"test"}]}` + if actual != expected { + t.Fatalf("bad: %s\n\n%s", actual, expected) + } +} diff --git a/template/test-fixtures/parse-contents.json b/template/test-fixtures/parse-contents.json new file mode 100644 index 000000000..edd70c12a --- /dev/null +++ b/template/test-fixtures/parse-contents.json @@ -0,0 +1 @@ +{"builders":[{"type":"test"}]} From 53e77eaceab1a1e4dfec7e764b7ec42f1eb1c673 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:46:04 -0700 Subject: [PATCH 35/39] packer: overrides work --- packer/core.go | 7 ++- packer/core_test.go | 47 +++++++++++++++++++ packer/test-fixtures/build-prov-override.json | 14 ++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 packer/test-fixtures/build-prov-override.json diff --git a/packer/core.go b/packer/core.go index a372c7ee8..3969da9c9 100644 --- a/packer/core.go +++ b/packer/core.go @@ -144,8 +144,11 @@ func (c *Core) Build(n string) (Build, error) { // Get the configuration config := make([]interface{}, 1, 2) config[0] = rawP.Config - - // TODO override + if rawP.Override != nil { + if override, ok := rawP.Override[rawName]; ok { + config = append(config, override) + } + } // If we're pausing, we wrap the provisioner in a special pauser. if rawP.PauseBefore > 0 { diff --git a/packer/core_test.go b/packer/core_test.go index 712694766..8cec16bae 100644 --- a/packer/core_test.go +++ b/packer/core_test.go @@ -222,6 +222,53 @@ func TestCoreBuild_provSkipInclude(t *testing.T) { } } +func TestCoreBuild_provOverride(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-prov-override.json")) + b := TestBuilder(t, config, "test") + p := TestProvisioner(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } + if !p.ProvCalled { + t.Fatal("provisioner not called") + } + + found := false + for _, raw := range p.PrepConfigs { + if m, ok := raw.(map[string]interface{}); ok { + if _, ok := m["foo"]; ok { + found = true + break + } + } + } + if !found { + t.Fatal("override not called") + } +} + func TestCoreBuild_postProcess(t *testing.T) { config := TestCoreConfig(t) testCoreTemplate(t, config, fixtureDir("build-pp.json")) diff --git a/packer/test-fixtures/build-prov-override.json b/packer/test-fixtures/build-prov-override.json new file mode 100644 index 000000000..eb3554792 --- /dev/null +++ b/packer/test-fixtures/build-prov-override.json @@ -0,0 +1,14 @@ +{ + "builders": [{ + "type": "test" + }], + + "provisioners": [{ + "type": "test", + "override": { + "test": { + "foo": "bar" + } + } + }] +} From d4b489a9ec60b825ee7fb47fd14c2db0eeedb561 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:46:11 -0700 Subject: [PATCH 36/39] update todo --- TODO.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO.txt b/TODO.txt index 031ec3ae3..944dc80e3 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,2 +1 @@ - var-file doesn't work -- prov/post-processors/hooks don't work From dd0a77550041228678b08b59449ca898526d3374 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:51:47 -0700 Subject: [PATCH 37/39] common/command: delete --- common/command/build_flags.go | 39 ---- common/command/build_flags_test.go | 104 ----------- common/command/flag_slice_value.go | 34 ---- common/command/flag_slice_value_test.go | 54 ------ common/command/template.go | 162 ----------------- common/command/template_test.go | 228 ------------------------ 6 files changed, 621 deletions(-) delete mode 100644 common/command/build_flags.go delete mode 100644 common/command/build_flags_test.go delete mode 100644 common/command/flag_slice_value.go delete mode 100644 common/command/flag_slice_value_test.go delete mode 100644 common/command/template.go delete mode 100644 common/command/template_test.go diff --git a/common/command/build_flags.go b/common/command/build_flags.go deleted file mode 100644 index d08ca58b8..000000000 --- a/common/command/build_flags.go +++ /dev/null @@ -1,39 +0,0 @@ -package command - -import ( - "flag" - "fmt" - "strings" -) - -// BuildOptionFlags sets the proper command line flags needed for -// build options. -func BuildOptionFlags(fs *flag.FlagSet, f *BuildOptions) { - fs.Var((*SliceValue)(&f.Except), "except", "build all builds except these") - fs.Var((*SliceValue)(&f.Only), "only", "only build the given builds by name") - fs.Var((*userVarValue)(&f.UserVars), "var", "specify a user variable") - fs.Var((*AppendSliceValue)(&f.UserVarFiles), "var-file", "file with user variables") -} - -// userVarValue is a flag.Value that parses out user variables in -// the form of 'key=value' and sets it on this map. -type userVarValue map[string]string - -func (v *userVarValue) String() string { - return "" -} - -func (v *userVarValue) Set(raw string) error { - idx := strings.Index(raw, "=") - if idx == -1 { - return fmt.Errorf("No '=' value in arg: %s", raw) - } - - if *v == nil { - *v = make(map[string]string) - } - - key, value := raw[0:idx], raw[idx+1:] - (*v)[key] = value - return nil -} diff --git a/common/command/build_flags_test.go b/common/command/build_flags_test.go deleted file mode 100644 index 5d39eb946..000000000 --- a/common/command/build_flags_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package command - -import ( - "flag" - "reflect" - "testing" -) - -func TestBuildOptionFlags(t *testing.T) { - opts := new(BuildOptions) - fs := flag.NewFlagSet("test", flag.ContinueOnError) - BuildOptionFlags(fs, opts) - - args := []string{ - "-except=foo,bar,baz", - "-only=a,b", - "-var=foo=bar", - "-var", "bar=baz", - "-var=foo=bang", - "-var-file=foo", - "-var-file=bar", - } - - err := fs.Parse(args) - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := []string{"foo", "bar", "baz"} - if !reflect.DeepEqual(opts.Except, expected) { - t.Fatalf("bad: %#v", opts.Except) - } - - expected = []string{"a", "b"} - if !reflect.DeepEqual(opts.Only, expected) { - t.Fatalf("bad: %#v", opts.Only) - } - - if len(opts.UserVars) != 2 { - t.Fatalf("bad: %#v", opts.UserVars) - } - - if opts.UserVars["foo"] != "bang" { - t.Fatalf("bad: %#v", opts.UserVars) - } - - if opts.UserVars["bar"] != "baz" { - t.Fatalf("bad: %#v", opts.UserVars) - } - - expected = []string{"foo", "bar"} - if !reflect.DeepEqual(opts.UserVarFiles, expected) { - t.Fatalf("bad: %#v", opts.UserVarFiles) - } -} - -func TestUserVarValue_implements(t *testing.T) { - var raw interface{} - raw = new(userVarValue) - if _, ok := raw.(flag.Value); !ok { - t.Fatalf("userVarValue should be a Value") - } -} - -func TestUserVarValueSet(t *testing.T) { - sv := new(userVarValue) - err := sv.Set("key=value") - if err != nil { - t.Fatalf("err: %s", err) - } - - vars := map[string]string(*sv) - if vars["key"] != "value" { - t.Fatalf("Bad: %#v", vars) - } - - // Empty value - err = sv.Set("key=") - if err != nil { - t.Fatalf("err: %s", err) - } - - vars = map[string]string(*sv) - if vars["key"] != "" { - t.Fatalf("Bad: %#v", vars) - } - - // Equal in value - err = sv.Set("key=foo=bar") - if err != nil { - t.Fatalf("err: %s", err) - } - - vars = map[string]string(*sv) - if vars["key"] != "foo=bar" { - t.Fatalf("Bad: %#v", vars) - } - - // No equal - err = sv.Set("key") - if err == nil { - t.Fatal("should have error") - } -} diff --git a/common/command/flag_slice_value.go b/common/command/flag_slice_value.go deleted file mode 100644 index 8989dedad..000000000 --- a/common/command/flag_slice_value.go +++ /dev/null @@ -1,34 +0,0 @@ -package command - -import "strings" - -// AppendSliceValue implements the flag.Value interface and allows multiple -// calls to the same variable to append a list. -type AppendSliceValue []string - -func (s *AppendSliceValue) String() string { - return strings.Join(*s, ",") -} - -func (s *AppendSliceValue) Set(value string) error { - if *s == nil { - *s = make([]string, 0, 1) - } - - *s = append(*s, value) - return nil -} - -// SliceValue implements the flag.Value interface and allows a list of -// strings to be given on the command line and properly parsed into a slice -// of strings internally. -type SliceValue []string - -func (s *SliceValue) String() string { - return strings.Join(*s, ",") -} - -func (s *SliceValue) Set(value string) error { - *s = strings.Split(value, ",") - return nil -} diff --git a/common/command/flag_slice_value_test.go b/common/command/flag_slice_value_test.go deleted file mode 100644 index ca80c9d9f..000000000 --- a/common/command/flag_slice_value_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package command - -import ( - "flag" - "reflect" - "testing" -) - -func TestAppendSliceValue_implements(t *testing.T) { - var raw interface{} - raw = new(AppendSliceValue) - if _, ok := raw.(flag.Value); !ok { - t.Fatalf("AppendSliceValue should be a Value") - } -} - -func TestAppendSliceValueSet(t *testing.T) { - sv := new(AppendSliceValue) - err := sv.Set("foo") - if err != nil { - t.Fatalf("err: %s", err) - } - - err = sv.Set("bar") - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := []string{"foo", "bar"} - if !reflect.DeepEqual([]string(*sv), expected) { - t.Fatalf("Bad: %#v", sv) - } -} - -func TestSliceValue_implements(t *testing.T) { - var raw interface{} - raw = new(SliceValue) - if _, ok := raw.(flag.Value); !ok { - t.Fatalf("SliceValue should be a Value") - } -} - -func TestSliceValueSet(t *testing.T) { - sv := new(SliceValue) - err := sv.Set("foo,bar,baz") - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := []string{"foo", "bar", "baz"} - if !reflect.DeepEqual([]string(*sv), expected) { - t.Fatalf("Bad: %#v", sv) - } -} diff --git a/common/command/template.go b/common/command/template.go deleted file mode 100644 index 27a42f901..000000000 --- a/common/command/template.go +++ /dev/null @@ -1,162 +0,0 @@ -package command - -import ( - "errors" - "fmt" - jsonutil "github.com/mitchellh/packer/common/json" - "github.com/mitchellh/packer/packer" - "io/ioutil" - "log" - "os" -) - -// BuildOptions is a set of options related to builds that can be set -// from the command line. -type BuildOptions struct { - UserVarFiles []string - UserVars map[string]string - Except []string - Only []string -} - -// Validate validates the options -func (f *BuildOptions) Validate() error { - if len(f.Except) > 0 && len(f.Only) > 0 { - return errors.New("Only one of '-except' or '-only' may be specified.") - } - - if len(f.UserVarFiles) > 0 { - for _, path := range f.UserVarFiles { - if _, err := os.Stat(path); err != nil { - return fmt.Errorf("Cannot access: %s", path) - } - } - } - - return nil -} - -// AllUserVars returns the user variables, compiled from both the -// file paths and the vars on the command line. -func (f *BuildOptions) AllUserVars() (map[string]string, error) { - all := make(map[string]string) - - // Copy in the variables from the files - for _, path := range f.UserVarFiles { - fileVars, err := readFileVars(path) - if err != nil { - return nil, err - } - - for k, v := range fileVars { - all[k] = v - } - } - - // Copy in the command-line vars - for k, v := range f.UserVars { - all[k] = v - } - - return all, nil -} - -// Builds returns the builds out of the given template that pass the -// configured options. -func (f *BuildOptions) Builds(t *packer.Template, cf *packer.ComponentFinder) ([]packer.Build, error) { - buildNames := t.BuildNames() - - // Process the name - tpl, _, err := t.NewConfigTemplate() - if err != nil { - return nil, err - } - - checks := make(map[string][]string) - checks["except"] = f.Except - checks["only"] = f.Only - for t, ns := range checks { - for _, n := range ns { - found := false - for _, actual := range buildNames { - var processed string - processed, err = tpl.Process(actual, nil) - if err != nil { - return nil, err - } - if actual == n || processed == n { - found = true - break - } - } - - if !found { - return nil, fmt.Errorf( - "Unknown build in '%s' flag: %s", t, n) - } - } - } - - builds := make([]packer.Build, 0, len(buildNames)) - for _, buildName := range buildNames { - var processedBuildName string - processedBuildName, err = tpl.Process(buildName, nil) - if err != nil { - return nil, err - } - if len(f.Except) > 0 { - found := false - for _, except := range f.Except { - if buildName == except || processedBuildName == except { - found = true - break - } - } - - if found { - log.Printf("Skipping build '%s' because specified by -except.", processedBuildName) - continue - } - } - - if len(f.Only) > 0 { - found := false - for _, only := range f.Only { - if buildName == only || processedBuildName == only { - found = true - break - } - } - - if !found { - log.Printf("Skipping build '%s' because not specified by -only.", processedBuildName) - continue - } - } - - log.Printf("Creating build: %s", processedBuildName) - build, err := t.Build(buildName, cf) - if err != nil { - return nil, fmt.Errorf("Failed to create build '%s': \n\n%s", buildName, err) - } - - builds = append(builds, build) - } - - return builds, nil -} - -func readFileVars(path string) (map[string]string, error) { - bytes, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - vars := make(map[string]string) - err = jsonutil.Unmarshal(bytes, &vars) - if err != nil { - return nil, err - } - - return vars, nil -} diff --git a/common/command/template_test.go b/common/command/template_test.go deleted file mode 100644 index 419ee7012..000000000 --- a/common/command/template_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package command - -import ( - "github.com/mitchellh/packer/packer" - "testing" -) - -func testTemplate() (*packer.Template, *packer.ComponentFinder) { - tplData := `{ - "variables": { - "foo": null - }, - - "builders": [ - { - "type": "foo" - }, - { - "name": "{{user \"foo\"}}", - "type": "bar" - } - ] - } - ` - - tpl, err := packer.ParseTemplate([]byte(tplData), map[string]string{"foo": "bar"}) - if err != nil { - panic(err) - } - - cf := &packer.ComponentFinder{ - Builder: func(string) (packer.Builder, error) { return new(packer.MockBuilder), nil }, - } - - return tpl, cf -} - -func TestBuildOptionsBuilds(t *testing.T) { - opts := new(BuildOptions) - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 2 { - t.Fatalf("bad: %d", len(bs)) - } -} - -func TestBuildOptionsBuilds_except(t *testing.T) { - opts := new(BuildOptions) - opts.Except = []string{"foo"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "bar" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -//Test to make sure the build name pattern matches -func TestBuildOptionsBuilds_exceptConfigTemplateRaw(t *testing.T) { - opts := new(BuildOptions) - opts.Except = []string{"{{user \"foo\"}}"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "foo" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -//Test to make sure the processed build name matches -func TestBuildOptionsBuilds_exceptConfigTemplateProcessed(t *testing.T) { - opts := new(BuildOptions) - opts.Except = []string{"bar"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "foo" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -func TestBuildOptionsBuilds_only(t *testing.T) { - opts := new(BuildOptions) - opts.Only = []string{"foo"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "foo" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -//Test to make sure the build name pattern matches -func TestBuildOptionsBuilds_onlyConfigTemplateRaw(t *testing.T) { - opts := new(BuildOptions) - opts.Only = []string{"{{user \"foo\"}}"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "bar" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -//Test to make sure the processed build name matches -func TestBuildOptionsBuilds_onlyConfigTemplateProcessed(t *testing.T) { - opts := new(BuildOptions) - opts.Only = []string{"bar"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "bar" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -func TestBuildOptionsBuilds_exceptNonExistent(t *testing.T) { - opts := new(BuildOptions) - opts.Except = []string{"i-dont-exist"} - - _, err := opts.Builds(testTemplate()) - if err == nil { - t.Fatal("err should not be nil") - } -} - -func TestBuildOptionsBuilds_onlyNonExistent(t *testing.T) { - opts := new(BuildOptions) - opts.Only = []string{"i-dont-exist"} - - _, err := opts.Builds(testTemplate()) - if err == nil { - t.Fatal("err should not be nil") - } -} - -func TestBuildOptionsValidate(t *testing.T) { - bf := new(BuildOptions) - - err := bf.Validate() - if err != nil { - t.Fatalf("err: %s", err) - } - - // Both set - bf.Except = make([]string, 1) - bf.Only = make([]string, 1) - err = bf.Validate() - if err == nil { - t.Fatal("should error") - } - - // One set - bf.Except = make([]string, 1) - bf.Only = make([]string, 0) - err = bf.Validate() - if err != nil { - t.Fatalf("err: %s", err) - } - - bf.Except = make([]string, 0) - bf.Only = make([]string, 1) - err = bf.Validate() - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestBuildOptionsValidate_userVarFiles(t *testing.T) { - bf := new(BuildOptions) - - err := bf.Validate() - if err != nil { - t.Fatalf("err: %s", err) - } - - // Non-existent file - bf.UserVarFiles = []string{"ireallyshouldntexistanywhere"} - err = bf.Validate() - if err == nil { - t.Fatal("should error") - } -} From 7f78a2c5d91a5e6f6f596201d5ded5eeab468907 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:58:04 -0700 Subject: [PATCH 38/39] helper/flag-kv: can parse JSON files --- helper/flag-kv/flag_json.go | 34 ++++++++++++++ helper/flag-kv/flag_json_test.go | 59 +++++++++++++++++++++++++ helper/flag-kv/test-fixtures/basic.json | 3 ++ 3 files changed, 96 insertions(+) create mode 100644 helper/flag-kv/flag_json.go create mode 100644 helper/flag-kv/flag_json_test.go create mode 100644 helper/flag-kv/test-fixtures/basic.json diff --git a/helper/flag-kv/flag_json.go b/helper/flag-kv/flag_json.go new file mode 100644 index 000000000..9af9fe1da --- /dev/null +++ b/helper/flag-kv/flag_json.go @@ -0,0 +1,34 @@ +package kvflag + +import ( + "encoding/json" + "fmt" + "os" +) + +// FlagJSON is a flag.Value implementation for parsing user variables +// from the command-line using JSON files. +type FlagJSON map[string]string + +func (v *FlagJSON) String() string { + return "" +} + +func (v *FlagJSON) Set(raw string) error { + f, err := os.Open(raw) + if err != nil { + return err + } + defer f.Close() + + if *v == nil { + *v = make(map[string]string) + } + + if err := json.NewDecoder(f).Decode(v); err != nil { + return fmt.Errorf( + "Error reading variables in '%s': %s", raw, err) + } + + return nil +} diff --git a/helper/flag-kv/flag_json_test.go b/helper/flag-kv/flag_json_test.go new file mode 100644 index 000000000..df5a99e64 --- /dev/null +++ b/helper/flag-kv/flag_json_test.go @@ -0,0 +1,59 @@ +package kvflag + +import ( + "flag" + "path/filepath" + "reflect" + "testing" +) + +func TestFlagJSON_impl(t *testing.T) { + var _ flag.Value = new(FlagJSON) +} + +func TestFlagJSON(t *testing.T) { + cases := []struct { + Input string + Initial map[string]string + Output map[string]string + Error bool + }{ + { + "basic.json", + nil, + map[string]string{"key": "value"}, + false, + }, + + { + "basic.json", + map[string]string{"foo": "bar"}, + map[string]string{"foo": "bar", "key": "value"}, + false, + }, + + { + "basic.json", + map[string]string{"key": "bar"}, + map[string]string{"key": "value"}, + false, + }, + } + + for _, tc := range cases { + f := new(FlagJSON) + if tc.Initial != nil { + f = (*FlagJSON)(&tc.Initial) + } + + err := f.Set(filepath.Join("./test-fixtures", tc.Input)) + if (err != nil) != tc.Error { + t.Fatalf("bad error. Input: %#v\n\n%s", tc.Input, err) + } + + actual := map[string]string(*f) + if !reflect.DeepEqual(actual, tc.Output) { + t.Fatalf("bad: %#v", actual) + } + } +} diff --git a/helper/flag-kv/test-fixtures/basic.json b/helper/flag-kv/test-fixtures/basic.json new file mode 100644 index 000000000..21da3b262 --- /dev/null +++ b/helper/flag-kv/test-fixtures/basic.json @@ -0,0 +1,3 @@ +{ + "key": "value" +} From 8df1bca5a1f1590e4672b1fda29ad3ba7161fc4d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 May 2015 09:58:36 -0700 Subject: [PATCH 39/39] command/meta: parse var-files --- TODO.txt | 1 - command/meta.go | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 TODO.txt diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index 944dc80e3..000000000 --- a/TODO.txt +++ /dev/null @@ -1 +0,0 @@ -- var-file doesn't work diff --git a/command/meta.go b/command/meta.go index e62577df9..0dc721bd1 100644 --- a/command/meta.go +++ b/command/meta.go @@ -32,7 +32,6 @@ type Meta struct { flagBuildExcept []string flagBuildOnly []string flagVars map[string]string - flagVarFiles []string } // Core returns the core for the given template given the configured @@ -122,7 +121,7 @@ func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet { // FlagSetVars tells us what variables to use if fs&FlagSetVars != 0 { f.Var((*kvflag.Flag)(&m.flagVars), "var", "") - f.Var((*sliceflag.StringFlag)(&m.flagVarFiles), "var-file", "") + f.Var((*kvflag.FlagJSON)(&m.flagVars), "var-file", "") } // Create an io.Writer that writes to our Ui properly for errors.