Merge pull request #2153 from mitchellh/f-interpolate-push

Interpolate the "push" config
This commit is contained in:
Mitchell Hashimoto 2015-05-29 16:02:35 -07:00
commit b507173f27
8 changed files with 176 additions and 31 deletions

View File

@ -28,6 +28,7 @@ func testMeta(t *testing.T) Meta {
var out, err bytes.Buffer var out, err bytes.Buffer
return Meta{ return Meta{
CoreConfig: packer.TestCoreConfig(t),
Ui: &packer.BasicUi{ Ui: &packer.BasicUi{
Writer: &out, Writer: &out,
ErrorWriter: &err, ErrorWriter: &err,

View File

@ -1,7 +1,6 @@
package command package command
import ( import (
"flag"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -37,7 +36,7 @@ func (c *PushCommand) Run(args []string) int {
var name string var name string
var create bool var create bool
f := flag.NewFlagSet("push", flag.ContinueOnError) f := c.Meta.FlagSet("push", FlagSetVars)
f.Usage = func() { c.Ui.Error(c.Help()) } f.Usage = func() { c.Ui.Error(c.Help()) }
f.StringVar(&token, "token", "", "token") f.StringVar(&token, "token", "", "token")
f.StringVar(&message, "m", "", "message") f.StringVar(&message, "m", "", "message")
@ -67,9 +66,17 @@ func (c *PushCommand) Run(args []string) int {
return 1 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 we didn't pass name from the CLI, use the template
if name == "" { if name == "" {
name = tpl.Push.Name name = push.Name
} }
// Validate some things // Validate some things
@ -83,14 +90,14 @@ func (c *PushCommand) Run(args []string) int {
// Determine our token // Determine our token
if token == "" { if token == "" {
token = tpl.Push.Token token = push.Token
} }
// Build our client // Build our client
defer func() { c.client = nil }() defer func() { c.client = nil }()
c.client = atlas.DefaultClient() c.client = atlas.DefaultClient()
if tpl.Push.Address != "" { if push.Address != "" {
c.client, err = atlas.NewClient(tpl.Push.Address) c.client, err = atlas.NewClient(push.Address)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf(
"Error setting up API client: %s", err)) "Error setting up API client: %s", err))
@ -103,9 +110,9 @@ func (c *PushCommand) Run(args []string) int {
// Build the archiving options // Build the archiving options
var opts archive.ArchiveOpts var opts archive.ArchiveOpts
opts.Include = tpl.Push.Include opts.Include = push.Include
opts.Exclude = tpl.Push.Exclude opts.Exclude = push.Exclude
opts.VCS = tpl.Push.VCS opts.VCS = push.VCS
opts.Extra = map[string]string{ opts.Extra = map[string]string{
archiveTemplateEntry: args[0], 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 // 3.) BaseDir is relative, so we use the path relative to the directory
// of the template. // of the template.
// //
path := tpl.Push.BaseDir path := push.BaseDir
if path == "" || !filepath.IsAbs(path) { if path == "" || !filepath.IsAbs(path) {
tplPath, err := filepath.Abs(args[0]) tplPath, err := filepath.Abs(args[0])
if err != nil { if err != nil {
@ -150,7 +157,7 @@ func (c *PushCommand) Run(args []string) int {
// Build the upload options // Build the upload options
var uploadOpts uploadOpts var uploadOpts uploadOpts
uploadOpts.Slug = tpl.Push.Name uploadOpts.Slug = push.Name
uploadOpts.Builds = make(map[string]*uploadBuildInfo) uploadOpts.Builds = make(map[string]*uploadBuildInfo)
for _, b := range tpl.Builders { for _, b := range tpl.Builders {
info := &uploadBuildInfo{Type: b.Type} info := &uploadBuildInfo{Type: b.Type}
@ -229,7 +236,7 @@ func (c *PushCommand) Run(args []string) int {
return 1 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 return 0
} }
@ -257,6 +264,10 @@ Options:
"username/name". "username/name".
-token=<token> The access token to use to when uploading -token=<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) return strings.TrimSpace(helpText)

View File

@ -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 { func testArchive(t *testing.T, r io.Reader) []string {
// Finish the archiving process in-memory // Finish the archiving process in-memory
var buf bytes.Buffer var buf bytes.Buffer

View File

@ -0,0 +1,11 @@
{
"variables": {
"name": null
},
"builders": [{"type": "dummy"}],
"push": {
"name": "{{user `name`}}"
}
}

View File

@ -12,8 +12,9 @@ import (
// Core is the main executor of Packer. If Packer is being used as a // 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. // library, this is the struct you'll want to instantiate to get anything done.
type Core struct { type Core struct {
Template *template.Template
components ComponentFinder components ComponentFinder
template *template.Template
variables map[string]string variables map[string]string
builds map[string]*template.Builder builds map[string]*template.Builder
} }
@ -51,8 +52,8 @@ type ComponentFinder struct {
// NewCore creates a new Core. // NewCore creates a new Core.
func NewCore(c *CoreConfig) (*Core, error) { func NewCore(c *CoreConfig) (*Core, error) {
result := &Core{ result := &Core{
Template: c.Template,
components: c.Components, components: c.Components,
template: c.Template,
variables: c.Variables, variables: c.Variables,
} }
if err := result.validate(); err != nil { 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. // to do this at this point with the variables.
result.builds = make(map[string]*template.Builder) result.builds = make(map[string]*template.Builder)
for _, b := range c.Template.Builders { 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 { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"Error interpolating builder '%s': %s", "Error interpolating builder '%s': %s",
@ -112,8 +113,8 @@ func (c *Core) Build(n string) (Build, error) {
rawName := configBuilder.Name rawName := configBuilder.Name
// Setup the provisioners for this build // Setup the provisioners for this build
provisioners := make([]coreBuildProvisioner, 0, len(c.template.Provisioners)) provisioners := make([]coreBuildProvisioner, 0, len(c.Template.Provisioners))
for _, rawP := range c.template.Provisioners { for _, rawP := range c.Template.Provisioners {
// If we're skipping this, then ignore it // If we're skipping this, then ignore it
if rawP.Skip(rawName) { if rawP.Skip(rawName) {
continue continue
@ -155,8 +156,8 @@ func (c *Core) Build(n string) (Build, error) {
} }
// Setup the post-processors // Setup the post-processors
postProcessors := make([][]coreBuildPostProcessor, 0, len(c.template.PostProcessors)) postProcessors := make([][]coreBuildPostProcessor, 0, len(c.Template.PostProcessors))
for _, rawPs := range c.template.PostProcessors { for _, rawPs := range c.Template.PostProcessors {
current := make([]coreBuildPostProcessor, 0, len(rawPs)) current := make([]coreBuildPostProcessor, 0, len(rawPs))
for _, rawP := range rawPs { for _, rawP := range rawPs {
// If we skip, ignore // If we skip, ignore
@ -201,11 +202,19 @@ func (c *Core) Build(n string) (Build, error) {
builderType: configBuilder.Type, builderType: configBuilder.Type,
postProcessors: postProcessors, postProcessors: postProcessors,
provisioners: provisioners, provisioners: provisioners,
templatePath: c.template.Path, templatePath: c.Template.Path,
variables: c.variables, variables: c.variables,
}, nil }, 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. // validate does a full validation of the template.
// //
// This will automatically call template.validate() in addition to doing // 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 { func (c *Core) validate() error {
// First validate the template in general, we can't do anything else // First validate the template in general, we can't do anything else
// unless the template itself is valid. // unless the template itself is valid.
if err := c.template.Validate(); err != nil { if err := c.Template.Validate(); err != nil {
return err return err
} }
// Validate variables are set // Validate variables are set
var err error var err error
for n, v := range c.template.Variables { for n, v := range c.Template.Variables {
if v.Required { if v.Required {
if _, ok := c.variables[n]; !ok { if _, ok := c.variables[n]; !ok {
err = multierror.Append(err, fmt.Errorf( err = multierror.Append(err, fmt.Errorf(
@ -241,10 +250,10 @@ func (c *Core) init() error {
} }
// Go through the variables and interpolate the environment variables // Go through the variables and interpolate the environment variables
ctx := c.context() ctx := c.Context()
ctx.EnableEnv = true ctx.EnableEnv = true
ctx.UserVariables = nil ctx.UserVariables = nil
for k, v := range c.template.Variables { for k, v := range c.Template.Variables {
// Ignore variables that are required // Ignore variables that are required
if v.Required { if v.Required {
continue continue
@ -266,12 +275,10 @@ func (c *Core) init() error {
c.variables[k] = def 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 return nil
} }
func (c *Core) context() *interpolate.Context {
return &interpolate.Context{
TemplatePath: c.template.Path,
UserVariables: c.variables,
}
}

View File

@ -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) { func TestCoreValidate(t *testing.T) {
cases := []struct { cases := []struct {
File string File string

View File

@ -0,0 +1,11 @@
{
"variables": {
"foo": null
},
"builders": [{"type": "test"}],
"push": {
"name": "{{user `foo`}}"
}
}

View File

@ -5,6 +5,47 @@ import (
"testing" "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) { func TestRenderMap(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
Input interface{} Input interface{}