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{