diff --git a/command/build/command.go b/command/build/command.go index 084224b3a..4ed49d902 100644 --- a/command/build/command.go +++ b/command/build/command.go @@ -4,6 +4,7 @@ import ( "bytes" "flag" "fmt" + cmdcommon "github.com/mitchellh/packer/common/command" "github.com/mitchellh/packer/packer" "io/ioutil" "log" @@ -22,15 +23,13 @@ func (Command) Help() string { func (c Command) Run(env packer.Environment, args []string) int { var cfgDebug bool var cfgForce bool - var cfgExcept []string - var cfgOnly []string + buildFilters := new(cmdcommon.BuildFilters) cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError) cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } cmdFlags.BoolVar(&cfgDebug, "debug", false, "debug mode for builds") cmdFlags.BoolVar(&cfgForce, "force", false, "force a build if artifacts exist") - cmdFlags.Var((*stringSliceValue)(&cfgExcept), "except", "build all builds except these") - cmdFlags.Var((*stringSliceValue)(&cfgOnly), "only", "only build the given builds by name") + cmdcommon.BuildFilterFlags(cmdFlags, buildFilters) if err := cmdFlags.Parse(args); err != nil { return 1 } @@ -41,8 +40,9 @@ func (c Command) Run(env packer.Environment, args []string) int { return 1 } - if len(cfgOnly) > 0 && len(cfgExcept) > 0 { - env.Ui().Error("Only one of '-except' or '-only' may be specified.\n") + if err := buildFilters.Validate(); err != nil { + env.Ui().Error(err.Error()) + env.Ui().Error("") env.Ui().Error(c.Help()) return 1 } @@ -72,47 +72,10 @@ func (c Command) Run(env packer.Environment, args []string) int { } // Go through each builder and compile the builds that we care about - buildNames := tpl.BuildNames() - builds := make([]packer.Build, 0, len(buildNames)) - for _, buildName := range buildNames { - if len(cfgExcept) > 0 { - found := false - for _, only := range cfgExcept { - if buildName == only { - found = true - break - } - } - - if found { - log.Printf("Skipping build '%s' because specified by -except.", buildName) - continue - } - } - - if len(cfgOnly) > 0 { - found := false - for _, only := range cfgOnly { - if buildName == only { - found = true - break - } - } - - if !found { - log.Printf("Skipping build '%s' because not specified by -only.", buildName) - continue - } - } - - log.Printf("Creating build: %s", buildName) - build, err := tpl.Build(buildName, components) - if err != nil { - env.Ui().Error(fmt.Sprintf("Failed to create build '%s': \n\n%s", buildName, err)) - return 1 - } - - builds = append(builds, build) + builds, err := buildFilters.Builds(tpl, components) + if err != nil { + env.Ui().Error(err.Error()) + return 1 } if cfgDebug { diff --git a/command/build/string_slice_value.go b/command/build/string_slice_value.go deleted file mode 100644 index c68cd5a1b..000000000 --- a/command/build/string_slice_value.go +++ /dev/null @@ -1,14 +0,0 @@ -package build - -import "strings" - -type stringSliceValue []string - -func (s *stringSliceValue) String() string { - return strings.Join(*s, ",") -} - -func (s *stringSliceValue) Set(value string) error { - *s = strings.Split(value, ",") - return nil -} diff --git a/common/command/build_flags.go b/common/command/build_flags.go new file mode 100644 index 000000000..b0f49222f --- /dev/null +++ b/common/command/build_flags.go @@ -0,0 +1,12 @@ +package command + +import ( + "flag" +) + +// BuildFilterFlags sets the proper command line flags needed for +// build filters. +func BuildFilterFlags(fs *flag.FlagSet, f *BuildFilters) { + fs.Var((*SliceValue)(&f.Except), "except", "build all builds except these") + fs.Var((*SliceValue)(&f.Only), "only", "only build the given builds by name") +} diff --git a/common/command/flag_slice_value.go b/common/command/flag_slice_value.go new file mode 100644 index 000000000..b6f466463 --- /dev/null +++ b/common/command/flag_slice_value.go @@ -0,0 +1,17 @@ +package command + +import "strings" + +// SliceValue implements the flag.Value interface and allows a list of +// strings to be given on the command line and properly parsed into a slice +// of strings internally. +type SliceValue []string + +func (s *SliceValue) String() string { + return strings.Join(*s, ",") +} + +func (s *SliceValue) Set(value string) error { + *s = strings.Split(value, ",") + return nil +} diff --git a/common/command/flag_slice_value_test.go b/common/command/flag_slice_value_test.go new file mode 100644 index 000000000..1b9e2a762 --- /dev/null +++ b/common/command/flag_slice_value_test.go @@ -0,0 +1,28 @@ +package command + +import ( + "flag" + "reflect" + "testing" +) + +func TestSliceValue_implements(t *testing.T) { + var raw interface{} + raw = new(SliceValue) + if _, ok := raw.(flag.Value); !ok { + t.Fatalf("SliceValue should be a Value") + } +} + +func TestSliceValueSet(t *testing.T) { + sv := new(SliceValue) + err := sv.Set("foo,bar,baz") + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := []string{"foo", "bar", "baz"} + if !reflect.DeepEqual([]string(*sv), expected) { + t.Fatalf("Bad: %#v", sv) + } +} diff --git a/common/command/template.go b/common/command/template.go new file mode 100644 index 000000000..6c8462378 --- /dev/null +++ b/common/command/template.go @@ -0,0 +1,71 @@ +package command + +import ( + "errors" + "fmt" + "github.com/mitchellh/packer/packer" + "log" +) + +// BuildFilters is a set of options to filter the builds out of a template. +type BuildFilters struct { + Except []string + Only []string +} + +// Validate validates the filter settings +func (f *BuildFilters) Validate() error { + if len(f.Except) > 0 && len(f.Only) > 0 { + return errors.New("Only one of '-except' or '-only' may be specified.") + } + + return nil +} + +// Builds returns the builds out of the given template that pass the +// configured filters. +func (f *BuildFilters) Builds(t *packer.Template, cf *packer.ComponentFinder) ([]packer.Build, error) { + buildNames := t.BuildNames() + builds := make([]packer.Build, 0, len(buildNames)) + for _, buildName := range buildNames { + if len(f.Except) > 0 { + found := false + for _, except := range f.Except { + if buildName == except { + found = true + break + } + } + + if found { + log.Printf("Skipping build '%s' because specified by -except.", buildName) + continue + } + } + + if len(f.Only) > 0 { + found := false + for _, only := range f.Only { + if buildName == only { + found = true + break + } + } + + if !found { + log.Printf("Skipping build '%s' because not specified by -only.", buildName) + continue + } + } + + log.Printf("Creating build: %s", buildName) + build, err := t.Build(buildName, cf) + if err != nil { + return nil, fmt.Errorf("Failed to create build '%s': \n\n%s", buildName, err) + } + + builds = append(builds, build) + } + + return builds, nil +} diff --git a/common/command/template_test.go b/common/command/template_test.go new file mode 100644 index 000000000..c934c21f8 --- /dev/null +++ b/common/command/template_test.go @@ -0,0 +1,37 @@ +package command + +import ( + "testing" +) + +func TestBuildFiltersValidate(t *testing.T) { + bf := new(BuildFilters) + + err := bf.Validate() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Both set + bf.Except = make([]string, 1) + bf.Only = make([]string, 1) + err = bf.Validate() + if err == nil { + t.Fatal("should error") + } + + // One set + bf.Except = make([]string, 1) + bf.Only = make([]string, 0) + err = bf.Validate() + if err != nil { + t.Fatalf("err: %s", err) + } + + bf.Except = make([]string, 0) + bf.Only = make([]string, 1) + err = bf.Validate() + if err != nil { + t.Fatalf("err: %s", err) + } +}