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..95e50597f 100644 --- a/command/push.go +++ b/command/push.go @@ -1,7 +1,6 @@ package command import ( - "flag" "fmt" "io" "os" @@ -12,6 +11,7 @@ import ( "github.com/hashicorp/atlas-go/archive" "github.com/hashicorp/atlas-go/v1" "github.com/mitchellh/packer/template" + "github.com/mitchellh/packer/template/interpolate" ) // archiveTemplateEntry is the name the template always takes within the slug. @@ -37,7 +37,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 +67,23 @@ 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 := tpl.Push + pushRaw, err := interpolate.RenderInterface(&push, core.Context()) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + push = *pushRaw.(*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 +97,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 +117,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 +134,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 +164,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 +243,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 +271,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..bdf9cb65a 100644 --- a/packer/core.go +++ b/packer/core.go @@ -66,7 +66,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", @@ -206,6 +206,14 @@ func (c *Core) Build(n string) (Build, error) { }, 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 @@ -241,7 +249,7 @@ 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 { @@ -268,10 +276,3 @@ func (c *Core) init() error { return nil } - -func (c *Core) context() *interpolate.Context { - return &interpolate.Context{ - TemplatePath: c.template.Path, - UserVariables: c.variables, - } -} 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{}