diff --git a/command/command_test.go b/command/command_test.go index 49e0c7276..126897810 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -28,6 +28,7 @@ func testMeta(t *testing.T) Meta { var out, err bytes.Buffer return Meta{ + CoreConfig: packer.TestCoreConfig(t), Ui: &packer.BasicUi{ Writer: &out, ErrorWriter: &err, diff --git a/command/push.go b/command/push.go index 297de804a..6c04917dd 100644 --- a/command/push.go +++ b/command/push.go @@ -1,7 +1,6 @@ package command import ( - "flag" "fmt" "io" "os" @@ -37,7 +36,7 @@ func (c *PushCommand) Run(args []string) int { var name string var create bool - f := flag.NewFlagSet("push", flag.ContinueOnError) + f := c.Meta.FlagSet("push", FlagSetVars) f.Usage = func() { c.Ui.Error(c.Help()) } f.StringVar(&token, "token", "", "token") f.StringVar(&message, "m", "", "message") @@ -67,9 +66,17 @@ func (c *PushCommand) Run(args []string) int { return 1 } + // Get the core + core, err := c.Meta.Core(tpl) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + push := core.Template.Push + // If we didn't pass name from the CLI, use the template if name == "" { - name = tpl.Push.Name + name = push.Name } // Validate some things @@ -83,14 +90,14 @@ func (c *PushCommand) Run(args []string) int { // Determine our token if token == "" { - token = tpl.Push.Token + token = push.Token } // Build our client defer func() { c.client = nil }() c.client = atlas.DefaultClient() - if tpl.Push.Address != "" { - c.client, err = atlas.NewClient(tpl.Push.Address) + if push.Address != "" { + c.client, err = atlas.NewClient(push.Address) if err != nil { c.Ui.Error(fmt.Sprintf( "Error setting up API client: %s", err)) @@ -103,9 +110,9 @@ func (c *PushCommand) Run(args []string) int { // Build the archiving options var opts archive.ArchiveOpts - opts.Include = tpl.Push.Include - opts.Exclude = tpl.Push.Exclude - opts.VCS = tpl.Push.VCS + opts.Include = push.Include + opts.Exclude = push.Exclude + opts.VCS = push.VCS opts.Extra = map[string]string{ archiveTemplateEntry: args[0], } @@ -120,7 +127,7 @@ func (c *PushCommand) Run(args []string) int { // 3.) BaseDir is relative, so we use the path relative to the directory // of the template. // - path := tpl.Push.BaseDir + path := push.BaseDir if path == "" || !filepath.IsAbs(path) { tplPath, err := filepath.Abs(args[0]) if err != nil { @@ -150,7 +157,7 @@ func (c *PushCommand) Run(args []string) int { // Build the upload options var uploadOpts uploadOpts - uploadOpts.Slug = tpl.Push.Name + uploadOpts.Slug = push.Name uploadOpts.Builds = make(map[string]*uploadBuildInfo) for _, b := range tpl.Builders { info := &uploadBuildInfo{Type: b.Type} @@ -229,7 +236,7 @@ func (c *PushCommand) Run(args []string) int { return 1 } - c.Ui.Say(fmt.Sprintf("Push successful to '%s'", tpl.Push.Name)) + c.Ui.Say(fmt.Sprintf("Push successful to '%s'", push.Name)) return 0 } @@ -257,6 +264,10 @@ Options: "username/name". -token= The access token to use to when uploading + + -var 'key=value' Variable for templates, can be used multiple times. + + -var-file=path JSON file containing user variables. ` return strings.TrimSpace(helpText) diff --git a/command/push_test.go b/command/push_test.go index 322637049..f1b7fd306 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -190,6 +190,35 @@ func TestPush_uploadErrorCh(t *testing.T) { } } +func TestPush_vars(t *testing.T) { + var actualOpts *uploadOpts + uploadFn := func(r io.Reader, opts *uploadOpts) (<-chan struct{}, <-chan error, error) { + actualOpts = opts + + doneCh := make(chan struct{}) + close(doneCh) + return doneCh, nil, nil + } + + c := &PushCommand{ + Meta: testMeta(t), + uploadFn: uploadFn, + } + + args := []string{ + "-var", "name=foo/bar", + filepath.Join(testFixture("push-vars"), "template.json"), + } + if code := c.Run(args); code != 0 { + fatalCommand(t, c.Meta) + } + + expected := "foo/bar" + if actualOpts.Slug != expected { + t.Fatalf("bad: %#v", actualOpts.Slug) + } +} + func testArchive(t *testing.T, r io.Reader) []string { // Finish the archiving process in-memory var buf bytes.Buffer diff --git a/command/test-fixtures/push-vars/template.json b/command/test-fixtures/push-vars/template.json new file mode 100644 index 000000000..3085062ae --- /dev/null +++ b/command/test-fixtures/push-vars/template.json @@ -0,0 +1,11 @@ +{ + "variables": { + "name": null + }, + + "builders": [{"type": "dummy"}], + + "push": { + "name": "{{user `name`}}" + } +} diff --git a/packer/core.go b/packer/core.go index 0f7886772..3bc5d295e 100644 --- a/packer/core.go +++ b/packer/core.go @@ -12,8 +12,9 @@ import ( // 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 { + Template *template.Template + components ComponentFinder - template *template.Template variables map[string]string builds map[string]*template.Builder } @@ -51,8 +52,8 @@ type ComponentFinder struct { // NewCore creates a new Core. func NewCore(c *CoreConfig) (*Core, error) { result := &Core{ + Template: c.Template, components: c.Components, - template: c.Template, variables: c.Variables, } if err := result.validate(); err != nil { @@ -66,7 +67,7 @@ func NewCore(c *CoreConfig) (*Core, error) { // to do this at this point with the variables. result.builds = make(map[string]*template.Builder) for _, b := range c.Template.Builders { - v, err := interpolate.Render(b.Name, result.context()) + v, err := interpolate.Render(b.Name, result.Context()) if err != nil { return nil, fmt.Errorf( "Error interpolating builder '%s': %s", @@ -112,8 +113,8 @@ func (c *Core) Build(n string) (Build, error) { rawName := configBuilder.Name // Setup the provisioners for this build - provisioners := make([]coreBuildProvisioner, 0, len(c.template.Provisioners)) - for _, rawP := range c.template.Provisioners { + 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 @@ -155,8 +156,8 @@ 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 { + 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 @@ -201,11 +202,19 @@ func (c *Core) Build(n string) (Build, error) { builderType: configBuilder.Type, postProcessors: postProcessors, provisioners: provisioners, - templatePath: c.template.Path, + templatePath: c.Template.Path, variables: c.variables, }, nil } +// Context returns an interpolation context. +func (c *Core) Context() *interpolate.Context { + return &interpolate.Context{ + TemplatePath: c.Template.Path, + UserVariables: c.variables, + } +} + // validate does a full validation of the template. // // This will automatically call template.validate() in addition to doing @@ -213,13 +222,13 @@ func (c *Core) Build(n string) (Build, error) { 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 { + if err := c.Template.Validate(); err != nil { return err } // Validate variables are set var err error - for n, v := range c.template.Variables { + for n, v := range c.Template.Variables { if v.Required { if _, ok := c.variables[n]; !ok { err = multierror.Append(err, fmt.Errorf( @@ -241,10 +250,10 @@ func (c *Core) init() error { } // Go through the variables and interpolate the environment variables - ctx := c.context() + ctx := c.Context() ctx.EnableEnv = true ctx.UserVariables = nil - for k, v := range c.template.Variables { + for k, v := range c.Template.Variables { // Ignore variables that are required if v.Required { continue @@ -266,12 +275,10 @@ func (c *Core) init() error { c.variables[k] = def } + // Interpolate the push configuration + if _, err := interpolate.RenderInterface(&c.Template.Push, c.Context()); err != nil { + return fmt.Errorf("Error interpolating 'push': %s", err) + } + return nil } - -func (c *Core) context() *interpolate.Context { - return &interpolate.Context{ - TemplatePath: c.template.Path, - UserVariables: c.variables, - } -} diff --git a/packer/core_test.go b/packer/core_test.go index cc03574ed..f11242d0c 100644 --- a/packer/core_test.go +++ b/packer/core_test.go @@ -368,6 +368,40 @@ func TestCoreBuild_templatePath(t *testing.T) { } } +func TestCore_pushInterpolate(t *testing.T) { + cases := []struct { + File string + Vars map[string]string + Result template.Push + }{ + { + "push-vars.json", + map[string]string{"foo": "bar"}, + template.Push{Name: "bar"}, + }, + } + + 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) + } + + expected := core.Template.Push + if !reflect.DeepEqual(expected, tc.Result) { + t.Fatalf("err: %s\n\n%#v", tc.File, expected) + } + } +} + func TestCoreValidate(t *testing.T) { cases := []struct { File string diff --git a/packer/test-fixtures/push-vars.json b/packer/test-fixtures/push-vars.json new file mode 100644 index 000000000..b5f518100 --- /dev/null +++ b/packer/test-fixtures/push-vars.json @@ -0,0 +1,11 @@ +{ + "variables": { + "foo": null + }, + + "builders": [{"type": "test"}], + + "push": { + "name": "{{user `foo`}}" + } +} diff --git a/template/interpolate/render_test.go b/template/interpolate/render_test.go index 60a88f6fb..7156c344c 100644 --- a/template/interpolate/render_test.go +++ b/template/interpolate/render_test.go @@ -5,6 +5,47 @@ import ( "testing" ) +func TestRenderInterface(t *testing.T) { + type Test struct { + Foo string + } + + cases := map[string]struct { + Input interface{} + Output interface{} + }{ + "basic": { + map[string]interface{}{ + "foo": "{{upper `bar`}}", + }, + map[string]interface{}{ + "foo": "BAR", + }, + }, + + "struct": { + &Test{ + Foo: "{{upper `bar`}}", + }, + &Test{ + Foo: "BAR", + }, + }, + } + + ctx := &Context{} + for k, tc := range cases { + actual, err := RenderInterface(tc.Input, ctx) + if err != nil { + t.Fatalf("err: %s\n\n%s", k, err) + } + + if !reflect.DeepEqual(actual, tc.Output) { + t.Fatalf("err: %s\n\n%#v\n\n%#v", k, actual, tc.Output) + } + } +} + func TestRenderMap(t *testing.T) { cases := map[string]struct { Input interface{}