From 96b0ec5395ca84f6de36be78b834c82bcfd7ad4b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Oct 2014 20:21:13 -0700 Subject: [PATCH 01/10] Start putting commands in command/, modify core --- command/build.go | 309 +++++++++++++++++++++++++++++++++++++++++++++++ command/meta.go | 15 +++ commands.go | 61 ++++++++++ config.go | 3 + packer.go | 34 +++--- 5 files changed, 405 insertions(+), 17 deletions(-) create mode 100644 command/build.go create mode 100644 command/meta.go create mode 100644 commands.go diff --git a/command/build.go b/command/build.go new file mode 100644 index 000000000..a0b33e530 --- /dev/null +++ b/command/build.go @@ -0,0 +1,309 @@ +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" +) + +type BuildCommand struct { + Meta +} + +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)) + 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() + if len(args) != 1 { + cmdFlags.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() + if err != nil { + env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err)) + env.Ui().Error("") + env.Ui().Error(c.Help()) + 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) + if err != nil { + env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err)) + return 1 + } + + // The component finder for our builds + components := &packer.ComponentFinder{ + Builder: env.Builder, + Hook: env.Hook, + PostProcessor: env.PostProcessor, + Provisioner: env.Provisioner, + } + + // 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 + } + + if cfgDebug { + env.Ui().Say("Debug mode enabled. Builds will not be parallelized.") + } + + // Compile all the UIs for the builds + colors := [5]packer.UiColor{ + packer.UiColorGreen, + packer.UiColorCyan, + packer.UiColorMagenta, + packer.UiColorYellow, + packer.UiColorBlue, + } + + buildUis := make(map[string]packer.Ui) + for i, b := range builds { + var ui packer.Ui + ui = env.Ui() + if cfgColor { + ui = &packer.ColoredUi{ + Color: colors[i%len(colors)], + Ui: env.Ui(), + } + } + + buildUis[b.Name()] = ui + ui.Say(fmt.Sprintf("%s output will be in this color.", b.Name())) + } + + // Add a newline between the color output and the actual output + env.Ui().Say("") + + log.Printf("Build debug mode: %v", cfgDebug) + log.Printf("Force build: %v", cfgForce) + + // Set the debug and force mode and prepare all the builds + for _, b := range builds { + log.Printf("Preparing build: %s", b.Name()) + b.SetDebug(cfgDebug) + b.SetForce(cfgForce) + + warnings, err := b.Prepare() + if err != nil { + env.Ui().Error(err.Error()) + return 1 + } + if len(warnings) > 0 { + ui := buildUis[b.Name()] + ui.Say(fmt.Sprintf("Warnings for build '%s':\n", b.Name())) + for _, warning := range warnings { + ui.Say(fmt.Sprintf("* %s", warning)) + } + ui.Say("") + } + } + + // Run all the builds in parallel and wait for them to complete + var interruptWg, wg sync.WaitGroup + interrupted := false + artifacts := make(map[string][]packer.Artifact) + errors := make(map[string]error) + for _, b := range builds { + // Increment the waitgroup so we wait for this item to finish properly + wg.Add(1) + + // Handle interrupts for this build + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt) + defer signal.Stop(sigCh) + go func(b packer.Build) { + <-sigCh + interruptWg.Add(1) + defer interruptWg.Done() + interrupted = true + + log.Printf("Stopping build: %s", b.Name()) + b.Cancel() + log.Printf("Build cancelled: %s", b.Name()) + }(b) + + // Run the build in a goroutine + go func(b packer.Build) { + defer wg.Done() + + name := b.Name() + log.Printf("Starting build run: %s", name) + ui := buildUis[name] + runArtifacts, err := b.Run(ui, env.Cache()) + + if err != nil { + ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err)) + errors[name] = err + } else { + ui.Say(fmt.Sprintf("Build '%s' finished.", name)) + artifacts[name] = runArtifacts + } + }(b) + + if cfgDebug { + log.Printf("Debug enabled, so waiting for build to finish: %s", b.Name()) + wg.Wait() + } + + if !cfgParallel { + log.Printf("Parallelization disabled, waiting for build to finish: %s", b.Name()) + wg.Wait() + } + + if interrupted { + log.Println("Interrupted, not going to start any more builds.") + break + } + } + + // Wait for both the builds to complete and the interrupt handler, + // if it is interrupted. + log.Printf("Waiting on builds to complete...") + wg.Wait() + + log.Printf("Builds completed. Waiting on interrupt barrier...") + interruptWg.Wait() + + if interrupted { + env.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)) + + env.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.Machine("error", err.Error()) + + env.Ui().Error(fmt.Sprintf("--> %s: %s", name, err)) + } + } + + if len(artifacts) > 0 { + env.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(), + } + + // Machine-readable helpful + ui.Machine("artifact-count", strconv.FormatInt(int64(len(buildArtifacts)), 10)) + + for i, artifact := range buildArtifacts { + var message bytes.Buffer + fmt.Fprintf(&message, "--> %s: ", name) + + if artifact != nil { + fmt.Fprintf(&message, artifact.String()) + } else { + fmt.Fprint(&message, "") + } + + iStr := strconv.FormatInt(int64(i), 10) + if artifact != nil { + ui.Machine("artifact", iStr, "builder-id", artifact.BuilderId()) + ui.Machine("artifact", iStr, "id", artifact.Id()) + ui.Machine("artifact", iStr, "string", artifact.String()) + + files := artifact.Files() + ui.Machine("artifact", + iStr, + "files-count", strconv.FormatInt(int64(len(files)), 10)) + for fi, file := range files { + fiStr := strconv.FormatInt(int64(fi), 10) + ui.Machine("artifact", iStr, "file", fiStr, file) + } + } else { + ui.Machine("artifact", iStr, "nil") + } + + ui.Machine("artifact", iStr, "end") + env.Ui().Say(message.String()) + } + } + } else { + env.Ui().Say("\n==> Builds finished but no artifacts were created.") + } + + if len(errors) > 0 { + // If any errors occurred, exit with a non-zero exit status + return 1 + } + + return 0 +} + +func (BuildCommand) Help() string { + helpText := ` +Usage: packer build [options] TEMPLATE + + Will execute multiple builds in parallel as defined in the template. + The various artifacts created by the template will be outputted. + +Options: + + -debug Debug mode enabled for builds + -force Force a build to continue if artifacts exist, deletes existing artifacts + -machine-readable Machine-readable output + -except=foo,bar,baz Build all builds other than these + -only=foo,bar,baz Only build the given builds by name + -parallel=false Disable parallelization (on by default) + -var 'key=value' Variable for templates, can be used multiple times. + -var-file=path JSON file containing user variables. +` + + return strings.TrimSpace(helpText) +} + +func (BuildCommand) Synopsis() string { + return "build image(s) from template" +} diff --git a/command/meta.go b/command/meta.go new file mode 100644 index 000000000..9c2f7f921 --- /dev/null +++ b/command/meta.go @@ -0,0 +1,15 @@ +package command + +import ( + "github.com/mitchellh/cli" + "github.com/mitchellh/packer/packer" +) + +type Meta struct { + EnvConfig *packer.EnvironmentConfig + Ui cli.Ui +} + +func (m *Meta) Environment() (packer.Environment, error) { + return packer.NewEnvironment(m.EnvConfig) +} diff --git a/commands.go b/commands.go new file mode 100644 index 000000000..215ec3dab --- /dev/null +++ b/commands.go @@ -0,0 +1,61 @@ +package main + +import ( + "os" + "os/signal" + + "github.com/mitchellh/cli" + "github.com/mitchellh/packer/command" +) + +// Commands is the mapping of all the available Terraform commands. +var Commands map[string]cli.CommandFactory + +// Ui is the cli.Ui used for communicating to the outside world. +var Ui cli.Ui + +const ErrorPrefix = "e:" +const OutputPrefix = "o:" + +func init() { + Ui = &cli.BasicUi{Writer: os.Stdout} + /* + Ui = &cli.PrefixedUi{ + AskPrefix: OutputPrefix, + OutputPrefix: OutputPrefix, + InfoPrefix: OutputPrefix, + ErrorPrefix: ErrorPrefix, + Ui: &cli.BasicUi{Writer: os.Stdout}, + } + */ + + meta := command.Meta{ + EnvConfig: &EnvConfig, + Ui: Ui, + } + + Commands = map[string]cli.CommandFactory{ + "build": func() (cli.Command, error) { + return &command.BuildCommand{ + Meta: meta, + }, nil + }, + } +} + +// makeShutdownCh creates an interrupt listener and returns a channel. +// A message will be sent on the channel for every interrupt received. +func makeShutdownCh() <-chan struct{} { + resultCh := make(chan struct{}) + + signalCh := make(chan os.Signal, 4) + signal.Notify(signalCh, os.Interrupt) + go func() { + for { + <-signalCh + resultCh <- struct{}{} + } + }() + + return resultCh +} diff --git a/config.go b/config.go index 385722969..4f5e6eba6 100644 --- a/config.go +++ b/config.go @@ -13,6 +13,9 @@ import ( "github.com/mitchellh/packer/packer/plugin" ) +// 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/packer.go b/packer.go index 14b67db22..2fe63aa96 100644 --- a/packer.go +++ b/packer.go @@ -10,6 +10,7 @@ import ( "path/filepath" "runtime" + "github.com/mitchellh/cli" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer/plugin" "github.com/mitchellh/panicwrap" @@ -118,16 +119,14 @@ func wrappedMain() int { defer plugin.CleanupClients() // Create the environment configuration - envConfig := packer.DefaultEnvironmentConfig() - envConfig.Cache = cache - envConfig.Commands = config.CommandNames() - envConfig.Components.Builder = config.LoadBuilder - envConfig.Components.Command = config.LoadCommand - envConfig.Components.Hook = config.LoadHook - envConfig.Components.PostProcessor = config.LoadPostProcessor - envConfig.Components.Provisioner = config.LoadProvisioner + 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 if machineReadable { - envConfig.Ui = &packer.MachineReadableUi{ + EnvConfig.Ui = &packer.MachineReadableUi{ Writer: os.Stdout, } @@ -139,17 +138,18 @@ func wrappedMain() int { } } - env, err := packer.NewEnvironment(envConfig) - if err != nil { - fmt.Fprintf(os.Stderr, "Packer initialization error: \n\n%s\n", err) - return 1 + //setupSignalHandlers(env) + + cli := &cli.CLI{ + Args: args, + Commands: Commands, + HelpFunc: cli.BasicHelpFunc("packer"), + HelpWriter: os.Stdout, } - setupSignalHandlers(env) - - exitCode, err := env.Cli(args) + exitCode, err := cli.Run() if err != nil { - fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error()) + fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err) return 1 } From 8054e66db66fae64ca4415a146607a44df1c9b9d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Oct 2014 20:31:02 -0700 Subject: [PATCH 02/10] command: move more to this package, remove old packages --- command/build/command.go | 283 ------------------- command/build/command_test.go | 54 ---- command/build/help.go | 19 -- command/{inspect/command.go => inspect.go} | 38 ++- command/inspect/command_test.go | 14 - command/inspect/help.go | 13 - command/{validate/command.go => validate.go} | 41 ++- command/validate/command_test.go | 14 - command/validate/help.go | 20 -- commands.go | 29 +- log.go | 29 ++ packer.go | 159 +++++++---- plugin/command-build/main.go | 15 - plugin/command-build/main_test.go | 1 - plugin/command-inspect/main.go | 15 - plugin/command-inspect/main_test.go | 1 - plugin/command-validate/main.go | 15 - plugin/command-validate/main_test.go | 1 - 18 files changed, 213 insertions(+), 548 deletions(-) delete mode 100644 command/build/command.go delete mode 100644 command/build/command_test.go delete mode 100644 command/build/help.go rename command/{inspect/command.go => inspect.go} (82%) delete mode 100644 command/inspect/command_test.go delete mode 100644 command/inspect/help.go rename command/{validate/command.go => validate.go} (72%) delete mode 100644 command/validate/command_test.go delete mode 100644 command/validate/help.go create mode 100644 log.go delete mode 100644 plugin/command-build/main.go delete mode 100644 plugin/command-build/main_test.go delete mode 100644 plugin/command-inspect/main.go delete mode 100644 plugin/command-inspect/main_test.go delete mode 100644 plugin/command-validate/main.go delete mode 100644 plugin/command-validate/main_test.go diff --git a/command/build/command.go b/command/build/command.go deleted file mode 100644 index ee2119a22..000000000 --- a/command/build/command.go +++ /dev/null @@ -1,283 +0,0 @@ -package build - -import ( - "bytes" - "flag" - "fmt" - cmdcommon "github.com/mitchellh/packer/common/command" - "github.com/mitchellh/packer/packer" - "log" - "os" - "os/signal" - "strconv" - "strings" - "sync" -) - -type Command byte - -func (Command) Help() string { - return strings.TrimSpace(helpText) -} - -func (c Command) Run(env packer.Environment, args []string) int { - var cfgColor, cfgDebug, cfgForce, cfgParallel bool - buildOptions := new(cmdcommon.BuildOptions) - - 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() - if len(args) != 1 { - cmdFlags.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() - if err != nil { - env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err)) - env.Ui().Error("") - env.Ui().Error(c.Help()) - 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) - if err != nil { - env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err)) - return 1 - } - - // The component finder for our builds - components := &packer.ComponentFinder{ - Builder: env.Builder, - Hook: env.Hook, - PostProcessor: env.PostProcessor, - Provisioner: env.Provisioner, - } - - // 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 - } - - if cfgDebug { - env.Ui().Say("Debug mode enabled. Builds will not be parallelized.") - } - - // Compile all the UIs for the builds - colors := [5]packer.UiColor{ - packer.UiColorGreen, - packer.UiColorCyan, - packer.UiColorMagenta, - packer.UiColorYellow, - packer.UiColorBlue, - } - - buildUis := make(map[string]packer.Ui) - for i, b := range builds { - var ui packer.Ui - ui = env.Ui() - if cfgColor { - ui = &packer.ColoredUi{ - Color: colors[i%len(colors)], - Ui: env.Ui(), - } - } - - buildUis[b.Name()] = ui - ui.Say(fmt.Sprintf("%s output will be in this color.", b.Name())) - } - - // Add a newline between the color output and the actual output - env.Ui().Say("") - - log.Printf("Build debug mode: %v", cfgDebug) - log.Printf("Force build: %v", cfgForce) - - // Set the debug and force mode and prepare all the builds - for _, b := range builds { - log.Printf("Preparing build: %s", b.Name()) - b.SetDebug(cfgDebug) - b.SetForce(cfgForce) - - warnings, err := b.Prepare() - if err != nil { - env.Ui().Error(err.Error()) - return 1 - } - if len(warnings) > 0 { - ui := buildUis[b.Name()] - ui.Say(fmt.Sprintf("Warnings for build '%s':\n", b.Name())) - for _, warning := range warnings { - ui.Say(fmt.Sprintf("* %s", warning)) - } - ui.Say("") - } - } - - // Run all the builds in parallel and wait for them to complete - var interruptWg, wg sync.WaitGroup - interrupted := false - artifacts := make(map[string][]packer.Artifact) - errors := make(map[string]error) - for _, b := range builds { - // Increment the waitgroup so we wait for this item to finish properly - wg.Add(1) - - // Handle interrupts for this build - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, os.Interrupt) - defer signal.Stop(sigCh) - go func(b packer.Build) { - <-sigCh - interruptWg.Add(1) - defer interruptWg.Done() - interrupted = true - - log.Printf("Stopping build: %s", b.Name()) - b.Cancel() - log.Printf("Build cancelled: %s", b.Name()) - }(b) - - // Run the build in a goroutine - go func(b packer.Build) { - defer wg.Done() - - name := b.Name() - log.Printf("Starting build run: %s", name) - ui := buildUis[name] - runArtifacts, err := b.Run(ui, env.Cache()) - - if err != nil { - ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err)) - errors[name] = err - } else { - ui.Say(fmt.Sprintf("Build '%s' finished.", name)) - artifacts[name] = runArtifacts - } - }(b) - - if cfgDebug { - log.Printf("Debug enabled, so waiting for build to finish: %s", b.Name()) - wg.Wait() - } - - if !cfgParallel { - log.Printf("Parallelization disabled, waiting for build to finish: %s", b.Name()) - wg.Wait() - } - - if interrupted { - log.Println("Interrupted, not going to start any more builds.") - break - } - } - - // Wait for both the builds to complete and the interrupt handler, - // if it is interrupted. - log.Printf("Waiting on builds to complete...") - wg.Wait() - - log.Printf("Builds completed. Waiting on interrupt barrier...") - interruptWg.Wait() - - if interrupted { - env.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)) - - env.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.Machine("error", err.Error()) - - env.Ui().Error(fmt.Sprintf("--> %s: %s", name, err)) - } - } - - if len(artifacts) > 0 { - env.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(), - } - - // Machine-readable helpful - ui.Machine("artifact-count", strconv.FormatInt(int64(len(buildArtifacts)), 10)) - - for i, artifact := range buildArtifacts { - var message bytes.Buffer - fmt.Fprintf(&message, "--> %s: ", name) - - if artifact != nil { - fmt.Fprintf(&message, artifact.String()) - } else { - fmt.Fprint(&message, "") - } - - iStr := strconv.FormatInt(int64(i), 10) - if artifact != nil { - ui.Machine("artifact", iStr, "builder-id", artifact.BuilderId()) - ui.Machine("artifact", iStr, "id", artifact.Id()) - ui.Machine("artifact", iStr, "string", artifact.String()) - - files := artifact.Files() - ui.Machine("artifact", - iStr, - "files-count", strconv.FormatInt(int64(len(files)), 10)) - for fi, file := range files { - fiStr := strconv.FormatInt(int64(fi), 10) - ui.Machine("artifact", iStr, "file", fiStr, file) - } - } else { - ui.Machine("artifact", iStr, "nil") - } - - ui.Machine("artifact", iStr, "end") - env.Ui().Say(message.String()) - } - } - } else { - env.Ui().Say("\n==> Builds finished but no artifacts were created.") - } - - if len(errors) > 0 { - // If any errors occurred, exit with a non-zero exit status - return 1 - } - - return 0 -} - -func (Command) Synopsis() string { - return "build image(s) from template" -} diff --git a/command/build/command_test.go b/command/build/command_test.go deleted file mode 100644 index 2edd82b4a..000000000 --- a/command/build/command_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package build - -import ( - "bytes" - "github.com/mitchellh/packer/packer" - "testing" -) - -func testEnvironment() packer.Environment { - config := packer.DefaultEnvironmentConfig() - config.Ui = &packer.BasicUi{ - Reader: new(bytes.Buffer), - Writer: new(bytes.Buffer), - } - - env, err := packer.NewEnvironment(config) - if err != nil { - panic(err) - } - - return env -} - -func TestCommand_Implements(t *testing.T) { - var _ packer.Command = new(Command) -} - -func TestCommand_Run_NoArgs(t *testing.T) { - command := new(Command) - result := command.Run(testEnvironment(), make([]string, 0)) - if result != 1 { - t.Fatalf("bad: %d", result) - } -} - -func TestCommand_Run_MoreThanOneArg(t *testing.T) { - command := new(Command) - - args := []string{"one", "two"} - result := command.Run(testEnvironment(), args) - if result != 1 { - t.Fatalf("bad: %d", result) - } -} - -func TestCommand_Run_MissingFile(t *testing.T) { - command := new(Command) - - args := []string{"i-better-not-exist"} - result := command.Run(testEnvironment(), args) - if result != 1 { - t.Fatalf("bad: %d", result) - } -} diff --git a/command/build/help.go b/command/build/help.go deleted file mode 100644 index 5b27a53ad..000000000 --- a/command/build/help.go +++ /dev/null @@ -1,19 +0,0 @@ -package build - -const helpText = ` -Usage: packer build [options] TEMPLATE - - Will execute multiple builds in parallel as defined in the template. - The various artifacts created by the template will be outputted. - -Options: - - -debug Debug mode enabled for builds - -force Force a build to continue if artifacts exist, deletes existing artifacts - -machine-readable Machine-readable output - -except=foo,bar,baz Build all builds other than these - -only=foo,bar,baz Only build the given builds by name - -parallel=false Disable parallelization (on by default) - -var 'key=value' Variable for templates, can be used multiple times. - -var-file=path JSON file containing user variables. -` diff --git a/command/inspect/command.go b/command/inspect.go similarity index 82% rename from command/inspect/command.go rename to command/inspect.go index 25d8427cc..8a9fd9569 100644 --- a/command/inspect/command.go +++ b/command/inspect.go @@ -1,4 +1,4 @@ -package inspect +package command import ( "flag" @@ -9,17 +9,17 @@ import ( "strings" ) -type Command struct{} - -func (Command) Help() string { - return strings.TrimSpace(helpText) +type InspectCommand struct{ + Meta } -func (c Command) Synopsis() string { - return "see components of a template" -} +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 + } -func (c Command) Run(env packer.Environment, args []string) int { flags := flag.NewFlagSet("inspect", flag.ContinueOnError) flags.Usage = func() { env.Ui().Say(c.Help()) } if err := flags.Parse(args); err != nil { @@ -148,3 +148,23 @@ func (c Command) Run(env packer.Environment, args []string) int { return 0 } + +func (*InspectCommand) Help() string { + helpText := ` +Usage: packer inspect TEMPLATE + + Inspects a template, parsing and outputting the components a template + defines. This does not validate the contents of a template (other than + basic syntax by necessity). + +Options: + + -machine-readable Machine-readable output +` + + return strings.TrimSpace(helpText) +} + +func (c *InspectCommand) Synopsis() string { + return "see components of a template" +} diff --git a/command/inspect/command_test.go b/command/inspect/command_test.go deleted file mode 100644 index a2766fef4..000000000 --- a/command/inspect/command_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package inspect - -import ( - "github.com/mitchellh/packer/packer" - "testing" -) - -func TestCommand_Impl(t *testing.T) { - var raw interface{} - raw = new(Command) - if _, ok := raw.(packer.Command); !ok { - t.Fatalf("must be a Command") - } -} diff --git a/command/inspect/help.go b/command/inspect/help.go deleted file mode 100644 index 320f24e8f..000000000 --- a/command/inspect/help.go +++ /dev/null @@ -1,13 +0,0 @@ -package inspect - -const helpText = ` -Usage: packer inspect TEMPLATE - - Inspects a template, parsing and outputting the components a template - defines. This does not validate the contents of a template (other than - basic syntax by necessity). - -Options: - - -machine-readable Machine-readable output -` diff --git a/command/validate/command.go b/command/validate.go similarity index 72% rename from command/validate/command.go rename to command/validate.go index 838ba13b5..a63019a9c 100644 --- a/command/validate/command.go +++ b/command/validate.go @@ -1,4 +1,4 @@ -package validate +package command import ( "flag" @@ -9,16 +9,20 @@ import ( "strings" ) -type Command byte - -func (Command) Help() string { - return strings.TrimSpace(helpString) +type ValidateCommand struct { + Meta } -func (c Command) Run(env packer.Environment, args []string) int { +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)) + 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") @@ -123,6 +127,29 @@ func (c Command) Run(env packer.Environment, args []string) int { return 0 } -func (Command) Synopsis() string { +func (*ValidateCommand) Help() string { + helpText := ` +Usage: packer validate [options] TEMPLATE + + Checks the template is valid by parsing the template and also + checking the configuration with the various builders, provisioners, etc. + + If it is not valid, the errors will be shown and the command will exit + with a non-zero exit status. If it is valid, it will exit with a zero + exit status. + +Options: + + -syntax-only Only check syntax. Do not verify config of the template. + -except=foo,bar,baz Validate all builds other than these + -only=foo,bar,baz Validate only these builds + -var 'key=value' Variable for templates, can be used multiple times. + -var-file=path JSON file containing user variables. +` + + return strings.TrimSpace(helpText) +} + +func (*ValidateCommand) Synopsis() string { return "check that a template is valid" } diff --git a/command/validate/command_test.go b/command/validate/command_test.go deleted file mode 100644 index 33fab19dd..000000000 --- a/command/validate/command_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package validate - -import ( - "github.com/mitchellh/packer/packer" - "testing" -) - -func TestCommand_Impl(t *testing.T) { - var raw interface{} - raw = new(Command) - if _, ok := raw.(packer.Command); !ok { - t.Fatalf("must be a Command") - } -} diff --git a/command/validate/help.go b/command/validate/help.go deleted file mode 100644 index 8959cd881..000000000 --- a/command/validate/help.go +++ /dev/null @@ -1,20 +0,0 @@ -package validate - -const helpString = ` -Usage: packer validate [options] TEMPLATE - - Checks the template is valid by parsing the template and also - checking the configuration with the various builders, provisioners, etc. - - If it is not valid, the errors will be shown and the command will exit - with a non-zero exit status. If it is valid, it will exit with a zero - exit status. - -Options: - - -syntax-only Only check syntax. Do not verify config of the template. - -except=foo,bar,baz Validate all builds other than these - -only=foo,bar,baz Validate only these builds - -var 'key=value' Variable for templates, can be used multiple times. - -var-file=path JSON file containing user variables. -` diff --git a/commands.go b/commands.go index 215ec3dab..2b5179edd 100644 --- a/commands.go +++ b/commands.go @@ -18,16 +18,13 @@ const ErrorPrefix = "e:" const OutputPrefix = "o:" func init() { - Ui = &cli.BasicUi{Writer: os.Stdout} - /* - Ui = &cli.PrefixedUi{ - AskPrefix: OutputPrefix, - OutputPrefix: OutputPrefix, - InfoPrefix: OutputPrefix, - ErrorPrefix: ErrorPrefix, - Ui: &cli.BasicUi{Writer: os.Stdout}, - } - */ + Ui = &cli.PrefixedUi{ + AskPrefix: OutputPrefix, + OutputPrefix: OutputPrefix, + InfoPrefix: OutputPrefix, + ErrorPrefix: ErrorPrefix, + Ui: &cli.BasicUi{Writer: os.Stdout}, + } meta := command.Meta{ EnvConfig: &EnvConfig, @@ -40,6 +37,18 @@ func init() { Meta: meta, }, nil }, + + "inspect": func() (cli.Command, error) { + return &command.InspectCommand{ + Meta: meta, + }, nil + }, + + "validate": func() (cli.Command, error) { + return &command.ValidateCommand{ + Meta: meta, + }, nil + }, } } diff --git a/log.go b/log.go new file mode 100644 index 000000000..ad3593c55 --- /dev/null +++ b/log.go @@ -0,0 +1,29 @@ +package main + +import ( + "io" + "os" +) + +// These are the environmental variables that determine if we log, and if +// we log whether or not the log should go to a file. +const EnvLog = "PACKER_LOG" //Set to True +const EnvLogFile = "PACKER_LOG_PATH" //Set to a file + +// logOutput determines where we should send logs (if anywhere). +func logOutput() (logOutput io.Writer, err error) { + logOutput = nil + if os.Getenv(EnvLog) != "" { + logOutput = os.Stderr + + if logPath := os.Getenv(EnvLogFile); logPath != "" { + var err error + logOutput, err = os.Create(logPath) + if err != nil { + return nil, err + } + } + } + + return +} diff --git a/packer.go b/packer.go index 2fe63aa96..f72c876b4 100644 --- a/packer.go +++ b/packer.go @@ -9,11 +9,13 @@ import ( "os" "path/filepath" "runtime" + "sync" "github.com/mitchellh/cli" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer/plugin" "github.com/mitchellh/panicwrap" + "github.com/mitchellh/prefixedio" ) func main() { @@ -25,58 +27,77 @@ func main() { // realMain is executed from main and returns the exit status to exit with. func realMain() int { - // If there is no explicit number of Go threads to use, then set it - if os.Getenv("GOMAXPROCS") == "" { - runtime.GOMAXPROCS(runtime.NumCPU()) + var wrapConfig panicwrap.WrapConfig + + if !panicwrap.Wrapped(&wrapConfig) { + // Determine where logs should go in general (requested by the user) + logWriter, err := logOutput() + if err != nil { + fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err) + return 1 + } + if logWriter == nil { + logWriter = ioutil.Discard + } + + // We always send logs to a temporary file that we use in case + // there is a panic. Otherwise, we delete it. + logTempFile, err := ioutil.TempFile("", "packer-log") + if err != nil { + fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err) + return 1 + } + defer os.Remove(logTempFile.Name()) + defer logTempFile.Close() + + // Tell the logger to log to this file + os.Setenv(EnvLog, "") + os.Setenv(EnvLogFile, "") + + // Setup the prefixed readers that send data properly to + // stdout/stderr. + doneCh := make(chan struct{}) + outR, outW := io.Pipe() + go copyOutput(outR, doneCh) + + // Create the configuration for panicwrap and wrap our executable + wrapConfig.Handler = panicHandler(logTempFile) + wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter) + wrapConfig.Stdout = outW + exitStatus, err := panicwrap.Wrap(&wrapConfig) + if err != nil { + fmt.Fprintf(os.Stderr, "Couldn't start Packer: %s", err) + return 1 + } + + // If >= 0, we're the parent, so just exit + if exitStatus >= 0 { + // Close the stdout writer so that our copy process can finish + outW.Close() + + // Wait for the output copying to finish + <-doneCh + + return exitStatus + } + + // We're the child, so just close the tempfile we made in order to + // save file handles since the tempfile is only used by the parent. + logTempFile.Close() } - // Determine where logs should go in general (requested by the user) - logWriter, err := logOutput() - if err != nil { - fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err) - return 1 - } - - // We also always send logs to a temporary file that we use in case - // there is a panic. Otherwise, we delete it. - logTempFile, err := ioutil.TempFile("", "packer-log") - if err != nil { - fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err) - return 1 - } - defer os.Remove(logTempFile.Name()) - defer logTempFile.Close() - - // Reset the log variables to minimize work in the subprocess - os.Setenv("PACKER_LOG", "") - os.Setenv("PACKER_LOG_FILE", "") - - // Create the configuration for panicwrap and wrap our executable - wrapConfig := &panicwrap.WrapConfig{ - Handler: panicHandler(logTempFile), - Writer: io.MultiWriter(logTempFile, logWriter), - } - - exitStatus, err := panicwrap.Wrap(wrapConfig) - if err != nil { - fmt.Fprintf(os.Stderr, "Couldn't start Packer: %s", err) - return 1 - } - - if exitStatus >= 0 { - return exitStatus - } - - // We're the child, so just close the tempfile we made in order to - // save file handles since the tempfile is only used by the parent. - logTempFile.Close() - + // Call the real main return wrappedMain() } // wrappedMain is called only when we're wrapped by panicwrap and // returns the exit status to exit with. func wrappedMain() int { + // If there is no explicit number of Go threads to use, then set it + if os.Getenv("GOMAXPROCS") == "" { + runtime.GOMAXPROCS(runtime.NumCPU()) + } + log.SetOutput(os.Stderr) log.Printf( @@ -220,20 +241,44 @@ func loadConfig() (*config, error) { return &config, nil } -// logOutput determines where we should send logs (if anywhere). -func logOutput() (logOutput io.Writer, err error) { - logOutput = ioutil.Discard - if os.Getenv("PACKER_LOG") != "" { - logOutput = os.Stderr +// copyOutput uses output prefixes to determine whether data on stdout +// should go to stdout or stderr. This is due to panicwrap using stderr +// as the log and error channel. +func copyOutput(r io.Reader, doneCh chan<- struct{}) { + defer close(doneCh) - if logPath := os.Getenv("PACKER_LOG_PATH"); logPath != "" { - var err error - logOutput, err = os.Create(logPath) - if err != nil { - return nil, err - } - } + pr, err := prefixedio.NewReader(r) + if err != nil { + panic(err) } - return + stderrR, err := pr.Prefix(ErrorPrefix) + if err != nil { + panic(err) + } + stdoutR, err := pr.Prefix(OutputPrefix) + if err != nil { + panic(err) + } + defaultR, err := pr.Prefix("") + if err != nil { + panic(err) + } + + var wg sync.WaitGroup + wg.Add(3) + go func() { + defer wg.Done() + io.Copy(os.Stderr, stderrR) + }() + go func() { + defer wg.Done() + io.Copy(os.Stdout, stdoutR) + }() + go func() { + defer wg.Done() + io.Copy(os.Stdout, defaultR) + }() + + wg.Wait() } diff --git a/plugin/command-build/main.go b/plugin/command-build/main.go deleted file mode 100644 index 1285e1e7a..000000000 --- a/plugin/command-build/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "github.com/mitchellh/packer/command/build" - "github.com/mitchellh/packer/packer/plugin" -) - -func main() { - server, err := plugin.Server() - if err != nil { - panic(err) - } - server.RegisterCommand(new(build.Command)) - server.Serve() -} diff --git a/plugin/command-build/main_test.go b/plugin/command-build/main_test.go deleted file mode 100644 index 06ab7d0f9..000000000 --- a/plugin/command-build/main_test.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/plugin/command-inspect/main.go b/plugin/command-inspect/main.go deleted file mode 100644 index 9aeedc34c..000000000 --- a/plugin/command-inspect/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "github.com/mitchellh/packer/command/inspect" - "github.com/mitchellh/packer/packer/plugin" -) - -func main() { - server, err := plugin.Server() - if err != nil { - panic(err) - } - server.RegisterCommand(new(inspect.Command)) - server.Serve() -} diff --git a/plugin/command-inspect/main_test.go b/plugin/command-inspect/main_test.go deleted file mode 100644 index 06ab7d0f9..000000000 --- a/plugin/command-inspect/main_test.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/plugin/command-validate/main.go b/plugin/command-validate/main.go deleted file mode 100644 index 1105ed75e..000000000 --- a/plugin/command-validate/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "github.com/mitchellh/packer/command/validate" - "github.com/mitchellh/packer/packer/plugin" -) - -func main() { - server, err := plugin.Server() - if err != nil { - panic(err) - } - server.RegisterCommand(new(validate.Command)) - server.Serve() -} diff --git a/plugin/command-validate/main_test.go b/plugin/command-validate/main_test.go deleted file mode 100644 index 06ab7d0f9..000000000 --- a/plugin/command-validate/main_test.go +++ /dev/null @@ -1 +0,0 @@ -package main From fa36cf82ee8c63252a210eca2920685e24907b4f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Oct 2014 20:34:49 -0700 Subject: [PATCH 03/10] command: move all remaining commands --- command/{fix/command.go => fix.go} | 49 +++++++++++++++---- command/fix/command_test.go | 14 ------ command/fix/help.go | 22 --------- commands.go | 6 +++ {command/fix => fix}/fixer.go | 0 {command/fix => fix}/fixer_createtime.go | 0 {command/fix => fix}/fixer_createtime_test.go | 0 {command/fix => fix}/fixer_iso_md5.go | 0 {command/fix => fix}/fixer_iso_md5_test.go | 0 .../fix => fix}/fixer_pp_vagrant_override.go | 0 .../fixer_pp_vagrant_override_test.go | 0 .../fix => fix}/fixer_virtualbox_gaattach.go | 0 .../fixer_virtualbox_gaattach_test.go | 0 .../fix => fix}/fixer_virtualbox_rename.go | 0 .../fixer_virtualbox_rename_test.go | 0 {command/fix => fix}/fixer_vmware_rename.go | 0 .../fix => fix}/fixer_vmware_rename_test.go | 0 plugin/command-fix/main.go | 15 ------ plugin/command-fix/main_test.go | 1 - 19 files changed, 45 insertions(+), 62 deletions(-) rename command/{fix/command.go => fix.go} (55%) delete mode 100644 command/fix/command_test.go delete mode 100644 command/fix/help.go rename {command/fix => fix}/fixer.go (100%) rename {command/fix => fix}/fixer_createtime.go (100%) rename {command/fix => fix}/fixer_createtime_test.go (100%) rename {command/fix => fix}/fixer_iso_md5.go (100%) rename {command/fix => fix}/fixer_iso_md5_test.go (100%) rename {command/fix => fix}/fixer_pp_vagrant_override.go (100%) rename {command/fix => fix}/fixer_pp_vagrant_override_test.go (100%) rename {command/fix => fix}/fixer_virtualbox_gaattach.go (100%) rename {command/fix => fix}/fixer_virtualbox_gaattach_test.go (100%) rename {command/fix => fix}/fixer_virtualbox_rename.go (100%) rename {command/fix => fix}/fixer_virtualbox_rename_test.go (100%) rename {command/fix => fix}/fixer_vmware_rename.go (100%) rename {command/fix => fix}/fixer_vmware_rename_test.go (100%) delete mode 100644 plugin/command-fix/main.go delete mode 100644 plugin/command-fix/main_test.go diff --git a/command/fix/command.go b/command/fix.go similarity index 55% rename from command/fix/command.go rename to command/fix.go index c62d3676b..aac0b3916 100644 --- a/command/fix/command.go +++ b/command/fix.go @@ -1,23 +1,28 @@ -package fix +package command import ( "bytes" "encoding/json" "flag" "fmt" - "github.com/mitchellh/packer/packer" "log" "os" "strings" + + "github.com/mitchellh/packer/fix" ) -type Command byte - -func (Command) Help() string { - return strings.TrimSpace(helpString) +type FixCommand struct { + Meta } -func (c Command) Run(env packer.Environment, args []string) int { +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)) + return 1 + } + cmdFlags := flag.NewFlagSet("fix", flag.ContinueOnError) cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } if err := cmdFlags.Parse(args); err != nil { @@ -50,9 +55,9 @@ func (c Command) Run(env packer.Environment, args []string) int { tplF.Close() input := templateData - for _, name := range FixerOrder { + for _, name := range fix.FixerOrder { var err error - fixer, ok := Fixers[name] + fixer, ok := fix.Fixers[name] if !ok { panic("fixer not found: " + name) } @@ -85,6 +90,30 @@ func (c Command) Run(env packer.Environment, args []string) int { return 0 } -func (c Command) Synopsis() string { +func (*FixCommand) Help() string { + helpText := ` +Usage: packer fix [options] TEMPLATE + + Reads the JSON template and attempts to fix known backwards + incompatibilities. The fixed template will be outputted to standard out. + + If the template cannot be fixed due to an error, the command will exit + with a non-zero exit status. Error messages will appear on standard error. + +Fixes that are run: + + iso-md5 Replaces "iso_md5" in builders with newer "iso_checksum" + createtime Replaces ".CreateTime" in builder configs with "{{timestamp}}" + virtualbox-gaattach Updates VirtualBox builders using "guest_additions_attach" + to use "guest_additions_mode" + pp-vagrant-override Replaces old-style provider overrides for the Vagrant + post-processor to new-style as of Packer 0.5.0. + virtualbox-rename Updates "virtualbox" builders to "virtualbox-iso" +` + + return strings.TrimSpace(helpText) +} + +func (c *FixCommand) Synopsis() string { return "fixes templates from old versions of packer" } diff --git a/command/fix/command_test.go b/command/fix/command_test.go deleted file mode 100644 index e6c0c59e4..000000000 --- a/command/fix/command_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package fix - -import ( - "github.com/mitchellh/packer/packer" - "testing" -) - -func TestCommand_Impl(t *testing.T) { - var raw interface{} - raw = new(Command) - if _, ok := raw.(packer.Command); !ok { - t.Fatalf("must be a Command") - } -} diff --git a/command/fix/help.go b/command/fix/help.go deleted file mode 100644 index 95c5f3e16..000000000 --- a/command/fix/help.go +++ /dev/null @@ -1,22 +0,0 @@ -package fix - -const helpString = ` -Usage: packer fix [options] TEMPLATE - - Reads the JSON template and attempts to fix known backwards - incompatibilities. The fixed template will be outputted to standard out. - - If the template cannot be fixed due to an error, the command will exit - with a non-zero exit status. Error messages will appear on standard error. - -Fixes that are run: - - iso-md5 Replaces "iso_md5" in builders with newer "iso_checksum" - createtime Replaces ".CreateTime" in builder configs with "{{timestamp}}" - virtualbox-gaattach Updates VirtualBox builders using "guest_additions_attach" - to use "guest_additions_mode" - pp-vagrant-override Replaces old-style provider overrides for the Vagrant - post-processor to new-style as of Packer 0.5.0. - virtualbox-rename Updates "virtualbox" builders to "virtualbox-iso" - -` diff --git a/commands.go b/commands.go index 2b5179edd..8aa6314aa 100644 --- a/commands.go +++ b/commands.go @@ -38,6 +38,12 @@ func init() { }, nil }, + "fix": func() (cli.Command, error) { + return &command.FixCommand{ + Meta: meta, + }, nil + }, + "inspect": func() (cli.Command, error) { return &command.InspectCommand{ Meta: meta, diff --git a/command/fix/fixer.go b/fix/fixer.go similarity index 100% rename from command/fix/fixer.go rename to fix/fixer.go diff --git a/command/fix/fixer_createtime.go b/fix/fixer_createtime.go similarity index 100% rename from command/fix/fixer_createtime.go rename to fix/fixer_createtime.go diff --git a/command/fix/fixer_createtime_test.go b/fix/fixer_createtime_test.go similarity index 100% rename from command/fix/fixer_createtime_test.go rename to fix/fixer_createtime_test.go diff --git a/command/fix/fixer_iso_md5.go b/fix/fixer_iso_md5.go similarity index 100% rename from command/fix/fixer_iso_md5.go rename to fix/fixer_iso_md5.go diff --git a/command/fix/fixer_iso_md5_test.go b/fix/fixer_iso_md5_test.go similarity index 100% rename from command/fix/fixer_iso_md5_test.go rename to fix/fixer_iso_md5_test.go diff --git a/command/fix/fixer_pp_vagrant_override.go b/fix/fixer_pp_vagrant_override.go similarity index 100% rename from command/fix/fixer_pp_vagrant_override.go rename to fix/fixer_pp_vagrant_override.go diff --git a/command/fix/fixer_pp_vagrant_override_test.go b/fix/fixer_pp_vagrant_override_test.go similarity index 100% rename from command/fix/fixer_pp_vagrant_override_test.go rename to fix/fixer_pp_vagrant_override_test.go diff --git a/command/fix/fixer_virtualbox_gaattach.go b/fix/fixer_virtualbox_gaattach.go similarity index 100% rename from command/fix/fixer_virtualbox_gaattach.go rename to fix/fixer_virtualbox_gaattach.go diff --git a/command/fix/fixer_virtualbox_gaattach_test.go b/fix/fixer_virtualbox_gaattach_test.go similarity index 100% rename from command/fix/fixer_virtualbox_gaattach_test.go rename to fix/fixer_virtualbox_gaattach_test.go diff --git a/command/fix/fixer_virtualbox_rename.go b/fix/fixer_virtualbox_rename.go similarity index 100% rename from command/fix/fixer_virtualbox_rename.go rename to fix/fixer_virtualbox_rename.go diff --git a/command/fix/fixer_virtualbox_rename_test.go b/fix/fixer_virtualbox_rename_test.go similarity index 100% rename from command/fix/fixer_virtualbox_rename_test.go rename to fix/fixer_virtualbox_rename_test.go diff --git a/command/fix/fixer_vmware_rename.go b/fix/fixer_vmware_rename.go similarity index 100% rename from command/fix/fixer_vmware_rename.go rename to fix/fixer_vmware_rename.go diff --git a/command/fix/fixer_vmware_rename_test.go b/fix/fixer_vmware_rename_test.go similarity index 100% rename from command/fix/fixer_vmware_rename_test.go rename to fix/fixer_vmware_rename_test.go diff --git a/plugin/command-fix/main.go b/plugin/command-fix/main.go deleted file mode 100644 index 03677674d..000000000 --- a/plugin/command-fix/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "github.com/mitchellh/packer/command/fix" - "github.com/mitchellh/packer/packer/plugin" -) - -func main() { - server, err := plugin.Server() - if err != nil { - panic(err) - } - server.RegisterCommand(new(fix.Command)) - server.Serve() -} diff --git a/plugin/command-fix/main_test.go b/plugin/command-fix/main_test.go deleted file mode 100644 index 06ab7d0f9..000000000 --- a/plugin/command-fix/main_test.go +++ /dev/null @@ -1 +0,0 @@ -package main From 3e1f4ae1d2625cd2fc8b2fb5ea5490286835fe68 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Oct 2014 20:37:11 -0700 Subject: [PATCH 04/10] packer: remove Command --- packer/command.go | 23 ----- packer/command_test.go | 22 ----- packer/environment.go | 176 ------------------------------------- packer/environment_test.go | 121 ------------------------- 4 files changed, 342 deletions(-) delete mode 100644 packer/command.go delete mode 100644 packer/command_test.go diff --git a/packer/command.go b/packer/command.go deleted file mode 100644 index d861347eb..000000000 --- a/packer/command.go +++ /dev/null @@ -1,23 +0,0 @@ -package packer - -// A command is a runnable sub-command of the `packer` application. -// When `packer` is called with the proper subcommand, this will be -// called. -// -// The mapping of command names to command interfaces is in the -// Environment struct. -type Command interface { - // Help should return long-form help text that includes the command-line - // usage, a brief few sentences explaining the function of the command, - // and the complete list of flags the command accepts. - Help() string - - // Run should run the actual command with the given environmet and - // command-line arguments. It should return the exit status when it is - // finished. - Run(env Environment, args []string) int - - // Synopsis should return a one-line, short synopsis of the command. - // This should be less than 50 characters ideally. - Synopsis() string -} diff --git a/packer/command_test.go b/packer/command_test.go deleted file mode 100644 index 06348fdac..000000000 --- a/packer/command_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package packer - -type TestCommand struct { - runArgs []string - runCalled bool - runEnv Environment -} - -func (tc *TestCommand) Help() string { - return "bar" -} - -func (tc *TestCommand) Run(env Environment, args []string) int { - tc.runCalled = true - tc.runArgs = args - tc.runEnv = env - return 0 -} - -func (tc *TestCommand) Synopsis() string { - return "foo" -} diff --git a/packer/environment.go b/packer/environment.go index 510b849c6..58585ffcf 100644 --- a/packer/environment.go +++ b/packer/environment.go @@ -4,19 +4,12 @@ package packer import ( "errors" "fmt" - "log" "os" - "sort" - "strings" - "sync" ) // The function type used to lookup Builder implementations. type BuilderFunc func(name string) (Builder, error) -// The function type used to lookup Command implementations. -type CommandFunc func(name string) (Command, error) - // The function type used to lookup Hook implementations. type HookFunc func(name string) (Hook, error) @@ -31,7 +24,6 @@ type ProvisionerFunc func(name string) (Provisioner, error) // commands, etc. type ComponentFinder struct { Builder BuilderFunc - Command CommandFunc Hook HookFunc PostProcessor PostProcessorFunc Provisioner ProvisionerFunc @@ -45,7 +37,6 @@ type ComponentFinder struct { type Environment interface { Builder(string) (Builder, error) Cache() Cache - Cli([]string) (int, error) Hook(string) (Hook, error) PostProcessor(string) (PostProcessor, error) Provisioner(string) (Provisioner, error) @@ -56,7 +47,6 @@ type Environment interface { // environment. type coreEnvironment struct { cache Cache - commands []string components ComponentFinder ui Ui } @@ -64,22 +54,14 @@ type coreEnvironment struct { // This struct configures new environments. type EnvironmentConfig struct { Cache Cache - Commands []string Components ComponentFinder Ui Ui } -type helpCommandEntry struct { - i int - key string - synopsis string -} - // 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.Commands = make([]string, 0) config.Ui = &BasicUi{ Reader: os.Stdin, Writer: os.Stdout, @@ -98,7 +80,6 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error env := &coreEnvironment{} env.cache = config.Cache - env.commands = config.Commands env.components = config.Components env.ui = config.Ui @@ -109,10 +90,6 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error env.components.Builder = func(string) (Builder, error) { return nil, nil } } - if env.components.Command == nil { - env.components.Command = func(string) (Command, error) { return nil, nil } - } - if env.components.Hook == nil { env.components.Hook = func(string) (Hook, error) { return nil, nil } } @@ -199,159 +176,6 @@ func (e *coreEnvironment) Provisioner(name string) (p Provisioner, err error) { return } -// Executes a command as if it was typed on the command-line interface. -// The return value is the exit code of the command. -func (e *coreEnvironment) Cli(args []string) (result int, err error) { - log.Printf("Environment.Cli: %#v\n", args) - - // If we have no arguments, just short-circuit here and print the help - if len(args) == 0 { - e.printHelp() - return 1, nil - } - - // This variable will track whether or not we're supposed to print - // the help or not. - isHelp := false - for _, arg := range args { - if arg == "-h" || arg == "--help" { - isHelp = true - break - } - } - - // Trim up to the command name - for i, v := range args { - if len(v) > 0 && v[0] != '-' { - args = args[i:] - break - } - } - - log.Printf("command + args: %#v", args) - - version := args[0] == "version" - if !version { - for _, arg := range args { - if arg == "--version" || arg == "-v" { - version = true - break - } - } - } - - var command Command - if version { - command = new(versionCommand) - } - - if command == nil { - command, err = e.components.Command(args[0]) - if err != nil { - return - } - - // If we still don't have a command, show the help. - if command == nil { - e.ui.Error(fmt.Sprintf("Unknown command: %s\n", args[0])) - e.printHelp() - return 1, nil - } - } - - // If we're supposed to print help, then print the help of the - // command rather than running it. - if isHelp { - e.ui.Say(command.Help()) - return 0, nil - } - - log.Printf("Executing command: %s\n", args[0]) - return command.Run(e, args[1:]), nil -} - -// Prints the CLI help to the UI. -func (e *coreEnvironment) printHelp() { - // Created a sorted slice of the map keys and record the longest - // command name so we can better format the output later. - maxKeyLen := 0 - for _, command := range e.commands { - if len(command) > maxKeyLen { - maxKeyLen = len(command) - } - } - - // Sort the keys - sort.Strings(e.commands) - - // Create the communication/sync mechanisms to get the synopsis' of - // the various commands. We do this in parallel since the overhead - // of the subprocess underneath is very expensive and this speeds things - // up an incredible amount. - var wg sync.WaitGroup - ch := make(chan *helpCommandEntry) - - for i, key := range e.commands { - wg.Add(1) - - // Get the synopsis in a goroutine since it may take awhile - // to subprocess out. - go func(i int, key string) { - defer wg.Done() - var synopsis string - command, err := e.components.Command(key) - if err != nil { - synopsis = fmt.Sprintf("Error loading command: %s", err.Error()) - } else if command == nil { - return - } else { - synopsis = command.Synopsis() - } - - // Pad the key with spaces so that they're all the same width - key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key))) - - // Output the command and the synopsis - ch <- &helpCommandEntry{ - i: i, - key: key, - synopsis: synopsis, - } - }(i, key) - } - - e.ui.Say("usage: packer [--version] [--help] []\n") - e.ui.Say("Available commands are:") - - // Make a goroutine that just waits for all the synopsis gathering - // to complete, and then output it. - synopsisDone := make(chan struct{}) - go func() { - defer close(synopsisDone) - entries := make([]string, len(e.commands)) - - for entry := range ch { - e.ui.Machine("command", entry.key, entry.synopsis) - message := fmt.Sprintf(" %s %s", entry.key, entry.synopsis) - entries[entry.i] = message - } - - for _, message := range entries { - if message != "" { - e.ui.Say(message) - } - } - }() - - // Wait to complete getting the synopsis' then close the channel - wg.Wait() - close(ch) - <-synopsisDone - - e.ui.Say("\nGlobally recognized options:") - e.ui.Say(" -machine-readable Machine-readable output format.") -} - // 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 { diff --git a/packer/environment_test.go b/packer/environment_test.go index c293c2a9a..80edab58e 100644 --- a/packer/environment_test.go +++ b/packer/environment_test.go @@ -6,8 +6,6 @@ import ( "io/ioutil" "log" "os" - "reflect" - "strings" "testing" ) @@ -43,13 +41,6 @@ func testEnvironment() Environment { return env } -func TestEnvironment_DefaultConfig_Commands(t *testing.T) { - config := DefaultEnvironmentConfig() - if len(config.Commands) != 0 { - t.Fatalf("bad: %#v", config.Commands) - } -} - func TestEnvironment_DefaultConfig_Ui(t *testing.T) { config := DefaultEnvironmentConfig() if config.Ui == nil { @@ -91,7 +82,6 @@ func TestEnvironment_NilComponents(t *testing.T) { // anything but if there is a panic in the test then yeah, something // went wrong. env.Builder("foo") - env.Cli([]string{"foo"}) env.Hook("foo") env.PostProcessor("foo") env.Provisioner("foo") @@ -154,117 +144,6 @@ func TestEnvironment_Cache(t *testing.T) { } } -func TestEnvironment_Cli_Error(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Command = func(n string) (Command, error) { return nil, errors.New("foo") } - - env, _ := NewEnvironment(config) - _, err := env.Cli([]string{"foo"}) - if err == nil { - t.Fatal("should have error") - } - if err.Error() != "foo" { - t.Fatalf("bad: %s", err) - } -} - -func TestEnvironment_Cli_CallsRun(t *testing.T) { - command := &TestCommand{} - commands := make(map[string]Command) - commands["foo"] = command - - config := &EnvironmentConfig{} - config.Commands = []string{"foo"} - config.Components.Command = func(n string) (Command, error) { return commands[n], nil } - - env, _ := NewEnvironment(config) - exitCode, err := env.Cli([]string{"foo", "bar", "baz"}) - if err != nil { - t.Fatalf("err: %s", err) - } - if exitCode != 0 { - t.Fatalf("bad: %d", exitCode) - } - if !command.runCalled { - t.Fatal("command should be run") - } - if command.runEnv != env { - t.Fatalf("bad env: %#v", command.runEnv) - } - if !reflect.DeepEqual(command.runArgs, []string{"bar", "baz"}) { - t.Fatalf("bad: %#v", command.runArgs) - } -} - -func TestEnvironment_DefaultCli_Empty(t *testing.T) { - defaultEnv := testEnvironment() - - // Test with no args - exitCode, _ := defaultEnv.Cli([]string{}) - if exitCode != 1 { - t.Fatalf("bad: %d", exitCode) - } - - // Test with only blank args - exitCode, _ = defaultEnv.Cli([]string{""}) - if exitCode != 1 { - t.Fatalf("bad: %d", exitCode) - } -} - -func TestEnvironment_DefaultCli_Help(t *testing.T) { - defaultEnv := testEnvironment() - - // A little lambda to help us test the output actually contains help - testOutput := func() { - buffer := defaultEnv.Ui().(*BasicUi).Writer.(*bytes.Buffer) - output := buffer.String() - buffer.Reset() - if !strings.Contains(output, "usage: packer") { - t.Fatalf("should contain help: %#v", output) - } - } - - // Test "--help" - exitCode, _ := defaultEnv.Cli([]string{"--help"}) - if exitCode != 1 { - t.Fatalf("bad: %d", exitCode) - } - testOutput() - - // Test "-h" - exitCode, _ = defaultEnv.Cli([]string{"--help"}) - if exitCode != 1 { - t.Fatalf("bad: %d", exitCode) - } - testOutput() -} - -func TestEnvironment_DefaultCli_Version(t *testing.T) { - defaultEnv := testEnvironment() - - versionCommands := []string{"version", "--version", "-v"} - for _, command := range versionCommands { - exitCode, _ := defaultEnv.Cli([]string{command}) - if exitCode != 0 { - t.Fatalf("bad: %d", exitCode) - } - - // Test the --version and -v can appear anywhere - exitCode, _ = defaultEnv.Cli([]string{"bad", command}) - - if command != "version" { - if exitCode != 0 { - t.Fatalf("bad: %d", exitCode) - } - } else { - if exitCode != 1 { - t.Fatalf("bad: %d", exitCode) - } - } - } -} - func TestEnvironment_Hook(t *testing.T) { hook := &MockHook{} hooks := make(map[string]Hook) From 779b6d1719ced0828ccc0acb21c62f114284f8dd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Oct 2014 20:40:24 -0700 Subject: [PATCH 05/10] packer/rpc, packer/plugin: remove Command interfaces --- config.go | 13 ------ packer/plugin/client.go | 11 ----- packer/plugin/command.go | 51 --------------------- packer/plugin/command_test.go | 51 --------------------- packer/plugin/plugin_test.go | 8 ---- packer/rpc/client.go | 7 --- packer/rpc/command.go | 84 ---------------------------------- packer/rpc/command_test.go | 71 ---------------------------- packer/rpc/environment.go | 15 ------ packer/rpc/environment_test.go | 14 ------ packer/rpc/server.go | 7 --- 11 files changed, 332 deletions(-) delete mode 100644 packer/plugin/command.go delete mode 100644 packer/plugin/command_test.go delete mode 100644 packer/rpc/command.go delete mode 100644 packer/rpc/command_test.go diff --git a/config.go b/config.go index 4f5e6eba6..7022fd202 100644 --- a/config.go +++ b/config.go @@ -104,19 +104,6 @@ func (c *config) LoadBuilder(name string) (packer.Builder, error) { return c.pluginClient(bin).Builder() } -// This is a proper packer.CommandFunc that can be used to load packer.Command -// implementations from the defined plugins. -func (c *config) LoadCommand(name string) (packer.Command, error) { - log.Printf("Loading command: %s\n", name) - bin, ok := c.Commands[name] - if !ok { - log.Printf("Command not found: %s\n", name) - return nil, nil - } - - return c.pluginClient(bin).Command() -} - // This is a proper implementation of packer.HookFunc that can be used // to load packer.Hook implementations from the defined plugins. func (c *config) LoadHook(name string) (packer.Hook, error) { diff --git a/packer/plugin/client.go b/packer/plugin/client.go index 15960f3dc..36a5ce5a1 100644 --- a/packer/plugin/client.go +++ b/packer/plugin/client.go @@ -138,17 +138,6 @@ func (c *Client) Builder() (packer.Builder, error) { return &cmdBuilder{client.Builder(), c}, nil } -// Returns a command implementation that is communicating over this -// client. If the client hasn't been started, this will start it. -func (c *Client) Command() (packer.Command, error) { - client, err := c.packrpcClient() - if err != nil { - return nil, err - } - - return &cmdCommand{client.Command(), c}, nil -} - // Returns a hook implementation that is communicating over this // client. If the client hasn't been started, this will start it. func (c *Client) Hook() (packer.Hook, error) { diff --git a/packer/plugin/command.go b/packer/plugin/command.go deleted file mode 100644 index c47f7b549..000000000 --- a/packer/plugin/command.go +++ /dev/null @@ -1,51 +0,0 @@ -package plugin - -import ( - "github.com/mitchellh/packer/packer" - "log" -) - -type cmdCommand struct { - command packer.Command - client *Client -} - -func (c *cmdCommand) Help() (result string) { - defer func() { - r := recover() - c.checkExit(r, func() { result = "" }) - }() - - result = c.command.Help() - return -} - -func (c *cmdCommand) Run(e packer.Environment, args []string) (exitCode int) { - defer func() { - r := recover() - c.checkExit(r, func() { exitCode = 1 }) - }() - - exitCode = c.command.Run(e, args) - return -} - -func (c *cmdCommand) Synopsis() (result string) { - defer func() { - r := recover() - c.checkExit(r, func() { - result = "" - }) - }() - - result = c.command.Synopsis() - return -} - -func (c *cmdCommand) checkExit(p interface{}, cb func()) { - if c.client.Exited() { - cb() - } else if p != nil && !Killed { - log.Panic(p) - } -} diff --git a/packer/plugin/command_test.go b/packer/plugin/command_test.go deleted file mode 100644 index 5c860c89c..000000000 --- a/packer/plugin/command_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package plugin - -import ( - "github.com/mitchellh/packer/packer" - "os/exec" - "testing" -) - -type helperCommand byte - -func (helperCommand) Help() string { - return "2" -} - -func (helperCommand) Run(packer.Environment, []string) int { - return 42 -} - -func (helperCommand) Synopsis() string { - return "1" -} - -func TestCommand_NoExist(t *testing.T) { - c := NewClient(&ClientConfig{Cmd: exec.Command("i-should-not-exist")}) - defer c.Kill() - - _, err := c.Command() - if err == nil { - t.Fatal("should have error") - } -} - -func TestCommand_Good(t *testing.T) { - c := NewClient(&ClientConfig{Cmd: helperProcess("command")}) - defer c.Kill() - - command, err := c.Command() - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - result := command.Synopsis() - if result != "1" { - t.Errorf("synopsis not correct: %s", result) - } - - result = command.Help() - if result != "2" { - t.Errorf("help not correct: %s", result) - } -} diff --git a/packer/plugin/plugin_test.go b/packer/plugin/plugin_test.go index d2cf2d201..23f994ea0 100644 --- a/packer/plugin/plugin_test.go +++ b/packer/plugin/plugin_test.go @@ -61,14 +61,6 @@ func TestHelperProcess(*testing.T) { } server.RegisterBuilder(new(packer.MockBuilder)) server.Serve() - case "command": - server, err := Server() - if err != nil { - log.Printf("[ERR] %s", err) - os.Exit(1) - } - server.RegisterCommand(new(helperCommand)) - server.Serve() case "hook": server, err := Server() if err != nil { diff --git a/packer/rpc/client.go b/packer/rpc/client.go index 62a0022b5..147c081a3 100644 --- a/packer/rpc/client.go +++ b/packer/rpc/client.go @@ -90,13 +90,6 @@ func (c *Client) Cache() packer.Cache { } } -func (c *Client) Command() packer.Command { - return &command{ - client: c.client, - mux: c.mux, - } -} - func (c *Client) Communicator() packer.Communicator { return &communicator{ client: c.client, diff --git a/packer/rpc/command.go b/packer/rpc/command.go deleted file mode 100644 index b5e5ccd52..000000000 --- a/packer/rpc/command.go +++ /dev/null @@ -1,84 +0,0 @@ -package rpc - -import ( - "github.com/mitchellh/packer/packer" - "net/rpc" -) - -// A Command is an implementation of the packer.Command interface where the -// command is actually executed over an RPC connection. -type command struct { - client *rpc.Client - mux *muxBroker -} - -// A CommandServer wraps a packer.Command and makes it exportable as part -// of a Golang RPC server. -type CommandServer struct { - command packer.Command - mux *muxBroker -} - -type CommandRunArgs struct { - Args []string - StreamId uint32 -} - -type CommandSynopsisArgs byte - -func (c *command) Help() (result string) { - err := c.client.Call("Command.Help", new(interface{}), &result) - if err != nil { - panic(err) - } - - return -} - -func (c *command) Run(env packer.Environment, args []string) (result int) { - nextId := c.mux.NextId() - server := newServerWithMux(c.mux, nextId) - server.RegisterEnvironment(env) - go server.Serve() - - rpcArgs := &CommandRunArgs{ - Args: args, - StreamId: nextId, - } - err := c.client.Call("Command.Run", rpcArgs, &result) - if err != nil { - panic(err) - } - - return -} - -func (c *command) Synopsis() (result string) { - err := c.client.Call("Command.Synopsis", CommandSynopsisArgs(0), &result) - if err != nil { - panic(err) - } - - return -} - -func (c *CommandServer) Help(args *interface{}, reply *string) error { - *reply = c.command.Help() - return nil -} - -func (c *CommandServer) Run(args *CommandRunArgs, reply *int) error { - client, err := newClientWithMux(c.mux, args.StreamId) - if err != nil { - return NewBasicError(err) - } - defer client.Close() - - *reply = c.command.Run(client.Environment(), args.Args) - return nil -} - -func (c *CommandServer) Synopsis(args *CommandSynopsisArgs, reply *string) error { - *reply = c.command.Synopsis() - return nil -} diff --git a/packer/rpc/command_test.go b/packer/rpc/command_test.go deleted file mode 100644 index 7ba433956..000000000 --- a/packer/rpc/command_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package rpc - -import ( - "github.com/mitchellh/packer/packer" - "reflect" - "testing" -) - -type TestCommand struct { - runArgs []string - runCalled bool - runEnv packer.Environment -} - -func (tc *TestCommand) Help() string { - return "bar" -} - -func (tc *TestCommand) Run(env packer.Environment, args []string) int { - tc.runCalled = true - tc.runArgs = args - tc.runEnv = env - return 0 -} - -func (tc *TestCommand) Synopsis() string { - return "foo" -} - -func TestRPCCommand(t *testing.T) { - // Create the command - command := new(TestCommand) - - // Start the server - client, server := testClientServer(t) - defer client.Close() - defer server.Close() - server.RegisterCommand(command) - commClient := client.Command() - - //Test Help - help := commClient.Help() - if help != "bar" { - t.Fatalf("bad: %s", help) - } - - // Test run - runArgs := []string{"foo", "bar"} - testEnv := &testEnvironment{} - exitCode := commClient.Run(testEnv, runArgs) - if !reflect.DeepEqual(command.runArgs, runArgs) { - t.Fatalf("bad: %#v", command.runArgs) - } - if exitCode != 0 { - t.Fatalf("bad: %d", exitCode) - } - - if command.runEnv == nil { - t.Fatal("runEnv should not be nil") - } - - // Test Synopsis - synopsis := commClient.Synopsis() - if synopsis != "foo" { - t.Fatalf("bad: %#v", synopsis) - } -} - -func TestCommand_Implements(t *testing.T) { - var _ packer.Command = new(command) -} diff --git a/packer/rpc/environment.go b/packer/rpc/environment.go index 5048b54ea..4e2b73da8 100644 --- a/packer/rpc/environment.go +++ b/packer/rpc/environment.go @@ -20,10 +20,6 @@ type EnvironmentServer struct { mux *muxBroker } -type EnvironmentCliArgs struct { - Args []string -} - func (e *Environment) Builder(name string) (b packer.Builder, err error) { var streamId uint32 err = e.client.Call("Environment.Builder", name, &streamId) @@ -53,12 +49,6 @@ func (e *Environment) Cache() packer.Cache { return client.Cache() } -func (e *Environment) Cli(args []string) (result int, err error) { - rpcArgs := &EnvironmentCliArgs{args} - err = e.client.Call("Environment.Cli", rpcArgs, &result) - return -} - func (e *Environment) Hook(name string) (h packer.Hook, err error) { var streamId uint32 err = e.client.Call("Environment.Hook", name, &streamId) @@ -138,11 +128,6 @@ func (e *EnvironmentServer) Cache(args *interface{}, reply *uint32) error { return nil } -func (e *EnvironmentServer) Cli(args *EnvironmentCliArgs, reply *int) (err error) { - *reply, err = e.env.Cli(args.Args) - return -} - func (e *EnvironmentServer) Hook(name string, reply *uint32) error { hook, err := e.env.Hook(name) if err != nil { diff --git a/packer/rpc/environment_test.go b/packer/rpc/environment_test.go index bd0a6784a..a5085d0ef 100644 --- a/packer/rpc/environment_test.go +++ b/packer/rpc/environment_test.go @@ -2,7 +2,6 @@ package rpc import ( "github.com/mitchellh/packer/packer" - "reflect" "testing" ) @@ -95,19 +94,6 @@ func TestEnvironmentRPC(t *testing.T) { t.Fatal("should be called") } - // Test Cli - cliArgs := []string{"foo", "bar"} - result, _ := eClient.Cli(cliArgs) - if !e.cliCalled { - t.Fatal("should be called") - } - if !reflect.DeepEqual(e.cliArgs, cliArgs) { - t.Fatalf("bad: %#v", e.cliArgs) - } - if result != 42 { - t.Fatalf("bad: %#v", result) - } - // Test Provisioner _, _ = eClient.Provisioner("foo") if !e.provCalled { diff --git a/packer/rpc/server.go b/packer/rpc/server.go index de1d0ebb1..ba73dbc8f 100644 --- a/packer/rpc/server.go +++ b/packer/rpc/server.go @@ -88,13 +88,6 @@ func (s *Server) RegisterCache(c packer.Cache) { }) } -func (s *Server) RegisterCommand(c packer.Command) { - s.server.RegisterName(DefaultCommandEndpoint, &CommandServer{ - command: c, - mux: s.mux, - }) -} - func (s *Server) RegisterCommunicator(c packer.Communicator) { s.server.RegisterName(DefaultCommunicatorEndpoint, &CommunicatorServer{ c: c, From bf9e734179ab24b9bcc675d9ff2db691ec187c3d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Oct 2014 20:42:41 -0700 Subject: [PATCH 06/10] Rename some files, style --- packer.go => main.go | 0 packer_test.go => main_test.go | 0 panic.go | 3 ++- 3 files changed, 2 insertions(+), 1 deletion(-) rename packer.go => main.go (100%) rename packer_test.go => main_test.go (100%) diff --git a/packer.go b/main.go similarity index 100% rename from packer.go rename to main.go diff --git a/packer_test.go b/main_test.go similarity index 100% rename from packer_test.go rename to main_test.go diff --git a/panic.go b/panic.go index f02d47f2e..4f5d6a10b 100644 --- a/panic.go +++ b/panic.go @@ -2,10 +2,11 @@ package main import ( "fmt" - "github.com/mitchellh/panicwrap" "io" "os" "strings" + + "github.com/mitchellh/panicwrap" ) // This is output if a panic happens. From 8dbe0f065cfb43767226afac89d805e52afea5cb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Oct 2014 20:51:34 -0700 Subject: [PATCH 07/10] Remove version from "packer" package --- checkpoint.go | 19 +++++---- command/version.go | 79 ++++++++++++++++++++++++++++++++++ commands.go | 10 +++++ common/step_download.go | 7 ++-- main.go | 4 +- packer/plugin/server.go | 3 -- packer/template.go | 2 + packer/version.go | 93 ----------------------------------------- version.go | 12 ++++++ 9 files changed, 119 insertions(+), 110 deletions(-) create mode 100644 command/version.go delete mode 100644 packer/version.go create mode 100644 version.go diff --git a/checkpoint.go b/checkpoint.go index 159e9673d..5d2e486db 100644 --- a/checkpoint.go +++ b/checkpoint.go @@ -6,11 +6,10 @@ import ( "path/filepath" "github.com/hashicorp/go-checkpoint" - "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/command" ) func init() { - packer.VersionChecker = packerVersionCheck checkpointResult = make(chan *checkpoint.CheckResponse, 1) } @@ -33,9 +32,9 @@ func runCheckpoint(c *config) { return } - version := packer.Version - if packer.VersionPrerelease != "" { - version += fmt.Sprintf(".%s", packer.VersionPrerelease) + version := Version + if VersionPrerelease != "" { + version += fmt.Sprintf(".%s", VersionPrerelease) } signaturePath := filepath.Join(configDir, "checkpoint_signature") @@ -58,21 +57,23 @@ func runCheckpoint(c *config) { checkpointResult <- resp } -// packerVersionCheck implements packer.VersionCheckFunc and is used +// commandVersionCheck implements command.VersionCheckFunc and is used // as the version checker. -func packerVersionCheck(current string) (packer.VersionCheckInfo, error) { +func commandVersionCheck() (command.VersionCheckInfo, error) { + // Wait for the result to come through info := <-checkpointResult if info == nil { - var zero packer.VersionCheckInfo + var zero command.VersionCheckInfo return zero, nil } + // Build the alerts that we may have received about our version alerts := make([]string, len(info.Alerts)) for i, a := range info.Alerts { alerts[i] = a.Message } - return packer.VersionCheckInfo{ + return command.VersionCheckInfo{ Outdated: info.Outdated, Latest: info.CurrentVersion, Alerts: alerts, diff --git a/command/version.go b/command/version.go new file mode 100644 index 000000000..5ca8e6004 --- /dev/null +++ b/command/version.go @@ -0,0 +1,79 @@ +package command + +import ( + "bytes" + "fmt" +) + +// VersionCommand is a Command implementation prints the version. +type VersionCommand struct { + Meta + + Revision string + Version string + VersionPrerelease string + CheckFunc VersionCheckFunc +} + +// VersionCheckFunc is the callback called by the Version command to +// check if there is a new version of Packer. +type VersionCheckFunc func() (VersionCheckInfo, error) + +// VersionCheckInfo is the return value for the VersionCheckFunc callback +// and tells the Version command information about the latest version +// of Packer. +type VersionCheckInfo struct { + Outdated bool + Latest string + Alerts []string +} + +func (c *VersionCommand) Help() string { + return "" +} + +func (c *VersionCommand) Run(args []string) int { + /* + env.Ui().Machine("version", Version) + env.Ui().Machine("version-prelease", VersionPrerelease) + env.Ui().Machine("version-commit", GitCommit) + */ + + var versionString bytes.Buffer + fmt.Fprintf(&versionString, "Packer v%s", c.Version) + if c.VersionPrerelease != "" { + fmt.Fprintf(&versionString, ".%s", c.VersionPrerelease) + + if c.Revision != "" { + fmt.Fprintf(&versionString, " (%s)", c.Revision) + } + } + + c.Ui.Output(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("") + + // Check the latest version + info, err := c.CheckFunc() + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error checking latest version: %s", err)) + } + if info.Outdated { + c.Ui.Output(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)) + } + } + + return 0 +} + +func (c *VersionCommand) Synopsis() string { + return "Prints the Packer version" +} diff --git a/commands.go b/commands.go index 8aa6314aa..193fd1465 100644 --- a/commands.go +++ b/commands.go @@ -55,6 +55,16 @@ func init() { Meta: meta, }, nil }, + + "version": func() (cli.Command, error) { + return &command.VersionCommand{ + Meta: meta, + Revision: GitCommit, + Version: Version, + VersionPrerelease: VersionPrerelease, + CheckFunc: commandVersionCheck, + }, nil + }, } } diff --git a/common/step_download.go b/common/step_download.go index 34156d52d..8d6378adc 100644 --- a/common/step_download.go +++ b/common/step_download.go @@ -3,10 +3,11 @@ package common import ( "encoding/hex" "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" "log" "time" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" ) // StepDownload downloads a remote file using the download client within @@ -70,7 +71,7 @@ func (s *StepDownload) Run(state multistep.StateBag) multistep.StepAction { CopyFile: false, Hash: HashForType(s.ChecksumType), Checksum: checksum, - UserAgent: packer.VersionString(), + UserAgent: "Packer", } path, err, retry := s.download(config, state) diff --git a/main.go b/main.go index f72c876b4..093719b3f 100644 --- a/main.go +++ b/main.go @@ -101,8 +101,8 @@ func wrappedMain() int { log.SetOutput(os.Stderr) log.Printf( - "Packer Version: %s %s %s", - packer.Version, packer.VersionPrerelease, packer.GitCommit) + "[INFO] Packer version: %s %s %s", + Version, VersionPrerelease, GitCommit) log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH) log.Printf("Built with Go Version: %s", runtime.Version()) diff --git a/packer/plugin/server.go b/packer/plugin/server.go index 83292c320..23f39c028 100644 --- a/packer/plugin/server.go +++ b/packer/plugin/server.go @@ -10,7 +10,6 @@ package plugin import ( "errors" "fmt" - "github.com/mitchellh/packer/packer" packrpc "github.com/mitchellh/packer/packer/rpc" "io/ioutil" "log" @@ -38,8 +37,6 @@ const APIVersion = "4" // Server waits for a connection to this plugin and returns a Packer // RPC server that you can use to register components and serve them. func Server() (*packrpc.Server, error) { - log.Printf("Plugin build against Packer '%s'", packer.GitCommit) - if os.Getenv(MagicCookieKey) != MagicCookieValue { return nil, errors.New( "Please do not execute plugins directly. Packer will execute these for you.") diff --git a/packer/template.go b/packer/template.go index ed71e90d6..93f487d61 100644 --- a/packer/template.go +++ b/packer/template.go @@ -119,6 +119,8 @@ func ParseTemplate(data []byte, vars map[string]string) (t *Template, err error) } if rawTpl.MinimumPackerVersion != "" { + // TODO: NOPE! Replace this + Version := "1.0" vCur, err := version.NewVersion(Version) if err != nil { panic(err) diff --git a/packer/version.go b/packer/version.go deleted file mode 100644 index 0be4b66a3..000000000 --- a/packer/version.go +++ /dev/null @@ -1,93 +0,0 @@ -package packer - -import ( - "bytes" - "fmt" -) - -// The git commit that is being compiled. This will be filled in by the -// compiler for source builds. -var GitCommit string - -// This should be check to a callback to check for the latest version. -// -// The global nature of this variable is dirty, but a version checker -// really shouldn't change anyways. -var VersionChecker VersionCheckFunc - -// The version of packer. -const Version = "0.7.1" - -// Any pre-release marker for the version. If this is "" (empty string), -// then it means that it is a final release. Otherwise, this is the -// pre-release marker. -const VersionPrerelease = "" - -// VersionCheckFunc is the callback that is called to check the latest -// version of Packer. -type VersionCheckFunc func(string) (VersionCheckInfo, error) - -// VersionCheckInfo is the return value for the VersionCheckFunc that -// contains the latest version information. -type VersionCheckInfo struct { - Outdated bool - Latest string - Alerts []string -} - -type versionCommand byte - -func (versionCommand) Help() string { - return `usage: packer version - -Outputs the version of Packer that is running. There are no additional -command-line flags for this command.` -} - -func (versionCommand) Run(env Environment, args []string) int { - env.Ui().Machine("version", Version) - env.Ui().Machine("version-prelease", VersionPrerelease) - env.Ui().Machine("version-commit", GitCommit) - env.Ui().Say(VersionString()) - - if VersionChecker != nil { - current := Version - if VersionPrerelease != "" { - current += fmt.Sprintf(".%s", VersionPrerelease) - } - - info, err := VersionChecker(current) - if err != nil { - env.Ui().Say(fmt.Sprintf("\nError checking latest version: %s", err)) - } - if info.Outdated { - env.Ui().Say(fmt.Sprintf( - "\nYour version of Packer is out of date! The latest version\n"+ - "is %s. You can update by downloading from www.packer.io.", - info.Latest)) - } - } - - return 0 -} - -func (versionCommand) Synopsis() string { - return "print Packer version" -} - -// VersionString returns the Packer version in human-readable -// form complete with pre-release and git commit info if it is -// available. -func VersionString() string { - var versionString bytes.Buffer - fmt.Fprintf(&versionString, "Packer v%s", Version) - if VersionPrerelease != "" { - fmt.Fprintf(&versionString, ".%s", VersionPrerelease) - - if GitCommit != "" { - fmt.Fprintf(&versionString, " (%s)", GitCommit) - } - } - - return versionString.String() -} diff --git a/version.go b/version.go new file mode 100644 index 000000000..dbfc8da12 --- /dev/null +++ b/version.go @@ -0,0 +1,12 @@ +package main + +// The git commit that was compiled. This will be filled in by the compiler. +var GitCommit string + +// The main version number that is being run at the moment. +const Version = "0.8.0" + +// A pre-release marker for the version. If this is "" (empty string) +// then it means that it is a final release. Otherwise, this is a pre-release +// such as "dev" (in development), "beta", "rc1", etc. +const VersionPrerelease = "dev" From a5a2c3ceb7d66efd899fa0e588944a2d4d809fac Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Oct 2014 20:58:05 -0700 Subject: [PATCH 08/10] command: support machine-readable still --- command/version.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/command/version.go b/command/version.go index 5ca8e6004..689614e60 100644 --- a/command/version.go +++ b/command/version.go @@ -33,11 +33,15 @@ func (c *VersionCommand) Help() string { } func (c *VersionCommand) Run(args []string) int { - /* - env.Ui().Machine("version", Version) - env.Ui().Machine("version-prelease", VersionPrerelease) - env.Ui().Machine("version-commit", GitCommit) - */ + 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) var versionString bytes.Buffer fmt.Fprintf(&versionString, "Packer v%s", c.Version) From 356b48827b2a0b63de6fd4044aea214e0090cc33 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Oct 2014 20:58:44 -0700 Subject: [PATCH 09/10] command: version tests --- command/version_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 command/version_test.go diff --git a/command/version_test.go b/command/version_test.go new file mode 100644 index 000000000..2a645690f --- /dev/null +++ b/command/version_test.go @@ -0,0 +1,11 @@ +package command + +import ( + "testing" + + "github.com/mitchellh/cli" +) + +func TestVersionCommand_implements(t *testing.T) { + var _ cli.Command = &VersionCommand{} +} From c51cd3e39c12790196f823849cb46cdfadef6250 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Oct 2014 20:59:13 -0700 Subject: [PATCH 10/10] remove command configs --- config.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/config.go b/config.go index 7022fd202..091dce2c2 100644 --- a/config.go +++ b/config.go @@ -23,7 +23,6 @@ type config struct { PluginMaxPort uint Builders map[string]string - Commands map[string]string PostProcessors map[string]string `json:"post-processors"` Provisioners map[string]string } @@ -82,15 +81,6 @@ func (c *config) Discover() error { return nil } -// Returns an array of defined command names. -func (c *config) CommandNames() (result []string) { - result = make([]string, 0, len(c.Commands)) - for name := range c.Commands { - result = append(result, name) - } - return -} - // This is a proper packer.BuilderFunc that can be used to load packer.Builder // implementations from the defined plugins. func (c *config) LoadBuilder(name string) (packer.Builder, error) { @@ -153,12 +143,6 @@ func (c *config) discover(path string) error { return err } - err = c.discoverSingle( - filepath.Join(path, "packer-command-*"), &c.Commands) - if err != nil { - return err - } - err = c.discoverSingle( filepath.Join(path, "packer-post-processor-*"), &c.PostProcessors) if err != nil {