command: build should be converted to new API, compiles

This commit is contained in:
Mitchell Hashimoto 2015-05-23 16:30:45 -07:00
parent 3ebfe06ec8
commit 9d89ca8e07
13 changed files with 355 additions and 81 deletions

2
TODO.txt Normal file
View File

@ -0,0 +1,2 @@
- var-file doesn't work
- prov/post-processors/hooks don't work

View File

@ -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 {

View File

@ -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,
},
}
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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))

View File

@ -27,8 +27,9 @@ func init() {
}
meta := command.Meta{
EnvConfig: &EnvConfig,
Ui: Ui,
CoreConfig: &CoreConfig,
EnvConfig: &EnvConfig,
Ui: Ui,
}
Commands = map[string]cli.CommandFactory{

View File

@ -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

29
helper/flag-kv/flag.go Normal file
View File

@ -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
}

View File

@ -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)
}
}
}

16
helper/flag-slice/flag.go Normal file
View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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{