diff --git a/command/build.go b/command/build.go index a0b33e530..ec6f70555 100644 --- a/command/build.go +++ b/command/build.go @@ -2,16 +2,16 @@ package command import ( "bytes" - "flag" "fmt" - cmdcommon "github.com/mitchellh/packer/common/command" - "github.com/mitchellh/packer/packer" "log" "os" "os/signal" "strconv" "strings" "sync" + + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template" ) type BuildCommand struct { @@ -20,71 +20,52 @@ type BuildCommand struct { func (c BuildCommand) Run(args []string) int { var cfgColor, cfgDebug, cfgForce, cfgParallel bool - buildOptions := new(cmdcommon.BuildOptions) - - env, err := c.Meta.Environment() - if err != nil { - c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err)) + flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars) + flags.Usage = func() { c.Ui.Say(c.Help()) } + flags.BoolVar(&cfgColor, "color", true, "") + flags.BoolVar(&cfgDebug, "debug", false, "") + flags.BoolVar(&cfgForce, "force", false, "") + flags.BoolVar(&cfgParallel, "parallel", true, "") + if err := flags.Parse(args); err != nil { return 1 } - cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError) - cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } - cmdFlags.BoolVar(&cfgColor, "color", true, "enable or disable color") - cmdFlags.BoolVar(&cfgDebug, "debug", false, "debug mode for builds") - cmdFlags.BoolVar(&cfgForce, "force", false, "force a build if artifacts exist") - cmdFlags.BoolVar(&cfgParallel, "parallel", true, "enable/disable parallelization") - cmdcommon.BuildOptionFlags(cmdFlags, buildOptions) - if err := cmdFlags.Parse(args); err != nil { - return 1 - } - - args = cmdFlags.Args() + args = flags.Args() if len(args) != 1 { - cmdFlags.Usage() + flags.Usage() return 1 } - if err := buildOptions.Validate(); err != nil { - env.Ui().Error(err.Error()) - env.Ui().Error("") - env.Ui().Error(c.Help()) - return 1 - } - - userVars, err := buildOptions.AllUserVars() + // Parse the template + tpl, err := template.ParseFile(args[0]) if err != nil { - env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err)) - env.Ui().Error("") - env.Ui().Error(c.Help()) + c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } - // Read the file into a byte array so that we can parse the template - log.Printf("Reading template: %s", args[0]) - tpl, err := packer.ParseTemplateFile(args[0], userVars) + // Get the core + core, err := c.Meta.Core(tpl) if err != nil { - env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err)) + c.Ui.Error(err.Error()) return 1 } - // The component finder for our builds - components := &packer.ComponentFinder{ - Builder: env.Builder, - Hook: env.Hook, - PostProcessor: env.PostProcessor, - Provisioner: env.Provisioner, - } + // Get the builds we care about + buildNames := c.Meta.BuildNames(core) + builds := make([]packer.Build, 0, len(buildNames)) + for _, n := range buildNames { + b, err := core.Build(n) + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Failed to initialize build '%s': %s", + n, err)) + } - // Go through each builder and compile the builds that we care about - builds, err := buildOptions.Builds(tpl, components) - if err != nil { - env.Ui().Error(err.Error()) - return 1 + builds = append(builds, b) } if cfgDebug { - env.Ui().Say("Debug mode enabled. Builds will not be parallelized.") + c.Ui.Say("Debug mode enabled. Builds will not be parallelized.") } // Compile all the UIs for the builds @@ -95,24 +76,23 @@ func (c BuildCommand) Run(args []string) int { packer.UiColorYellow, packer.UiColorBlue, } - buildUis := make(map[string]packer.Ui) - for i, b := range builds { + for i, b := range buildNames { var ui packer.Ui - ui = env.Ui() + ui = c.Ui if cfgColor { ui = &packer.ColoredUi{ Color: colors[i%len(colors)], - Ui: env.Ui(), + Ui: ui, } } - buildUis[b.Name()] = ui - ui.Say(fmt.Sprintf("%s output will be in this color.", b.Name())) + buildUis[b] = ui + ui.Say(fmt.Sprintf("%s output will be in this color.", b)) } // Add a newline between the color output and the actual output - env.Ui().Say("") + c.Ui.Say("") log.Printf("Build debug mode: %v", cfgDebug) log.Printf("Force build: %v", cfgForce) @@ -125,7 +105,7 @@ func (c BuildCommand) Run(args []string) int { warnings, err := b.Prepare() if err != nil { - env.Ui().Error(err.Error()) + c.Ui.Error(err.Error()) return 1 } if len(warnings) > 0 { @@ -169,7 +149,7 @@ func (c BuildCommand) Run(args []string) int { name := b.Name() log.Printf("Starting build run: %s", name) ui := buildUis[name] - runArtifacts, err := b.Run(ui, env.Cache()) + runArtifacts, err := b.Run(ui, c.CoreConfig.Cache) if err != nil { ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err)) @@ -205,34 +185,34 @@ func (c BuildCommand) Run(args []string) int { interruptWg.Wait() if interrupted { - env.Ui().Say("Cleanly cancelled builds after being interrupted.") + c.Ui.Say("Cleanly cancelled builds after being interrupted.") return 1 } if len(errors) > 0 { - env.Ui().Machine("error-count", strconv.FormatInt(int64(len(errors)), 10)) + c.Ui.Machine("error-count", strconv.FormatInt(int64(len(errors)), 10)) - env.Ui().Error("\n==> Some builds didn't complete successfully and had errors:") + c.Ui.Error("\n==> Some builds didn't complete successfully and had errors:") for name, err := range errors { // Create a UI for the machine readable stuff to be targetted ui := &packer.TargettedUi{ Target: name, - Ui: env.Ui(), + Ui: c.Ui, } ui.Machine("error", err.Error()) - env.Ui().Error(fmt.Sprintf("--> %s: %s", name, err)) + c.Ui.Error(fmt.Sprintf("--> %s: %s", name, err)) } } if len(artifacts) > 0 { - env.Ui().Say("\n==> Builds finished. The artifacts of successful builds are:") + c.Ui.Say("\n==> Builds finished. The artifacts of successful builds are:") for name, buildArtifacts := range artifacts { // Create a UI for the machine readable stuff to be targetted ui := &packer.TargettedUi{ Target: name, - Ui: env.Ui(), + Ui: c.Ui, } // Machine-readable helpful @@ -267,11 +247,11 @@ func (c BuildCommand) Run(args []string) int { } ui.Machine("artifact", iStr, "end") - env.Ui().Say(message.String()) + c.Ui.Say(message.String()) } } } else { - env.Ui().Say("\n==> Builds finished but no artifacts were created.") + c.Ui.Say("\n==> Builds finished but no artifacts were created.") } if len(errors) > 0 { diff --git a/command/command_test.go b/command/command_test.go index 500ea7f9e..49e0c7276 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -1,20 +1,23 @@ package command import ( + "bytes" "path/filepath" "testing" - "github.com/mitchellh/cli" + "github.com/mitchellh/packer/packer" ) const fixturesDir = "./test-fixtures" func fatalCommand(t *testing.T, m Meta) { - ui := m.Ui.(*cli.MockUi) + ui := m.Ui.(*packer.BasicUi) + out := ui.Writer.(*bytes.Buffer) + err := ui.ErrorWriter.(*bytes.Buffer) t.Fatalf( "Bad exit code.\n\nStdout:\n\n%s\n\nStderr:\n\n%s", - ui.OutputWriter.String(), - ui.ErrorWriter.String()) + out.String(), + err.String()) } func testFixture(n string) string { @@ -22,7 +25,12 @@ func testFixture(n string) string { } func testMeta(t *testing.T) Meta { + var out, err bytes.Buffer + return Meta{ - Ui: new(cli.MockUi), + Ui: &packer.BasicUi{ + Writer: &out, + ErrorWriter: &err, + }, } } diff --git a/command/fix.go b/command/fix.go index aac0b3916..e908dc52e 100644 --- a/command/fix.go +++ b/command/fix.go @@ -3,7 +3,6 @@ package command import ( "bytes" "encoding/json" - "flag" "fmt" "log" "os" @@ -17,28 +16,22 @@ type FixCommand struct { } 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)) + flags := c.Meta.FlagSet("fix", FlagSetNone) + flags.Usage = func() { c.Ui.Say(c.Help()) } + if err := flags.Parse(args); err != nil { return 1 } - cmdFlags := flag.NewFlagSet("fix", flag.ContinueOnError) - cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } - 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 } // Read the file for decoding tplF, err := os.Open(args[0]) if err != nil { - env.Ui().Error(fmt.Sprintf("Error opening template: %s", err)) + c.Ui.Error(fmt.Sprintf("Error opening template: %s", err)) return 1 } defer tplF.Close() @@ -47,7 +40,7 @@ func (c *FixCommand) Run(args []string) int { var templateData map[string]interface{} decoder := json.NewDecoder(tplF) if err := decoder.Decode(&templateData); err != nil { - env.Ui().Error(fmt.Sprintf("Error parsing template: %s", err)) + c.Ui.Error(fmt.Sprintf("Error parsing template: %s", err)) return 1 } @@ -65,7 +58,7 @@ func (c *FixCommand) Run(args []string) int { log.Printf("Running fixer: %s", name) input, err = fixer.Fix(input) if err != nil { - env.Ui().Error(fmt.Sprintf("Error fixing: %s", err)) + c.Ui.Error(fmt.Sprintf("Error fixing: %s", err)) return 1 } } @@ -73,20 +66,20 @@ func (c *FixCommand) Run(args []string) int { var output bytes.Buffer encoder := json.NewEncoder(&output) if err := encoder.Encode(input); err != nil { - env.Ui().Error(fmt.Sprintf("Error encoding: %s", err)) + c.Ui.Error(fmt.Sprintf("Error encoding: %s", err)) return 1 } var indented bytes.Buffer if err := json.Indent(&indented, output.Bytes(), "", " "); err != nil { - env.Ui().Error(fmt.Sprintf("Error encoding: %s", err)) + c.Ui.Error(fmt.Sprintf("Error encoding: %s", err)) return 1 } result := indented.String() result = strings.Replace(result, `\u003c`, "<", -1) result = strings.Replace(result, `\u003e`, ">", -1) - env.Ui().Say(result) + c.Ui.Say(result) return 0 } diff --git a/command/inspect.go b/command/inspect.go index 8a9fd9569..f564b2689 100644 --- a/command/inspect.go +++ b/command/inspect.go @@ -1,27 +1,20 @@ package command import ( - "flag" "fmt" - "github.com/mitchellh/packer/packer" - "log" "sort" "strings" + + "github.com/mitchellh/packer/template" ) -type InspectCommand struct{ +type InspectCommand struct { Meta } 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 - } - - flags := flag.NewFlagSet("inspect", flag.ContinueOnError) - flags.Usage = func() { env.Ui().Say(c.Help()) } + flags := c.Meta.FlagSet("inspect", FlagSetNone) + flags.Usage = func() { c.Ui.Say(c.Help()) } if err := flags.Parse(args); err != nil { return 1 } @@ -32,16 +25,15 @@ func (c *InspectCommand) Run(args []string) int { return 1 } - // Read the file into a byte array so that we can parse the template - log.Printf("Reading template: %#v", args[0]) - tpl, err := packer.ParseTemplateFile(args[0], nil) + // Parse the template + tpl, err := template.ParseFile(args[0]) if err != nil { - env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err)) + c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } // Convenience... - ui := env.Ui() + ui := c.Ui // Description if tpl.Description != "" { diff --git a/command/meta.go b/command/meta.go index 9c2f7f921..0dc721bd1 100644 --- a/command/meta.go +++ b/command/meta.go @@ -1,15 +1,148 @@ 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 + Ui packer.Ui + + // These are set by command-line flags + flagBuildExcept []string + flagBuildOnly []string + flagVars map[string]string } -func (m *Meta) Environment() (packer.Environment, error) { - return packer.NewEnvironment(m.EnvConfig) +// 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((*kvflag.FlagJSON)(&m.flagVars), "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 } diff --git a/command/push.go b/command/push.go index 74915de3f..1c53d8508 100644 --- a/command/push.go +++ b/command/push.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/atlas-go/archive" "github.com/hashicorp/atlas-go/v1" - "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template" ) // archiveTemplateEntry is the name the template always takes within the slug. @@ -58,15 +58,15 @@ func (c *PushCommand) Run(args []string) int { "longer used. It will be removed in the next version.")) } - // Read the template - tpl, err := packer.ParseTemplateFile(args[0], nil) + // Parse the template + tpl, err := template.ParseFile(args[0]) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } // Validate some things - if tpl.Push.Name == "" { + if tpl.Push == nil || tpl.Push.Name == "" { c.Ui.Error(fmt.Sprintf( "The 'push' section must be specified in the template with\n" + "at least the 'name' option set.")) @@ -131,7 +131,7 @@ func (c *PushCommand) Run(args []string) int { } // Find the Atlas post-processors, if possible - var atlasPPs []packer.RawPostProcessorConfig + var atlasPPs []*template.PostProcessor for _, list := range tpl.PostProcessors { for _, pp := range list { if pp.Type == "atlas" { @@ -221,7 +221,7 @@ func (c *PushCommand) Run(args []string) int { return 1 } - c.Ui.Output(fmt.Sprintf("Push successful to '%s'", tpl.Push.Name)) + c.Ui.Say(fmt.Sprintf("Push successful to '%s'", tpl.Push.Name)) return 0 } diff --git a/command/validate.go b/command/validate.go index a63019a9c..5d7e16c5d 100644 --- a/command/validate.go +++ b/command/validate.go @@ -1,12 +1,12 @@ package command import ( - "flag" "fmt" - cmdcommon "github.com/mitchellh/packer/common/command" - "github.com/mitchellh/packer/packer" "log" "strings" + + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template" ) type ValidateCommand struct { @@ -15,72 +15,54 @@ type ValidateCommand struct { 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)) + flags := c.Meta.FlagSet("validate", FlagSetBuildFilter|FlagSetVars) + flags.Usage = func() { c.Ui.Say(c.Help()) } + flags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only") + if err := flags.Parse(args); err != nil { 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") - 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()) - return 1 - } - - // Parse the template into a machine-usable format - 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)) + c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } + // If we're only checking syntax, then we're done already if cfgSyntaxOnly { - env.Ui().Say("Syntax-only check passed. Everything looks okay.") + c.Ui.Say("Syntax-only check passed. Everything looks okay.") return 0 } + // Get the core + core, err := c.Meta.Core(tpl) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + errs := make([]error, 0) warnings := make(map[string][]string) - // 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)) + } - // Otherwise, get all the builds - builds, err := buildOptions.Builds(tpl, components) - if err != nil { - env.Ui().Error(err.Error()) - return 1 + builds = append(builds, b) } // Check the configuration of all builds @@ -96,12 +78,12 @@ func (c *ValidateCommand) Run(args []string) int { } if len(errs) > 0 { - env.Ui().Error("Template validation failed. Errors are shown below.\n") + c.Ui.Error("Template validation failed. Errors are shown below.\n") for i, err := range errs { - env.Ui().Error(err.Error()) + c.Ui.Error(err.Error()) if (i + 1) < len(errs) { - env.Ui().Error("") + c.Ui.Error("") } } @@ -109,21 +91,21 @@ func (c *ValidateCommand) Run(args []string) int { } if len(warnings) > 0 { - env.Ui().Say("Template validation succeeded, but there were some warnings.") - env.Ui().Say("These are ONLY WARNINGS, and Packer will attempt to build the") - env.Ui().Say("template despite them, but they should be paid attention to.\n") + c.Ui.Say("Template validation succeeded, but there were some warnings.") + c.Ui.Say("These are ONLY WARNINGS, and Packer will attempt to build the") + c.Ui.Say("template despite them, but they should be paid attention to.\n") for build, warns := range warnings { - env.Ui().Say(fmt.Sprintf("Warnings for build '%s':\n", build)) + c.Ui.Say(fmt.Sprintf("Warnings for build '%s':\n", build)) for _, warning := range warns { - env.Ui().Say(fmt.Sprintf("* %s", warning)) + c.Ui.Say(fmt.Sprintf("* %s", warning)) } } return 0 } - env.Ui().Say("Template validated successfully.") + c.Ui.Say("Template validated successfully.") return 0 } diff --git a/command/version.go b/command/version.go index 689614e60..cd170f2df 100644 --- a/command/version.go +++ b/command/version.go @@ -33,15 +33,9 @@ func (c *VersionCommand) Help() string { } func (c *VersionCommand) Run(args []string) int { - 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) + c.Ui.Machine("version", c.Version) + c.Ui.Machine("version-prelease", c.VersionPrerelease) + c.Ui.Machine("version-commit", c.Revision) var versionString bytes.Buffer fmt.Fprintf(&versionString, "Packer v%s", c.Version) @@ -53,13 +47,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 +62,7 @@ func (c *VersionCommand) Run(args []string) int { "Error checking latest version: %s", err)) } if info.Outdated { - c.Ui.Output(fmt.Sprintf( + c.Ui.Say(fmt.Sprintf( "Your version of Packer is out of date! The latest version\n"+ "is %s. You can update by downloading from www.packer.io", info.Latest)) diff --git a/commands.go b/commands.go index 9c6458f64..e0f313957 100644 --- a/commands.go +++ b/commands.go @@ -6,6 +6,7 @@ import ( "github.com/mitchellh/cli" "github.com/mitchellh/packer/command" + "github.com/mitchellh/packer/packer" ) // Commands is the mapping of all the available Terraform commands. @@ -18,17 +19,13 @@ const ErrorPrefix = "e:" const OutputPrefix = "o:" func init() { - Ui = &cli.PrefixedUi{ - AskPrefix: OutputPrefix, - OutputPrefix: OutputPrefix, - InfoPrefix: OutputPrefix, - ErrorPrefix: ErrorPrefix, - Ui: &cli.BasicUi{Writer: os.Stdout}, - } - meta := command.Meta{ - EnvConfig: &EnvConfig, - Ui: Ui, + CoreConfig: &CoreConfig, + Ui: &packer.BasicUi{ + Reader: os.Stdin, + Writer: os.Stdout, + ErrorWriter: os.Stdout, + }, } Commands = map[string]cli.CommandFactory{ diff --git a/common/command/build_flags.go b/common/command/build_flags.go deleted file mode 100644 index d08ca58b8..000000000 --- a/common/command/build_flags.go +++ /dev/null @@ -1,39 +0,0 @@ -package command - -import ( - "flag" - "fmt" - "strings" -) - -// BuildOptionFlags sets the proper command line flags needed for -// build options. -func BuildOptionFlags(fs *flag.FlagSet, f *BuildOptions) { - fs.Var((*SliceValue)(&f.Except), "except", "build all builds except these") - fs.Var((*SliceValue)(&f.Only), "only", "only build the given builds by name") - fs.Var((*userVarValue)(&f.UserVars), "var", "specify a user variable") - fs.Var((*AppendSliceValue)(&f.UserVarFiles), "var-file", "file with user variables") -} - -// userVarValue is a flag.Value that parses out user variables in -// the form of 'key=value' and sets it on this map. -type userVarValue map[string]string - -func (v *userVarValue) String() string { - return "" -} - -func (v *userVarValue) Set(raw string) error { - idx := strings.Index(raw, "=") - if idx == -1 { - return fmt.Errorf("No '=' value in arg: %s", raw) - } - - if *v == nil { - *v = make(map[string]string) - } - - key, value := raw[0:idx], raw[idx+1:] - (*v)[key] = value - return nil -} diff --git a/common/command/build_flags_test.go b/common/command/build_flags_test.go deleted file mode 100644 index 5d39eb946..000000000 --- a/common/command/build_flags_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package command - -import ( - "flag" - "reflect" - "testing" -) - -func TestBuildOptionFlags(t *testing.T) { - opts := new(BuildOptions) - fs := flag.NewFlagSet("test", flag.ContinueOnError) - BuildOptionFlags(fs, opts) - - args := []string{ - "-except=foo,bar,baz", - "-only=a,b", - "-var=foo=bar", - "-var", "bar=baz", - "-var=foo=bang", - "-var-file=foo", - "-var-file=bar", - } - - err := fs.Parse(args) - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := []string{"foo", "bar", "baz"} - if !reflect.DeepEqual(opts.Except, expected) { - t.Fatalf("bad: %#v", opts.Except) - } - - expected = []string{"a", "b"} - if !reflect.DeepEqual(opts.Only, expected) { - t.Fatalf("bad: %#v", opts.Only) - } - - if len(opts.UserVars) != 2 { - t.Fatalf("bad: %#v", opts.UserVars) - } - - if opts.UserVars["foo"] != "bang" { - t.Fatalf("bad: %#v", opts.UserVars) - } - - if opts.UserVars["bar"] != "baz" { - t.Fatalf("bad: %#v", opts.UserVars) - } - - expected = []string{"foo", "bar"} - if !reflect.DeepEqual(opts.UserVarFiles, expected) { - t.Fatalf("bad: %#v", opts.UserVarFiles) - } -} - -func TestUserVarValue_implements(t *testing.T) { - var raw interface{} - raw = new(userVarValue) - if _, ok := raw.(flag.Value); !ok { - t.Fatalf("userVarValue should be a Value") - } -} - -func TestUserVarValueSet(t *testing.T) { - sv := new(userVarValue) - err := sv.Set("key=value") - if err != nil { - t.Fatalf("err: %s", err) - } - - vars := map[string]string(*sv) - if vars["key"] != "value" { - t.Fatalf("Bad: %#v", vars) - } - - // Empty value - err = sv.Set("key=") - if err != nil { - t.Fatalf("err: %s", err) - } - - vars = map[string]string(*sv) - if vars["key"] != "" { - t.Fatalf("Bad: %#v", vars) - } - - // Equal in value - err = sv.Set("key=foo=bar") - if err != nil { - t.Fatalf("err: %s", err) - } - - vars = map[string]string(*sv) - if vars["key"] != "foo=bar" { - t.Fatalf("Bad: %#v", vars) - } - - // No equal - err = sv.Set("key") - if err == nil { - t.Fatal("should have error") - } -} diff --git a/common/command/flag_slice_value.go b/common/command/flag_slice_value.go deleted file mode 100644 index 8989dedad..000000000 --- a/common/command/flag_slice_value.go +++ /dev/null @@ -1,34 +0,0 @@ -package command - -import "strings" - -// AppendSliceValue implements the flag.Value interface and allows multiple -// calls to the same variable to append a list. -type AppendSliceValue []string - -func (s *AppendSliceValue) String() string { - return strings.Join(*s, ",") -} - -func (s *AppendSliceValue) Set(value string) error { - if *s == nil { - *s = make([]string, 0, 1) - } - - *s = append(*s, value) - return nil -} - -// 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 deleted file mode 100644 index ca80c9d9f..000000000 --- a/common/command/flag_slice_value_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package command - -import ( - "flag" - "reflect" - "testing" -) - -func TestAppendSliceValue_implements(t *testing.T) { - var raw interface{} - raw = new(AppendSliceValue) - if _, ok := raw.(flag.Value); !ok { - t.Fatalf("AppendSliceValue should be a Value") - } -} - -func TestAppendSliceValueSet(t *testing.T) { - sv := new(AppendSliceValue) - 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) - } -} - -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 deleted file mode 100644 index 27a42f901..000000000 --- a/common/command/template.go +++ /dev/null @@ -1,162 +0,0 @@ -package command - -import ( - "errors" - "fmt" - jsonutil "github.com/mitchellh/packer/common/json" - "github.com/mitchellh/packer/packer" - "io/ioutil" - "log" - "os" -) - -// BuildOptions is a set of options related to builds that can be set -// from the command line. -type BuildOptions struct { - UserVarFiles []string - UserVars map[string]string - Except []string - Only []string -} - -// Validate validates the options -func (f *BuildOptions) Validate() error { - if len(f.Except) > 0 && len(f.Only) > 0 { - return errors.New("Only one of '-except' or '-only' may be specified.") - } - - if len(f.UserVarFiles) > 0 { - for _, path := range f.UserVarFiles { - if _, err := os.Stat(path); err != nil { - return fmt.Errorf("Cannot access: %s", path) - } - } - } - - return nil -} - -// AllUserVars returns the user variables, compiled from both the -// file paths and the vars on the command line. -func (f *BuildOptions) AllUserVars() (map[string]string, error) { - all := make(map[string]string) - - // Copy in the variables from the files - for _, path := range f.UserVarFiles { - fileVars, err := readFileVars(path) - if err != nil { - return nil, err - } - - for k, v := range fileVars { - all[k] = v - } - } - - // Copy in the command-line vars - for k, v := range f.UserVars { - all[k] = v - } - - return all, nil -} - -// Builds returns the builds out of the given template that pass the -// configured options. -func (f *BuildOptions) Builds(t *packer.Template, cf *packer.ComponentFinder) ([]packer.Build, error) { - buildNames := t.BuildNames() - - // Process the name - tpl, _, err := t.NewConfigTemplate() - if err != nil { - return nil, err - } - - checks := make(map[string][]string) - checks["except"] = f.Except - checks["only"] = f.Only - for t, ns := range checks { - for _, n := range ns { - found := false - for _, actual := range buildNames { - var processed string - processed, err = tpl.Process(actual, nil) - if err != nil { - return nil, err - } - if actual == n || processed == n { - found = true - break - } - } - - if !found { - return nil, fmt.Errorf( - "Unknown build in '%s' flag: %s", t, n) - } - } - } - - builds := make([]packer.Build, 0, len(buildNames)) - for _, buildName := range buildNames { - var processedBuildName string - processedBuildName, err = tpl.Process(buildName, nil) - if err != nil { - return nil, err - } - if len(f.Except) > 0 { - found := false - for _, except := range f.Except { - if buildName == except || processedBuildName == except { - found = true - break - } - } - - if found { - log.Printf("Skipping build '%s' because specified by -except.", processedBuildName) - continue - } - } - - if len(f.Only) > 0 { - found := false - for _, only := range f.Only { - if buildName == only || processedBuildName == only { - found = true - break - } - } - - if !found { - log.Printf("Skipping build '%s' because not specified by -only.", processedBuildName) - continue - } - } - - log.Printf("Creating build: %s", processedBuildName) - 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 -} - -func readFileVars(path string) (map[string]string, error) { - bytes, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - vars := make(map[string]string) - err = jsonutil.Unmarshal(bytes, &vars) - if err != nil { - return nil, err - } - - return vars, nil -} diff --git a/common/command/template_test.go b/common/command/template_test.go deleted file mode 100644 index 419ee7012..000000000 --- a/common/command/template_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package command - -import ( - "github.com/mitchellh/packer/packer" - "testing" -) - -func testTemplate() (*packer.Template, *packer.ComponentFinder) { - tplData := `{ - "variables": { - "foo": null - }, - - "builders": [ - { - "type": "foo" - }, - { - "name": "{{user \"foo\"}}", - "type": "bar" - } - ] - } - ` - - tpl, err := packer.ParseTemplate([]byte(tplData), map[string]string{"foo": "bar"}) - if err != nil { - panic(err) - } - - cf := &packer.ComponentFinder{ - Builder: func(string) (packer.Builder, error) { return new(packer.MockBuilder), nil }, - } - - return tpl, cf -} - -func TestBuildOptionsBuilds(t *testing.T) { - opts := new(BuildOptions) - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 2 { - t.Fatalf("bad: %d", len(bs)) - } -} - -func TestBuildOptionsBuilds_except(t *testing.T) { - opts := new(BuildOptions) - opts.Except = []string{"foo"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "bar" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -//Test to make sure the build name pattern matches -func TestBuildOptionsBuilds_exceptConfigTemplateRaw(t *testing.T) { - opts := new(BuildOptions) - opts.Except = []string{"{{user \"foo\"}}"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "foo" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -//Test to make sure the processed build name matches -func TestBuildOptionsBuilds_exceptConfigTemplateProcessed(t *testing.T) { - opts := new(BuildOptions) - opts.Except = []string{"bar"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "foo" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -func TestBuildOptionsBuilds_only(t *testing.T) { - opts := new(BuildOptions) - opts.Only = []string{"foo"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "foo" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -//Test to make sure the build name pattern matches -func TestBuildOptionsBuilds_onlyConfigTemplateRaw(t *testing.T) { - opts := new(BuildOptions) - opts.Only = []string{"{{user \"foo\"}}"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "bar" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -//Test to make sure the processed build name matches -func TestBuildOptionsBuilds_onlyConfigTemplateProcessed(t *testing.T) { - opts := new(BuildOptions) - opts.Only = []string{"bar"} - - bs, err := opts.Builds(testTemplate()) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(bs) != 1 { - t.Fatalf("bad: %d", len(bs)) - } - - if bs[0].Name() != "bar" { - t.Fatalf("bad: %s", bs[0].Name()) - } -} - -func TestBuildOptionsBuilds_exceptNonExistent(t *testing.T) { - opts := new(BuildOptions) - opts.Except = []string{"i-dont-exist"} - - _, err := opts.Builds(testTemplate()) - if err == nil { - t.Fatal("err should not be nil") - } -} - -func TestBuildOptionsBuilds_onlyNonExistent(t *testing.T) { - opts := new(BuildOptions) - opts.Only = []string{"i-dont-exist"} - - _, err := opts.Builds(testTemplate()) - if err == nil { - t.Fatal("err should not be nil") - } -} - -func TestBuildOptionsValidate(t *testing.T) { - bf := new(BuildOptions) - - 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) - } -} - -func TestBuildOptionsValidate_userVarFiles(t *testing.T) { - bf := new(BuildOptions) - - err := bf.Validate() - if err != nil { - t.Fatalf("err: %s", err) - } - - // Non-existent file - bf.UserVarFiles = []string{"ireallyshouldntexistanywhere"} - err = bf.Validate() - if err == nil { - t.Fatal("should error") - } -} diff --git a/config.go b/config.go index 4acb3c3b1..a9c07043f 100644 --- a/config.go +++ b/config.go @@ -13,8 +13,8 @@ import ( "github.com/mitchellh/packer/packer/plugin" ) -// EnvConfig is the global EnvironmentConfig we use to initialize the CLI. -var EnvConfig packer.EnvironmentConfig +// CoreConfig is the global CoreConfig we use to initialize the CLI. +var CoreConfig packer.CoreConfig type config struct { DisableCheckpoint bool `json:"disable_checkpoint"` diff --git a/helper/flag-kv/flag.go b/helper/flag-kv/flag.go new file mode 100644 index 000000000..0bf4b0086 --- /dev/null +++ b/helper/flag-kv/flag.go @@ -0,0 +1,29 @@ +package kvflag + +import ( + "fmt" + "strings" +) + +// Flag is a flag.Value implementation for parsing user variables +// from the command-line in the format of '-var key=value'. +type Flag map[string]string + +func (v *Flag) String() string { + return "" +} + +func (v *Flag) Set(raw string) error { + idx := strings.Index(raw, "=") + if idx == -1 { + return fmt.Errorf("No '=' value in arg: %s", raw) + } + + if *v == nil { + *v = make(map[string]string) + } + + key, value := raw[0:idx], raw[idx+1:] + (*v)[key] = value + return nil +} diff --git a/helper/flag-kv/flag_json.go b/helper/flag-kv/flag_json.go new file mode 100644 index 000000000..9af9fe1da --- /dev/null +++ b/helper/flag-kv/flag_json.go @@ -0,0 +1,34 @@ +package kvflag + +import ( + "encoding/json" + "fmt" + "os" +) + +// FlagJSON is a flag.Value implementation for parsing user variables +// from the command-line using JSON files. +type FlagJSON map[string]string + +func (v *FlagJSON) String() string { + return "" +} + +func (v *FlagJSON) Set(raw string) error { + f, err := os.Open(raw) + if err != nil { + return err + } + defer f.Close() + + if *v == nil { + *v = make(map[string]string) + } + + if err := json.NewDecoder(f).Decode(v); err != nil { + return fmt.Errorf( + "Error reading variables in '%s': %s", raw, err) + } + + return nil +} diff --git a/helper/flag-kv/flag_json_test.go b/helper/flag-kv/flag_json_test.go new file mode 100644 index 000000000..df5a99e64 --- /dev/null +++ b/helper/flag-kv/flag_json_test.go @@ -0,0 +1,59 @@ +package kvflag + +import ( + "flag" + "path/filepath" + "reflect" + "testing" +) + +func TestFlagJSON_impl(t *testing.T) { + var _ flag.Value = new(FlagJSON) +} + +func TestFlagJSON(t *testing.T) { + cases := []struct { + Input string + Initial map[string]string + Output map[string]string + Error bool + }{ + { + "basic.json", + nil, + map[string]string{"key": "value"}, + false, + }, + + { + "basic.json", + map[string]string{"foo": "bar"}, + map[string]string{"foo": "bar", "key": "value"}, + false, + }, + + { + "basic.json", + map[string]string{"key": "bar"}, + map[string]string{"key": "value"}, + false, + }, + } + + for _, tc := range cases { + f := new(FlagJSON) + if tc.Initial != nil { + f = (*FlagJSON)(&tc.Initial) + } + + err := f.Set(filepath.Join("./test-fixtures", tc.Input)) + if (err != nil) != tc.Error { + t.Fatalf("bad error. Input: %#v\n\n%s", tc.Input, err) + } + + actual := map[string]string(*f) + if !reflect.DeepEqual(actual, tc.Output) { + t.Fatalf("bad: %#v", actual) + } + } +} diff --git a/helper/flag-kv/flag_test.go b/helper/flag-kv/flag_test.go new file mode 100644 index 000000000..9f81d5192 --- /dev/null +++ b/helper/flag-kv/flag_test.go @@ -0,0 +1,56 @@ +package kvflag + +import ( + "flag" + "reflect" + "testing" +) + +func TestFlag_impl(t *testing.T) { + var _ flag.Value = new(Flag) +} + +func TestFlag(t *testing.T) { + cases := []struct { + Input string + Output map[string]string + Error bool + }{ + { + "key=value", + map[string]string{"key": "value"}, + false, + }, + + { + "key=", + map[string]string{"key": ""}, + false, + }, + + { + "key=foo=bar", + map[string]string{"key": "foo=bar"}, + false, + }, + + { + "key", + nil, + true, + }, + } + + for _, tc := range cases { + f := new(Flag) + err := f.Set(tc.Input) + if (err != nil) != tc.Error { + t.Fatalf("bad error. Input: %#v", tc.Input) + } + + actual := map[string]string(*f) + if !reflect.DeepEqual(actual, tc.Output) { + t.Fatalf("bad: %#v", actual) + } + } +} diff --git a/helper/flag-kv/test-fixtures/basic.json b/helper/flag-kv/test-fixtures/basic.json new file mode 100644 index 000000000..21da3b262 --- /dev/null +++ b/helper/flag-kv/test-fixtures/basic.json @@ -0,0 +1,3 @@ +{ + "key": "value" +} diff --git a/helper/flag-slice/flag.go b/helper/flag-slice/flag.go new file mode 100644 index 000000000..da75149dc --- /dev/null +++ b/helper/flag-slice/flag.go @@ -0,0 +1,16 @@ +package sliceflag + +import "strings" + +// StringFlag implements the flag.Value interface and allows multiple +// calls to the same variable to append a list. +type StringFlag []string + +func (s *StringFlag) String() string { + return strings.Join(*s, ",") +} + +func (s *StringFlag) Set(value string) error { + *s = append(*s, value) + return nil +} diff --git a/helper/flag-slice/flag_test.go b/helper/flag-slice/flag_test.go new file mode 100644 index 000000000..f72e1d960 --- /dev/null +++ b/helper/flag-slice/flag_test.go @@ -0,0 +1,33 @@ +package sliceflag + +import ( + "flag" + "reflect" + "testing" +) + +func TestStringFlag_implements(t *testing.T) { + var raw interface{} + raw = new(StringFlag) + if _, ok := raw.(flag.Value); !ok { + t.Fatalf("StringFlag should be a Value") + } +} + +func TestStringFlagSet(t *testing.T) { + sv := new(StringFlag) + err := sv.Set("foo") + if err != nil { + t.Fatalf("err: %s", err) + } + + err = sv.Set("bar") + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := []string{"foo", "bar"} + if !reflect.DeepEqual([]string(*sv), expected) { + t.Fatalf("Bad: %#v", sv) + } +} diff --git a/main.go b/main.go index 2bebafb9e..73d4b88cf 100644 --- a/main.go +++ b/main.go @@ -140,14 +140,13 @@ func wrappedMain() int { defer plugin.CleanupClients() // Create the environment configuration - 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 + CoreConfig.Cache = cache + CoreConfig.Components.Builder = config.LoadBuilder + CoreConfig.Components.Hook = config.LoadHook + CoreConfig.Components.PostProcessor = config.LoadPostProcessor + CoreConfig.Components.Provisioner = config.LoadProvisioner if machineReadable { - EnvConfig.Ui = &packer.MachineReadableUi{ + CoreConfig.Ui = &packer.MachineReadableUi{ Writer: os.Stdout, } diff --git a/packer/build_test.go b/packer/build_test.go index 5a073d39c..4f93e03a5 100644 --- a/packer/build_test.go +++ b/packer/build_test.go @@ -19,7 +19,7 @@ func testBuild() *coreBuild { }, postProcessors: [][]coreBuildPostProcessor{ []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp"}, "testPP", make(map[string]interface{}), true}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp"}, "testPP", make(map[string]interface{}), true}, }, }, variables: make(map[string]string), @@ -66,12 +66,12 @@ func TestBuild_Prepare(t *testing.T) { } corePP := build.postProcessors[0][0] - pp := corePP.processor.(*TestPostProcessor) - if !pp.configCalled { + pp := corePP.processor.(*MockPostProcessor) + if !pp.ConfigureCalled { t.Fatal("should be called") } - if !reflect.DeepEqual(pp.configVal, []interface{}{make(map[string]interface{}), packerConfig}) { - t.Fatalf("bad: %#v", pp.configVal) + if !reflect.DeepEqual(pp.ConfigureConfigs, []interface{}{make(map[string]interface{}), packerConfig}) { + t.Fatalf("bad: %#v", pp.ConfigureConfigs) } } @@ -208,8 +208,8 @@ func TestBuild_Run(t *testing.T) { } // Verify post-processor was run - pp := build.postProcessors[0][0].processor.(*TestPostProcessor) - if !pp.ppCalled { + pp := build.postProcessors[0][0].processor.(*MockPostProcessor) + if !pp.PostProcessCalled { t.Fatal("should be called") } } @@ -244,7 +244,7 @@ func TestBuild_Run_Artifacts(t *testing.T) { build = testBuild() build.postProcessors = [][]coreBuildPostProcessor{ []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp"}, "pp", make(map[string]interface{}), false}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp"}, "pp", make(map[string]interface{}), false}, }, } @@ -269,10 +269,10 @@ func TestBuild_Run_Artifacts(t *testing.T) { build = testBuild() build.postProcessors = [][]coreBuildPostProcessor{ []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1"}, "pp", make(map[string]interface{}), false}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp1"}, "pp", make(map[string]interface{}), false}, }, []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2"}, "pp", make(map[string]interface{}), true}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp2"}, "pp", make(map[string]interface{}), true}, }, } @@ -297,12 +297,12 @@ func TestBuild_Run_Artifacts(t *testing.T) { build = testBuild() build.postProcessors = [][]coreBuildPostProcessor{ []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1a"}, "pp", make(map[string]interface{}), false}, - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1b"}, "pp", make(map[string]interface{}), true}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp1a"}, "pp", make(map[string]interface{}), false}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp1b"}, "pp", make(map[string]interface{}), true}, }, []coreBuildPostProcessor{ - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2a"}, "pp", make(map[string]interface{}), false}, - coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2b"}, "pp", make(map[string]interface{}), false}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp2a"}, "pp", make(map[string]interface{}), false}, + coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp2b"}, "pp", make(map[string]interface{}), false}, }, } @@ -328,7 +328,7 @@ func TestBuild_Run_Artifacts(t *testing.T) { build.postProcessors = [][]coreBuildPostProcessor{ []coreBuildPostProcessor{ coreBuildPostProcessor{ - &TestPostProcessor{artifactId: "pp", keep: true}, "pp", make(map[string]interface{}), false, + &MockPostProcessor{ArtifactId: "pp", Keep: true}, "pp", make(map[string]interface{}), false, }, }, } diff --git a/packer/builder_mock.go b/packer/builder_mock.go index bfa0a0e47..9cb016963 100644 --- a/packer/builder_mock.go +++ b/packer/builder_mock.go @@ -42,6 +42,12 @@ func (tb *MockBuilder) Run(ui Ui, h Hook, c Cache) (Artifact, error) { return nil, nil } + if h != nil { + if err := h.Run(HookProvision, ui, nil, nil); err != nil { + return nil, err + } + } + return &MockArtifact{ IdValue: tb.ArtifactId, }, nil diff --git a/packer/core.go b/packer/core.go new file mode 100644 index 000000000..3969da9c9 --- /dev/null +++ b/packer/core.go @@ -0,0 +1,245 @@ +package packer + +import ( + "fmt" + "os" + "sort" + + "github.com/hashicorp/go-multierror" + "github.com/mitchellh/packer/template" + "github.com/mitchellh/packer/template/interpolate" +) + +// Core is the main executor of Packer. If Packer is being used as a +// library, this is the struct you'll want to instantiate to get anything done. +type Core struct { + cache Cache + components ComponentFinder + ui Ui + template *template.Template + variables map[string]string + builds map[string]*template.Builder +} + +// CoreConfig is the structure for initializing a new Core. Once a CoreConfig +// is used to initialize a Core, it shouldn't be re-used or modified again. +type CoreConfig struct { + Cache Cache + Components ComponentFinder + Ui Ui + Template *template.Template + Variables map[string]string +} + +// The function type used to lookup Builder implementations. +type BuilderFunc func(name string) (Builder, error) + +// The function type used to lookup Hook implementations. +type HookFunc func(name string) (Hook, error) + +// The function type used to lookup PostProcessor implementations. +type PostProcessorFunc func(name string) (PostProcessor, error) + +// The function type used to lookup Provisioner implementations. +type ProvisionerFunc func(name string) (Provisioner, error) + +// ComponentFinder is a struct that contains the various function +// pointers necessary to look up components of Packer such as builders, +// commands, etc. +type ComponentFinder struct { + Builder BuilderFunc + Hook HookFunc + PostProcessor PostProcessorFunc + Provisioner ProvisionerFunc +} + +// NewCore creates a new Core. +func NewCore(c *CoreConfig) (*Core, error) { + if c.Ui == nil { + c.Ui = &BasicUi{ + Reader: os.Stdin, + Writer: os.Stdout, + ErrorWriter: os.Stdout, + } + } + + // Go through and interpolate all the build names. We shuld be able + // to do this at this point with the variables. + builds := make(map[string]*template.Builder) + for _, b := range c.Template.Builders { + v, err := interpolate.Render(b.Name, &interpolate.Context{ + UserVariables: c.Variables, + }) + if err != nil { + return nil, fmt.Errorf( + "Error interpolating builder '%s': %s", + b.Name, err) + } + + builds[v] = b + } + + return &Core{ + cache: c.Cache, + components: c.Components, + ui: c.Ui, + template: c.Template, + variables: c.Variables, + builds: builds, + }, nil +} + +// BuildNames returns the builds that are available in this configured core. +func (c *Core) BuildNames() []string { + r := make([]string, 0, len(c.builds)) + for n, _ := range c.builds { + r = append(r, n) + } + sort.Strings(r) + + return r +} + +// Build returns the Build object for the given name. +func (c *Core) Build(n string) (Build, error) { + // Setup the builder + configBuilder, ok := c.builds[n] + if !ok { + return nil, fmt.Errorf("no such build found: %s", n) + } + builder, err := c.components.Builder(configBuilder.Type) + if err != nil { + return nil, fmt.Errorf( + "error initializing builder '%s': %s", + configBuilder.Type, err) + } + if builder == nil { + return nil, fmt.Errorf( + "builder type not found: %s", configBuilder.Type) + } + + // rawName is the uninterpolated name that we use for various lookups + rawName := configBuilder.Name + + // Setup the provisioners for this build + provisioners := make([]coreBuildProvisioner, 0, len(c.template.Provisioners)) + for _, rawP := range c.template.Provisioners { + // If we're skipping this, then ignore it + if rawP.Skip(rawName) { + continue + } + + // Get the provisioner + provisioner, err := c.components.Provisioner(rawP.Type) + if err != nil { + return nil, fmt.Errorf( + "error initializing provisioner '%s': %s", + rawP.Type, err) + } + if provisioner == nil { + return nil, fmt.Errorf( + "provisioner type not found: %s", rawP.Type) + } + + // Get the configuration + config := make([]interface{}, 1, 2) + config[0] = rawP.Config + if rawP.Override != nil { + if override, ok := rawP.Override[rawName]; ok { + config = append(config, override) + } + } + + // If we're pausing, we wrap the provisioner in a special pauser. + if rawP.PauseBefore > 0 { + provisioner = &PausedProvisioner{ + PauseBefore: rawP.PauseBefore, + Provisioner: provisioner, + } + } + + provisioners = append(provisioners, coreBuildProvisioner{ + provisioner: provisioner, + config: config, + }) + } + + // Setup the post-processors + postProcessors := make([][]coreBuildPostProcessor, 0, len(c.template.PostProcessors)) + for _, rawPs := range c.template.PostProcessors { + current := make([]coreBuildPostProcessor, 0, len(rawPs)) + for _, rawP := range rawPs { + // If we skip, ignore + if rawP.Skip(rawName) { + continue + } + + // Get the post-processor + postProcessor, err := c.components.PostProcessor(rawP.Type) + if err != nil { + return nil, fmt.Errorf( + "error initializing post-processor '%s': %s", + rawP.Type, err) + } + if postProcessor == nil { + return nil, fmt.Errorf( + "post-processor type not found: %s", rawP.Type) + } + + current = append(current, coreBuildPostProcessor{ + processor: postProcessor, + processorType: rawP.Type, + config: rawP.Config, + keepInputArtifact: rawP.KeepInputArtifact, + }) + } + + // If we have no post-processors in this chain, just continue. + if len(current) == 0 { + continue + } + + postProcessors = append(postProcessors, current) + } + + // TODO hooks one day + + return &coreBuild{ + name: n, + builder: builder, + builderConfig: configBuilder.Config, + builderType: configBuilder.Type, + postProcessors: postProcessors, + provisioners: provisioners, + variables: c.variables, + }, nil +} + +// Validate does a full validation of the template. +// +// This will automatically call template.Validate() in addition to doing +// richer semantic checks around variables and so on. +func (c *Core) Validate() error { + // First validate the template in general, we can't do anything else + // unless the template itself is valid. + if err := c.template.Validate(); err != nil { + return err + } + + // Validate variables are set + var err error + for n, v := range c.template.Variables { + if v.Required { + if _, ok := c.variables[n]; !ok { + err = multierror.Append(err, fmt.Errorf( + "required variable not set: %s", n)) + } + } + } + + // TODO: validate all builders exist + // TODO: ^^ provisioner + // TODO: ^^ post-processor + + return err +} diff --git a/packer/core_test.go b/packer/core_test.go new file mode 100644 index 000000000..8cec16bae --- /dev/null +++ b/packer/core_test.go @@ -0,0 +1,378 @@ +package packer + +import ( + "os" + "reflect" + "testing" + + "github.com/mitchellh/packer/template" +) + +func TestCoreBuildNames(t *testing.T) { + cases := []struct { + File string + Vars map[string]string + Result []string + }{ + { + "build-names-basic.json", + nil, + []string{"something"}, + }, + + { + "build-names-func.json", + nil, + []string{"TUBES"}, + }, + } + + for _, tc := range cases { + tpl, err := template.ParseFile(fixtureDir(tc.File)) + if err != nil { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + + core, err := NewCore(&CoreConfig{ + Template: tpl, + Variables: tc.Vars, + }) + if err != nil { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + + names := core.BuildNames() + if !reflect.DeepEqual(names, tc.Result) { + t.Fatalf("err: %s\n\n%#v", tc.File, names) + } + } +} + +func TestCoreBuild_basic(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-basic.json")) + b := TestBuilder(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } +} + +func TestCoreBuild_basicInterpolated(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-basic-interpolated.json")) + b := TestBuilder(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("NAME") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } +} + +func TestCoreBuild_nonExist(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-basic.json")) + TestBuilder(t, config, "test") + core := TestCore(t, config) + + _, err := core.Build("nope") + if err == nil { + t.Fatal("should error") + } +} + +func TestCoreBuild_prov(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-prov.json")) + b := TestBuilder(t, config, "test") + p := TestProvisioner(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } + if !p.ProvCalled { + t.Fatal("provisioner not called") + } +} + +func TestCoreBuild_provSkip(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-prov-skip.json")) + b := TestBuilder(t, config, "test") + p := TestProvisioner(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } + if p.ProvCalled { + t.Fatal("provisioner should not be called") + } +} + +func TestCoreBuild_provSkipInclude(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-prov-skip-include.json")) + b := TestBuilder(t, config, "test") + p := TestProvisioner(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } + if !p.ProvCalled { + t.Fatal("provisioner should be called") + } +} + +func TestCoreBuild_provOverride(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-prov-override.json")) + b := TestBuilder(t, config, "test") + p := TestProvisioner(t, config, "test") + core := TestCore(t, config) + + b.ArtifactId = "hello" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(nil, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != b.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } + if !p.ProvCalled { + t.Fatal("provisioner not called") + } + + found := false + for _, raw := range p.PrepConfigs { + if m, ok := raw.(map[string]interface{}); ok { + if _, ok := m["foo"]; ok { + found = true + break + } + } + } + if !found { + t.Fatal("override not called") + } +} + +func TestCoreBuild_postProcess(t *testing.T) { + config := TestCoreConfig(t) + testCoreTemplate(t, config, fixtureDir("build-pp.json")) + b := TestBuilder(t, config, "test") + p := TestPostProcessor(t, config, "test") + core := TestCore(t, config) + ui := TestUi(t) + + b.ArtifactId = "hello" + p.ArtifactId = "goodbye" + + build, err := core.Build("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := build.Prepare(); err != nil { + t.Fatalf("err: %s", err) + } + + artifact, err := build.Run(ui, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + if len(artifact) != 1 { + t.Fatalf("bad: %#v", artifact) + } + + if artifact[0].Id() != p.ArtifactId { + t.Fatalf("bad: %s", artifact[0].Id()) + } + if p.PostProcessArtifact.Id() != b.ArtifactId { + t.Fatalf("bad: %s", p.PostProcessArtifact.Id()) + } +} + +func TestCoreValidate(t *testing.T) { + cases := []struct { + File string + Vars map[string]string + Err bool + }{ + { + "validate-dup-builder.json", + nil, + true, + }, + + // Required variable not set + { + "validate-req-variable.json", + nil, + true, + }, + + { + "validate-req-variable.json", + map[string]string{"foo": "bar"}, + false, + }, + } + + for _, tc := range cases { + f, err := os.Open(fixtureDir(tc.File)) + if err != nil { + t.Fatalf("err: %s", err) + } + + tpl, err := template.Parse(f) + f.Close() + if err != nil { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + + core, err := NewCore(&CoreConfig{ + Template: tpl, + Variables: tc.Vars, + }) + if err != nil { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + + if err := core.Validate(); (err != nil) != tc.Err { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + } +} + +func testComponentFinder() *ComponentFinder { + builderFactory := func(n string) (Builder, error) { return new(MockBuilder), nil } + ppFactory := func(n string) (PostProcessor, error) { return new(MockPostProcessor), nil } + provFactory := func(n string) (Provisioner, error) { return new(MockProvisioner), nil } + return &ComponentFinder{ + Builder: builderFactory, + PostProcessor: ppFactory, + Provisioner: provFactory, + } +} + +func testCoreTemplate(t *testing.T, c *CoreConfig, p string) { + tpl, err := template.ParseFile(p) + if err != nil { + t.Fatalf("err: %s\n\n%s", p, err) + } + + c.Template = tpl +} diff --git a/packer/environment.go b/packer/environment.go deleted file mode 100644 index 58585ffcf..000000000 --- a/packer/environment.go +++ /dev/null @@ -1,183 +0,0 @@ -// The packer package contains the core components of Packer. -package packer - -import ( - "errors" - "fmt" - "os" -) - -// The function type used to lookup Builder implementations. -type BuilderFunc func(name string) (Builder, error) - -// The function type used to lookup Hook implementations. -type HookFunc func(name string) (Hook, error) - -// The function type used to lookup PostProcessor implementations. -type PostProcessorFunc func(name string) (PostProcessor, error) - -// The function type used to lookup Provisioner implementations. -type ProvisionerFunc func(name string) (Provisioner, error) - -// ComponentFinder is a struct that contains the various function -// pointers necessary to look up components of Packer such as builders, -// commands, etc. -type ComponentFinder struct { - Builder BuilderFunc - Hook HookFunc - PostProcessor PostProcessorFunc - Provisioner ProvisionerFunc -} - -// The environment interface provides access to the configuration and -// state of a single Packer run. -// -// It allows for things such as executing CLI commands, getting the -// list of available builders, and more. -type Environment interface { - Builder(string) (Builder, error) - Cache() Cache - Hook(string) (Hook, error) - PostProcessor(string) (PostProcessor, error) - Provisioner(string) (Provisioner, error) - Ui() Ui -} - -// An implementation of an Environment that represents the Packer core -// environment. -type coreEnvironment struct { - cache Cache - components ComponentFinder - ui Ui -} - -// This struct configures new environments. -type EnvironmentConfig struct { - Cache Cache - Components ComponentFinder - Ui Ui -} - -// 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.Ui = &BasicUi{ - Reader: os.Stdin, - Writer: os.Stdout, - ErrorWriter: os.Stdout, - } - - return config -} - -// This creates a new environment -func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error) { - if config == nil { - err = errors.New("config must be given to initialize environment") - return - } - - env := &coreEnvironment{} - env.cache = config.Cache - env.components = config.Components - env.ui = config.Ui - - // We want to make sure the components have valid function pointers. - // If a function pointer was not given, we assume that the function - // will just return a nil component. - if env.components.Builder == nil { - env.components.Builder = func(string) (Builder, error) { return nil, nil } - } - - if env.components.Hook == nil { - env.components.Hook = func(string) (Hook, error) { return nil, nil } - } - - if env.components.PostProcessor == nil { - env.components.PostProcessor = func(string) (PostProcessor, error) { return nil, nil } - } - - if env.components.Provisioner == nil { - env.components.Provisioner = func(string) (Provisioner, error) { return nil, nil } - } - - // The default cache is just the system temporary directory - if env.cache == nil { - env.cache = &FileCache{CacheDir: os.TempDir()} - } - - resultEnv = env - return -} - -// Returns a builder of the given name that is registered with this -// environment. -func (e *coreEnvironment) Builder(name string) (b Builder, err error) { - b, err = e.components.Builder(name) - if err != nil { - return - } - - if b == nil { - err = fmt.Errorf("No builder returned for name: %s", name) - } - - return -} - -// Returns the cache for this environment -func (e *coreEnvironment) Cache() Cache { - return e.cache -} - -// Returns a hook of the given name that is registered with this -// environment. -func (e *coreEnvironment) Hook(name string) (h Hook, err error) { - h, err = e.components.Hook(name) - if err != nil { - return - } - - if h == nil { - err = fmt.Errorf("No hook returned for name: %s", name) - } - - return -} - -// Returns a PostProcessor for the given name that is registered with this -// environment. -func (e *coreEnvironment) PostProcessor(name string) (p PostProcessor, err error) { - p, err = e.components.PostProcessor(name) - if err != nil { - return - } - - if p == nil { - err = fmt.Errorf("No post processor found for name: %s", name) - } - - return -} - -// Returns a provisioner for the given name that is registered with this -// environment. -func (e *coreEnvironment) Provisioner(name string) (p Provisioner, err error) { - p, err = e.components.Provisioner(name) - if err != nil { - return - } - - if p == nil { - err = fmt.Errorf("No provisioner returned for name: %s", name) - } - - return -} - -// 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 { - return e.ui -} diff --git a/packer/environment_test.go b/packer/environment_test.go deleted file mode 100644 index 80edab58e..000000000 --- a/packer/environment_test.go +++ /dev/null @@ -1,310 +0,0 @@ -package packer - -import ( - "bytes" - "errors" - "io/ioutil" - "log" - "os" - "testing" -) - -func init() { - // Disable log output for tests - log.SetOutput(ioutil.Discard) -} - -func testComponentFinder() *ComponentFinder { - builderFactory := func(n string) (Builder, error) { return new(MockBuilder), nil } - ppFactory := func(n string) (PostProcessor, error) { return new(TestPostProcessor), nil } - provFactory := func(n string) (Provisioner, error) { return new(MockProvisioner), nil } - return &ComponentFinder{ - Builder: builderFactory, - PostProcessor: ppFactory, - Provisioner: provFactory, - } -} - -func testEnvironment() Environment { - config := DefaultEnvironmentConfig() - config.Ui = &BasicUi{ - Reader: new(bytes.Buffer), - Writer: new(bytes.Buffer), - ErrorWriter: new(bytes.Buffer), - } - - env, err := NewEnvironment(config) - if err != nil { - panic(err) - } - - return env -} - -func TestEnvironment_DefaultConfig_Ui(t *testing.T) { - config := DefaultEnvironmentConfig() - if config.Ui == nil { - t.Fatal("config.Ui should not be nil") - } - - rwUi, ok := config.Ui.(*BasicUi) - if !ok { - t.Fatal("default UI should be BasicUi") - } - if rwUi.Writer != os.Stdout { - t.Fatal("default UI should go to stdout") - } - if rwUi.Reader != os.Stdin { - t.Fatal("default UI reader should go to stdin") - } -} - -func TestNewEnvironment_NoConfig(t *testing.T) { - env, err := NewEnvironment(nil) - if env != nil { - t.Fatal("env should be nil") - } - if err == nil { - t.Fatal("should have error") - } -} - -func TestEnvironment_NilComponents(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components = *new(ComponentFinder) - - env, err := NewEnvironment(config) - if err != nil { - t.Fatalf("err: %s", err) - } - - // All of these should not cause panics... so we don't assert - // anything but if there is a panic in the test then yeah, something - // went wrong. - env.Builder("foo") - env.Hook("foo") - env.PostProcessor("foo") - env.Provisioner("foo") -} - -func TestEnvironment_Builder(t *testing.T) { - builder := &MockBuilder{} - builders := make(map[string]Builder) - builders["foo"] = builder - - config := DefaultEnvironmentConfig() - config.Components.Builder = func(n string) (Builder, error) { return builders[n], nil } - - env, _ := NewEnvironment(config) - returnedBuilder, err := env.Builder("foo") - if err != nil { - t.Fatalf("err: %s", err) - } - if returnedBuilder != builder { - t.Fatalf("bad: %#v", returnedBuilder) - } -} - -func TestEnvironment_Builder_NilError(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Builder = func(n string) (Builder, error) { return nil, nil } - - env, _ := NewEnvironment(config) - returnedBuilder, err := env.Builder("foo") - if err == nil { - t.Fatal("should have error") - } - if returnedBuilder != nil { - t.Fatalf("bad: %#v", returnedBuilder) - } -} - -func TestEnvironment_Builder_Error(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Builder = func(n string) (Builder, error) { return nil, errors.New("foo") } - - env, _ := NewEnvironment(config) - returnedBuilder, err := env.Builder("foo") - if err == nil { - t.Fatal("should have error") - } - if err.Error() != "foo" { - t.Fatalf("bad err: %s", err) - } - if returnedBuilder != nil { - t.Fatalf("should be nil: %#v", returnedBuilder) - } -} - -func TestEnvironment_Cache(t *testing.T) { - config := DefaultEnvironmentConfig() - env, _ := NewEnvironment(config) - if env.Cache() == nil { - t.Fatal("cache should not be nil") - } -} - -func TestEnvironment_Hook(t *testing.T) { - hook := &MockHook{} - hooks := make(map[string]Hook) - hooks["foo"] = hook - - config := DefaultEnvironmentConfig() - config.Components.Hook = func(n string) (Hook, error) { return hooks[n], nil } - - env, _ := NewEnvironment(config) - returned, err := env.Hook("foo") - if err != nil { - t.Fatalf("err: %s", err) - } - if returned != hook { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_Hook_NilError(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Hook = func(n string) (Hook, error) { return nil, nil } - - env, _ := NewEnvironment(config) - returned, err := env.Hook("foo") - if err == nil { - t.Fatal("should have error") - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_Hook_Error(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Hook = func(n string) (Hook, error) { return nil, errors.New("foo") } - - env, _ := NewEnvironment(config) - returned, err := env.Hook("foo") - if err == nil { - t.Fatal("should have error") - } - if err.Error() != "foo" { - t.Fatalf("err: %s", err) - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_PostProcessor(t *testing.T) { - pp := &TestPostProcessor{} - pps := make(map[string]PostProcessor) - pps["foo"] = pp - - config := DefaultEnvironmentConfig() - config.Components.PostProcessor = func(n string) (PostProcessor, error) { return pps[n], nil } - - env, _ := NewEnvironment(config) - returned, err := env.PostProcessor("foo") - if err != nil { - t.Fatalf("err: %s", err) - } - if returned != pp { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_PostProcessor_NilError(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.PostProcessor = func(n string) (PostProcessor, error) { return nil, nil } - - env, _ := NewEnvironment(config) - returned, err := env.PostProcessor("foo") - if err == nil { - t.Fatal("should have error") - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_PostProcessor_Error(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.PostProcessor = func(n string) (PostProcessor, error) { return nil, errors.New("foo") } - - env, _ := NewEnvironment(config) - returned, err := env.PostProcessor("foo") - if err == nil { - t.Fatal("should be an error") - } - if err.Error() != "foo" { - t.Fatalf("bad err: %s", err) - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironmentProvisioner(t *testing.T) { - p := &MockProvisioner{} - ps := make(map[string]Provisioner) - ps["foo"] = p - - config := DefaultEnvironmentConfig() - config.Components.Provisioner = func(n string) (Provisioner, error) { return ps[n], nil } - - env, _ := NewEnvironment(config) - returned, err := env.Provisioner("foo") - if err != nil { - t.Fatalf("err: %s", err) - } - if returned != p { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironmentProvisioner_NilError(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Provisioner = func(n string) (Provisioner, error) { return nil, nil } - - env, _ := NewEnvironment(config) - returned, err := env.Provisioner("foo") - if err == nil { - t.Fatal("should have error") - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironmentProvisioner_Error(t *testing.T) { - config := DefaultEnvironmentConfig() - config.Components.Provisioner = func(n string) (Provisioner, error) { - return nil, errors.New("foo") - } - - env, _ := NewEnvironment(config) - returned, err := env.Provisioner("foo") - if err == nil { - t.Fatal("should have error") - } - if err.Error() != "foo" { - t.Fatalf("err: %s", err) - } - if returned != nil { - t.Fatalf("bad: %#v", returned) - } -} - -func TestEnvironment_SettingUi(t *testing.T) { - ui := &BasicUi{ - Reader: new(bytes.Buffer), - Writer: new(bytes.Buffer), - } - - config := &EnvironmentConfig{} - config.Ui = ui - - env, _ := NewEnvironment(config) - - if env.Ui() != ui { - t.Fatalf("UI should be equal: %#v", env.Ui()) - } -} diff --git a/packer/packer_test.go b/packer/packer_test.go new file mode 100644 index 000000000..ec536e2c3 --- /dev/null +++ b/packer/packer_test.go @@ -0,0 +1,11 @@ +package packer + +import ( + "path/filepath" +) + +const FixtureDir = "./test-fixtures" + +func fixtureDir(n string) string { + return filepath.Join(FixtureDir, n) +} diff --git a/packer/post_processor_mock.go b/packer/post_processor_mock.go new file mode 100644 index 000000000..591e4b876 --- /dev/null +++ b/packer/post_processor_mock.go @@ -0,0 +1,33 @@ +package packer + +// MockPostProcessor is an implementation of PostProcessor that can be +// used for tests. +type MockPostProcessor struct { + ArtifactId string + Keep bool + Error error + + ConfigureCalled bool + ConfigureConfigs []interface{} + ConfigureError error + + PostProcessCalled bool + PostProcessArtifact Artifact + PostProcessUi Ui +} + +func (t *MockPostProcessor) Configure(configs ...interface{}) error { + t.ConfigureCalled = true + t.ConfigureConfigs = configs + return t.ConfigureError +} + +func (t *MockPostProcessor) PostProcess(ui Ui, a Artifact) (Artifact, bool, error) { + t.PostProcessCalled = true + t.PostProcessArtifact = a + t.PostProcessUi = ui + + return &MockArtifact{ + IdValue: t.ArtifactId, + }, t.Keep, t.Error +} diff --git a/packer/post_processor_test.go b/packer/post_processor_test.go deleted file mode 100644 index fa6dbdbf9..000000000 --- a/packer/post_processor_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package packer - -type TestPostProcessor struct { - artifactId string - keep bool - configCalled bool - configVal []interface{} - ppCalled bool - ppArtifact Artifact - ppUi Ui -} - -func (pp *TestPostProcessor) Configure(v ...interface{}) error { - pp.configCalled = true - pp.configVal = v - return nil -} - -func (pp *TestPostProcessor) PostProcess(ui Ui, a Artifact) (Artifact, bool, error) { - pp.ppCalled = true - pp.ppArtifact = a - pp.ppUi = ui - return &TestArtifact{id: pp.artifactId}, pp.keep, nil -} diff --git a/packer/rpc/client.go b/packer/rpc/client.go index 0e0140028..2f682f47a 100644 --- a/packer/rpc/client.go +++ b/packer/rpc/client.go @@ -100,13 +100,6 @@ func (c *Client) Communicator() packer.Communicator { } } -func (c *Client) Environment() packer.Environment { - return &Environment{ - client: c.client, - mux: c.mux, - } -} - func (c *Client) Hook() packer.Hook { return &hook{ client: c.client, diff --git a/packer/rpc/environment.go b/packer/rpc/environment.go deleted file mode 100644 index 4e2b73da8..000000000 --- a/packer/rpc/environment.go +++ /dev/null @@ -1,178 +0,0 @@ -package rpc - -import ( - "github.com/mitchellh/packer/packer" - "log" - "net/rpc" -) - -// A Environment is an implementation of the packer.Environment interface -// where the actual environment is executed over an RPC connection. -type Environment struct { - client *rpc.Client - mux *muxBroker -} - -// A EnvironmentServer wraps a packer.Environment and makes it exportable -// as part of a Golang RPC server. -type EnvironmentServer struct { - env packer.Environment - mux *muxBroker -} - -func (e *Environment) Builder(name string) (b packer.Builder, err error) { - var streamId uint32 - err = e.client.Call("Environment.Builder", name, &streamId) - if err != nil { - return - } - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - return nil, err - } - b = client.Builder() - return -} - -func (e *Environment) Cache() packer.Cache { - var streamId uint32 - if err := e.client.Call("Environment.Cache", new(interface{}), &streamId); err != nil { - panic(err) - } - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - log.Printf("[ERR] Error getting cache client: %s", err) - return nil - } - return client.Cache() -} - -func (e *Environment) Hook(name string) (h packer.Hook, err error) { - var streamId uint32 - err = e.client.Call("Environment.Hook", name, &streamId) - if err != nil { - return - } - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - return nil, err - } - return client.Hook(), nil -} - -func (e *Environment) PostProcessor(name string) (p packer.PostProcessor, err error) { - var streamId uint32 - err = e.client.Call("Environment.PostProcessor", name, &streamId) - if err != nil { - return - } - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - return nil, err - } - p = client.PostProcessor() - return -} - -func (e *Environment) Provisioner(name string) (p packer.Provisioner, err error) { - var streamId uint32 - err = e.client.Call("Environment.Provisioner", name, &streamId) - if err != nil { - return - } - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - return nil, err - } - p = client.Provisioner() - return -} - -func (e *Environment) Ui() packer.Ui { - var streamId uint32 - e.client.Call("Environment.Ui", new(interface{}), &streamId) - - client, err := newClientWithMux(e.mux, streamId) - if err != nil { - log.Printf("[ERR] Error connecting to Ui: %s", err) - return nil - } - return client.Ui() -} - -func (e *EnvironmentServer) Builder(name string, reply *uint32) error { - builder, err := e.env.Builder(name) - if err != nil { - return NewBasicError(err) - } - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterBuilder(builder) - go server.Serve() - return nil -} - -func (e *EnvironmentServer) Cache(args *interface{}, reply *uint32) error { - cache := e.env.Cache() - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterCache(cache) - go server.Serve() - return nil -} - -func (e *EnvironmentServer) Hook(name string, reply *uint32) error { - hook, err := e.env.Hook(name) - if err != nil { - return NewBasicError(err) - } - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterHook(hook) - go server.Serve() - return nil -} - -func (e *EnvironmentServer) PostProcessor(name string, reply *uint32) error { - pp, err := e.env.PostProcessor(name) - if err != nil { - return NewBasicError(err) - } - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterPostProcessor(pp) - go server.Serve() - return nil -} - -func (e *EnvironmentServer) Provisioner(name string, reply *uint32) error { - prov, err := e.env.Provisioner(name) - if err != nil { - return NewBasicError(err) - } - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterProvisioner(prov) - go server.Serve() - return nil -} - -func (e *EnvironmentServer) Ui(args *interface{}, reply *uint32) error { - ui := e.env.Ui() - - *reply = e.mux.NextId() - server := newServerWithMux(e.mux, *reply) - server.RegisterUi(ui) - go server.Serve() - return nil -} diff --git a/packer/rpc/environment_test.go b/packer/rpc/environment_test.go deleted file mode 100644 index a5085d0ef..000000000 --- a/packer/rpc/environment_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package rpc - -import ( - "github.com/mitchellh/packer/packer" - "testing" -) - -var testEnvBuilder = &packer.MockBuilder{} -var testEnvCache = &testCache{} -var testEnvUi = &testUi{} - -type testEnvironment struct { - builderCalled bool - builderName string - cliCalled bool - cliArgs []string - hookCalled bool - hookName string - ppCalled bool - ppName string - provCalled bool - provName string - uiCalled bool -} - -func (e *testEnvironment) Builder(name string) (packer.Builder, error) { - e.builderCalled = true - e.builderName = name - return testEnvBuilder, nil -} - -func (e *testEnvironment) Cache() packer.Cache { - return testEnvCache -} - -func (e *testEnvironment) Cli(args []string) (int, error) { - e.cliCalled = true - e.cliArgs = args - return 42, nil -} - -func (e *testEnvironment) Hook(name string) (packer.Hook, error) { - e.hookCalled = true - e.hookName = name - return nil, nil -} - -func (e *testEnvironment) PostProcessor(name string) (packer.PostProcessor, error) { - e.ppCalled = true - e.ppName = name - return nil, nil -} - -func (e *testEnvironment) Provisioner(name string) (packer.Provisioner, error) { - e.provCalled = true - e.provName = name - return nil, nil -} - -func (e *testEnvironment) Ui() packer.Ui { - e.uiCalled = true - return testEnvUi -} - -func TestEnvironmentRPC(t *testing.T) { - // Create the interface to test - e := &testEnvironment{} - - // Start the server - client, server := testClientServer(t) - defer client.Close() - defer server.Close() - server.RegisterEnvironment(e) - eClient := client.Environment() - - // Test Builder - builder, _ := eClient.Builder("foo") - if !e.builderCalled { - t.Fatal("builder should be called") - } - if e.builderName != "foo" { - t.Fatalf("bad: %#v", e.builderName) - } - - builder.Prepare(nil) - if !testEnvBuilder.PrepareCalled { - t.Fatal("should be called") - } - - // Test Cache - cache := eClient.Cache() - cache.Lock("foo") - if !testEnvCache.lockCalled { - t.Fatal("should be called") - } - - // Test Provisioner - _, _ = eClient.Provisioner("foo") - if !e.provCalled { - t.Fatal("should be called") - } - if e.provName != "foo" { - t.Fatalf("bad: %s", e.provName) - } - - // Test Ui - ui := eClient.Ui() - if !e.uiCalled { - t.Fatal("should be called") - } - - // Test calls on the Ui - ui.Say("format") - if !testEnvUi.sayCalled { - t.Fatal("should be called") - } - if testEnvUi.sayMessage != "format" { - t.Fatalf("bad: %#v", testEnvUi.sayMessage) - } -} - -func TestEnvironment_ImplementsEnvironment(t *testing.T) { - var _ packer.Environment = new(Environment) -} diff --git a/packer/rpc/server.go b/packer/rpc/server.go index 1f3e7eef2..b6d17dacf 100644 --- a/packer/rpc/server.go +++ b/packer/rpc/server.go @@ -19,7 +19,6 @@ const ( DefaultCacheEndpoint = "Cache" DefaultCommandEndpoint = "Command" DefaultCommunicatorEndpoint = "Communicator" - DefaultEnvironmentEndpoint = "Environment" DefaultHookEndpoint = "Hook" DefaultPostProcessorEndpoint = "PostProcessor" DefaultProvisionerEndpoint = "Provisioner" @@ -95,13 +94,6 @@ func (s *Server) RegisterCommunicator(c packer.Communicator) { }) } -func (s *Server) RegisterEnvironment(b packer.Environment) { - s.server.RegisterName(DefaultEnvironmentEndpoint, &EnvironmentServer{ - env: b, - mux: s.mux, - }) -} - func (s *Server) RegisterHook(h packer.Hook) { s.server.RegisterName(DefaultHookEndpoint, &HookServer{ hook: h, diff --git a/packer/template.go b/packer/template.go deleted file mode 100644 index 717ae7682..000000000 --- a/packer/template.go +++ /dev/null @@ -1,734 +0,0 @@ -package packer - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "os" - "sort" - "text/template" - "time" - - "github.com/hashicorp/go-version" - "github.com/mitchellh/mapstructure" - jsonutil "github.com/mitchellh/packer/common/json" -) - -// The rawTemplate struct represents the structure of a template read -// directly from a file. The builders and other components map just to -// "interface{}" pointers since we actually don't know what their contents -// are until we read the "type" field. -type rawTemplate struct { - MinimumPackerVersion string `mapstructure:"min_packer_version"` - - Description string - Builders []map[string]interface{} - Hooks map[string][]string - Push PushConfig - PostProcessors []interface{} `mapstructure:"post-processors"` - Provisioners []map[string]interface{} - Variables map[string]interface{} -} - -// The Template struct represents a parsed template, parsed into the most -// completed form it can be without additional processing by the caller. -type Template struct { - RawContents []byte - Description string - Variables map[string]RawVariable - Builders map[string]RawBuilderConfig - Hooks map[string][]string - Push *PushConfig - PostProcessors [][]RawPostProcessorConfig - Provisioners []RawProvisionerConfig -} - -// PushConfig is the configuration structure for the push settings. -type PushConfig struct { - Name string - Address string - BaseDir string `mapstructure:"base_dir"` - Include []string - Exclude []string - Token string - VCS bool -} - -// The RawBuilderConfig struct represents a raw, unprocessed builder -// configuration. It contains the name of the builder as well as the -// raw configuration. If requested, this is used to compile into a full -// builder configuration at some point. -type RawBuilderConfig struct { - Name string - Type string - - RawConfig interface{} -} - -// RawPostProcessorConfig represents a raw, unprocessed post-processor -// configuration. It contains the type of the post processor as well as the -// raw configuration that is handed to the post-processor for it to process. -type RawPostProcessorConfig struct { - TemplateOnlyExcept `mapstructure:",squash"` - - Type string - KeepInputArtifact bool `mapstructure:"keep_input_artifact"` - RawConfig map[string]interface{} -} - -// RawProvisionerConfig represents a raw, unprocessed provisioner configuration. -// It contains the type of the provisioner as well as the raw configuration -// that is handed to the provisioner for it to process. -type RawProvisionerConfig struct { - TemplateOnlyExcept `mapstructure:",squash"` - - Type string - Override map[string]interface{} - RawPauseBefore string `mapstructure:"pause_before"` - - RawConfig interface{} - - pauseBefore time.Duration -} - -// RawVariable represents a variable configuration within a template. -type RawVariable struct { - Default string // The default value for this variable - Required bool // If the variable is required or not - Value string // The set value for this variable - HasValue bool // True if the value was set -} - -// ParseTemplate takes a byte slice and parses a Template from it, returning -// the template and possibly errors while loading the template. The error -// could potentially be a MultiError, representing multiple errors. Knowing -// and checking for this can be useful, if you wish to format it in a certain -// way. -// -// The second parameter, vars, are the values for a set of user variables. -func ParseTemplate(data []byte, vars map[string]string) (t *Template, err error) { - var rawTplInterface interface{} - err = jsonutil.Unmarshal(data, &rawTplInterface) - if err != nil { - return - } - - // Decode the raw template interface into the actual rawTemplate - // structure, checking for any extranneous keys along the way. - var md mapstructure.Metadata - var rawTpl rawTemplate - decoderConfig := &mapstructure.DecoderConfig{ - Metadata: &md, - Result: &rawTpl, - } - - decoder, err := mapstructure.NewDecoder(decoderConfig) - if err != nil { - return - } - - err = decoder.Decode(rawTplInterface) - if err != nil { - return - } - - if rawTpl.MinimumPackerVersion != "" { - // TODO: NOPE! Replace this - Version := "1.0" - vCur, err := version.NewVersion(Version) - if err != nil { - panic(err) - } - vReq, err := version.NewVersion(rawTpl.MinimumPackerVersion) - if err != nil { - return nil, fmt.Errorf( - "'minimum_packer_version' error: %s", err) - } - - if vCur.LessThan(vReq) { - return nil, fmt.Errorf( - "Template requires Packer version %s. "+ - "Running version is %s.", - vReq, vCur) - } - } - - errors := make([]error, 0) - - if len(md.Unused) > 0 { - sort.Strings(md.Unused) - for _, unused := range md.Unused { - errors = append( - errors, fmt.Errorf("Unknown root level key in template: '%s'", unused)) - } - } - - t = &Template{} - t.RawContents = data - t.Description = rawTpl.Description - t.Variables = make(map[string]RawVariable) - t.Builders = make(map[string]RawBuilderConfig) - t.Hooks = rawTpl.Hooks - t.Push = &rawTpl.Push - t.PostProcessors = make([][]RawPostProcessorConfig, len(rawTpl.PostProcessors)) - t.Provisioners = make([]RawProvisionerConfig, len(rawTpl.Provisioners)) - - // Gather all the variables - for k, v := range rawTpl.Variables { - var variable RawVariable - variable.Required = v == nil - - // Create a new mapstructure decoder in order to decode the default - // value since this is the only value in the regular template that - // can be weakly typed. - decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &variable.Default, - WeaklyTypedInput: true, - }) - if err != nil { - // This should never happen. - panic(err) - } - - err = decoder.Decode(v) - if err != nil { - errors = append(errors, - fmt.Errorf("Error decoding default value for user var '%s': %s", k, err)) - continue - } - - // Set the value of this variable if we have it - if val, ok := vars[k]; ok { - variable.HasValue = true - variable.Value = val - delete(vars, k) - } - - t.Variables[k] = variable - } - - // Gather all the builders - for i, v := range rawTpl.Builders { - var raw RawBuilderConfig - if err := mapstructure.Decode(v, &raw); err != nil { - if merr, ok := err.(*mapstructure.Error); ok { - for _, err := range merr.Errors { - errors = append(errors, fmt.Errorf("builder %d: %s", i+1, err)) - } - } else { - errors = append(errors, fmt.Errorf("builder %d: %s", i+1, err)) - } - - continue - } - - if raw.Type == "" { - errors = append(errors, fmt.Errorf("builder %d: missing 'type'", i+1)) - continue - } - - // Attempt to get the name of the builder. If the "name" key - // missing, use the "type" field, which is guaranteed to exist - // at this point. - if raw.Name == "" { - raw.Name = raw.Type - } - - // Check if we already have a builder with this name and error if so - if _, ok := t.Builders[raw.Name]; ok { - errors = append(errors, fmt.Errorf("builder with name '%s' already exists", raw.Name)) - continue - } - - // Now that we have the name, remove it from the config - as the builder - // itself doesn't know about, and it will cause a validation error. - delete(v, "name") - - raw.RawConfig = v - - t.Builders[raw.Name] = raw - } - - // Gather all the post-processors. This is a complicated process since there - // are actually three different formats that the user can use to define - // a post-processor. - for i, rawV := range rawTpl.PostProcessors { - rawPP, err := parsePostProcessor(i, rawV) - if err != nil { - errors = append(errors, err...) - continue - } - - configs := make([]RawPostProcessorConfig, 0, len(rawPP)) - for j, pp := range rawPP { - var config RawPostProcessorConfig - if err := mapstructure.Decode(pp, &config); err != nil { - if merr, ok := err.(*mapstructure.Error); ok { - for _, err := range merr.Errors { - errors = append(errors, - fmt.Errorf("Post-processor #%d.%d: %s", i+1, j+1, err)) - } - } else { - errors = append(errors, - fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err)) - } - - continue - } - - if config.Type == "" { - errors = append(errors, - fmt.Errorf("Post-processor %d.%d: missing 'type'", i+1, j+1)) - continue - } - - // Remove the input keep_input_artifact option - config.TemplateOnlyExcept.Prune(pp) - delete(pp, "keep_input_artifact") - - // Verify that the only settings are good - if errs := config.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 { - for _, err := range errs { - errors = append(errors, - fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err)) - } - - continue - } - - config.RawConfig = pp - - // Add it to the list of configs - configs = append(configs, config) - } - - t.PostProcessors[i] = configs - } - - // Gather all the provisioners - for i, v := range rawTpl.Provisioners { - raw := &t.Provisioners[i] - if err := mapstructure.Decode(v, raw); err != nil { - if merr, ok := err.(*mapstructure.Error); ok { - for _, err := range merr.Errors { - errors = append(errors, fmt.Errorf("provisioner %d: %s", i+1, err)) - } - } else { - errors = append(errors, fmt.Errorf("provisioner %d: %s", i+1, err)) - } - - continue - } - - if raw.Type == "" { - errors = append(errors, fmt.Errorf("provisioner %d: missing 'type'", i+1)) - continue - } - - // Delete the keys that we used - raw.TemplateOnlyExcept.Prune(v) - delete(v, "override") - - // Verify that the override keys exist... - for name, _ := range raw.Override { - if _, ok := t.Builders[name]; !ok { - errors = append( - errors, fmt.Errorf("provisioner %d: build '%s' not found for override", i+1, name)) - } - } - - // Verify that the only settings are good - if errs := raw.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 { - for _, err := range errs { - errors = append(errors, - fmt.Errorf("provisioner %d: %s", i+1, err)) - } - } - - // Setup the pause settings - if raw.RawPauseBefore != "" { - duration, err := time.ParseDuration(raw.RawPauseBefore) - if err != nil { - errors = append( - errors, fmt.Errorf( - "provisioner %d: pause_before invalid: %s", - i+1, err)) - } - - raw.pauseBefore = duration - } - - // Remove the pause_before setting if it is there so that we don't - // get template validation errors later. - delete(v, "pause_before") - - raw.RawConfig = v - } - - if len(t.Builders) == 0 { - errors = append(errors, fmt.Errorf("No builders are defined in the template.")) - } - - // Verify that all the variable sets were for real variables. - for k, _ := range vars { - errors = append(errors, fmt.Errorf("Unknown user variables: %s", k)) - } - - // If there were errors, we put it into a MultiError and return - if len(errors) > 0 { - err = &MultiError{errors} - t = nil - return - } - - return -} - -// ParseTemplateFile takes the given template file and parses it into -// a single template. -func ParseTemplateFile(path string, vars map[string]string) (*Template, error) { - var data []byte - - if path == "-" { - // Read from stdin... - buf := new(bytes.Buffer) - _, err := io.Copy(buf, os.Stdin) - if err != nil { - return nil, err - } - - data = buf.Bytes() - } else { - var err error - data, err = ioutil.ReadFile(path) - if err != nil { - return nil, err - } - } - - return ParseTemplate(data, vars) -} - -func parsePostProcessor(i int, rawV interface{}) (result []map[string]interface{}, errors []error) { - switch v := rawV.(type) { - case string: - result = []map[string]interface{}{ - {"type": v}, - } - case map[string]interface{}: - result = []map[string]interface{}{v} - case []interface{}: - result = make([]map[string]interface{}, len(v)) - errors = make([]error, 0) - for j, innerRawV := range v { - switch innerV := innerRawV.(type) { - case string: - result[j] = map[string]interface{}{"type": innerV} - case map[string]interface{}: - result[j] = innerV - case []interface{}: - errors = append( - errors, - fmt.Errorf("Post-processor %d.%d: sequences not allowed to be nested in sequences", i+1, j+1)) - default: - errors = append(errors, fmt.Errorf("Post-processor %d.%d is in a bad format.", i+1, j+1)) - } - } - - if len(errors) == 0 { - errors = nil - } - default: - result = nil - errors = []error{fmt.Errorf("Post-processor %d is in a bad format.", i+1)} - } - - return -} - -// BuildNames returns a slice of the available names of builds that -// this template represents. -func (t *Template) BuildNames() []string { - names := make([]string, 0, len(t.Builders)) - for name, _ := range t.Builders { - names = append(names, name) - } - - return names -} - -// Build returns a Build for the given name. -// -// If the build does not exist as part of this template, an error is -// returned. -func (t *Template) Build(name string, components *ComponentFinder) (b Build, err error) { - // Setup the Builder - builderConfig, ok := t.Builders[name] - if !ok { - err = fmt.Errorf("No such build found in template: %s", name) - return - } - - // We panic if there is no builder function because this is really - // an internal bug that always needs to be fixed, not an error. - if components.Builder == nil { - panic("no builder function") - } - - // Panic if there are provisioners on the template but no provisioner - // component finder. This is always an internal error, so we panic. - if len(t.Provisioners) > 0 && components.Provisioner == nil { - panic("no provisioner function") - } - - builder, err := components.Builder(builderConfig.Type) - if err != nil { - return - } - - if builder == nil { - err = fmt.Errorf("Builder type not found: %s", builderConfig.Type) - return - } - - // Process the name - tpl, variables, err := t.NewConfigTemplate() - if err != nil { - return nil, err - } - - rawName := name - name, err = tpl.Process(name, nil) - if err != nil { - return nil, err - } - - // Gather the Hooks - hooks := make(map[string][]Hook) - for tplEvent, tplHooks := range t.Hooks { - curHooks := make([]Hook, 0, len(tplHooks)) - - for _, hookName := range tplHooks { - var hook Hook - hook, err = components.Hook(hookName) - if err != nil { - return - } - - if hook == nil { - err = fmt.Errorf("Hook not found: %s", hookName) - return - } - - curHooks = append(curHooks, hook) - } - - hooks[tplEvent] = curHooks - } - - // Prepare the post-processors - postProcessors := make([][]coreBuildPostProcessor, 0, len(t.PostProcessors)) - for _, rawPPs := range t.PostProcessors { - current := make([]coreBuildPostProcessor, 0, len(rawPPs)) - for _, rawPP := range rawPPs { - if rawPP.TemplateOnlyExcept.Skip(rawName) { - continue - } - - pp, err := components.PostProcessor(rawPP.Type) - if err != nil { - return nil, err - } - - if pp == nil { - return nil, fmt.Errorf("PostProcessor type not found: %s", rawPP.Type) - } - - current = append(current, coreBuildPostProcessor{ - processor: pp, - processorType: rawPP.Type, - config: rawPP.RawConfig, - keepInputArtifact: rawPP.KeepInputArtifact, - }) - } - - // If we have no post-processors in this chain, just continue. - // This can happen if the post-processors skip certain builds. - if len(current) == 0 { - continue - } - - postProcessors = append(postProcessors, current) - } - - // Prepare the provisioners - provisioners := make([]coreBuildProvisioner, 0, len(t.Provisioners)) - for _, rawProvisioner := range t.Provisioners { - if rawProvisioner.TemplateOnlyExcept.Skip(rawName) { - continue - } - - var provisioner Provisioner - provisioner, err = components.Provisioner(rawProvisioner.Type) - if err != nil { - return - } - - if provisioner == nil { - err = fmt.Errorf("Provisioner type not found: %s", rawProvisioner.Type) - return - } - - configs := make([]interface{}, 1, 2) - configs[0] = rawProvisioner.RawConfig - - if rawProvisioner.Override != nil { - if override, ok := rawProvisioner.Override[name]; ok { - configs = append(configs, override) - } - } - - if rawProvisioner.pauseBefore > 0 { - provisioner = &PausedProvisioner{ - PauseBefore: rawProvisioner.pauseBefore, - Provisioner: provisioner, - } - } - - coreProv := coreBuildProvisioner{provisioner, configs} - provisioners = append(provisioners, coreProv) - } - - b = &coreBuild{ - name: name, - builder: builder, - builderConfig: builderConfig.RawConfig, - builderType: builderConfig.Type, - hooks: hooks, - postProcessors: postProcessors, - provisioners: provisioners, - variables: variables, - } - - return -} - -//Build a ConfigTemplate object populated by the values within a -//parsed template -func (t *Template) NewConfigTemplate() (c *ConfigTemplate, variables map[string]string, err error) { - - // Prepare the variable template processor, which is a bit unique - // because we don't allow user variable usage and we add a function - // to read from the environment. - varTpl, err := NewConfigTemplate() - if err != nil { - return nil, nil, err - } - varTpl.Funcs(template.FuncMap{ - "env": templateEnv, - "user": templateDisableUser, - }) - - // Prepare the variables - var varErrors []error - variables = make(map[string]string) - for k, v := range t.Variables { - if v.Required && !v.HasValue { - varErrors = append(varErrors, - fmt.Errorf("Required user variable '%s' not set", k)) - } - - var val string - if v.HasValue { - val = v.Value - } else { - val, err = varTpl.Process(v.Default, nil) - if err != nil { - varErrors = append(varErrors, - fmt.Errorf("Error processing user variable '%s': %s'", k, err)) - } - } - - variables[k] = val - } - - if len(varErrors) > 0 { - return nil, variables, &MultiError{varErrors} - } - - // Process the name - tpl, err := NewConfigTemplate() - if err != nil { - return nil, variables, err - } - tpl.UserVars = variables - - return tpl, variables, nil -} - -// TemplateOnlyExcept contains the logic required for "only" and "except" -// meta-parameters. -type TemplateOnlyExcept struct { - Only []string - Except []string -} - -// Prune will prune out the used values from the raw map. -func (t *TemplateOnlyExcept) Prune(raw map[string]interface{}) { - delete(raw, "except") - delete(raw, "only") -} - -// Skip tests if we should skip putting this item onto a build. -func (t *TemplateOnlyExcept) Skip(name string) bool { - if len(t.Only) > 0 { - onlyFound := false - for _, n := range t.Only { - if n == name { - onlyFound = true - break - } - } - - if !onlyFound { - // Skip this provisioner - return true - } - } - - // If the name is in the except list, then skip that - for _, n := range t.Except { - if n == name { - return true - } - } - - return false -} - -// Validates the only/except parameters. -func (t *TemplateOnlyExcept) Validate(b map[string]RawBuilderConfig) (e []error) { - if len(t.Only) > 0 && len(t.Except) > 0 { - e = append(e, - fmt.Errorf("Only one of 'only' or 'except' may be specified.")) - } - - if len(t.Only) > 0 { - for _, n := range t.Only { - if _, ok := b[n]; !ok { - e = append(e, - fmt.Errorf("'only' specified builder '%s' not found", n)) - } - } - } - - for _, n := range t.Except { - if _, ok := b[n]; !ok { - e = append(e, - fmt.Errorf("'except' specified builder '%s' not found", n)) - } - } - - return -} diff --git a/packer/template_test.go b/packer/template_test.go deleted file mode 100644 index b676672b1..000000000 --- a/packer/template_test.go +++ /dev/null @@ -1,1914 +0,0 @@ -package packer - -import ( - "io/ioutil" - "os" - "reflect" - "sort" - "testing" - "time" -) - -func testTemplateComponentFinder() *ComponentFinder { - builder := new(MockBuilder) - pp := new(TestPostProcessor) - provisioner := &MockProvisioner{} - - builderMap := map[string]Builder{ - "test-builder": builder, - } - - ppMap := map[string]PostProcessor{ - "test-pp": pp, - } - - provisionerMap := map[string]Provisioner{ - "test-prov": provisioner, - } - - builderFactory := func(n string) (Builder, error) { return builderMap[n], nil } - ppFactory := func(n string) (PostProcessor, error) { return ppMap[n], nil } - provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil } - return &ComponentFinder{ - Builder: builderFactory, - PostProcessor: ppFactory, - Provisioner: provFactory, - } -} - -func TestParseTemplateFile_basic(t *testing.T) { - data := ` - { - "builders": [{"type": "something"}] - } - ` - - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - tf.Write([]byte(data)) - tf.Close() - - result, err := ParseTemplateFile(tf.Name(), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(result.Builders) != 1 { - t.Fatalf("bad: %#v", result.Builders) - } - - if string(result.RawContents) != data { - t.Fatalf("expected %q to be %q", result.RawContents, data) - } -} - -func TestParseTemplateFile_minPackerVersionBad(t *testing.T) { - data := ` - { - "min_packer_version": "27.0.0", - "builders": [{"type": "something"}] - } - ` - - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - tf.Write([]byte(data)) - tf.Close() - - _, err = ParseTemplateFile(tf.Name(), nil) - if err == nil { - t.Fatal("expects error") - } -} - -func TestParseTemplateFile_minPackerVersionFormat(t *testing.T) { - data := ` - { - "min_packer_version": "NOPE NOPE NOPE", - "builders": [{"type": "something"}] - } - ` - - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - tf.Write([]byte(data)) - tf.Close() - - _, err = ParseTemplateFile(tf.Name(), nil) - if err == nil { - t.Fatal("expects error") - } -} - -func TestParseTemplateFile_minPackerVersionGood(t *testing.T) { - data := ` - { - "min_packer_version": "0.1", - "builders": [{"type": "something"}] - } - ` - - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - tf.Write([]byte(data)) - tf.Close() - - _, err = ParseTemplateFile(tf.Name(), nil) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestParseTemplateFile_stdin(t *testing.T) { - data := ` - { - "builders": [{"type": "something"}] - } - ` - - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - defer tf.Close() - tf.Write([]byte(data)) - - // Sync and seek to the beginning so that we can re-read the contents - tf.Sync() - tf.Seek(0, 0) - - // Set stdin to something we control - oldStdin := os.Stdin - defer func() { os.Stdin = oldStdin }() - os.Stdin = tf - - result, err := ParseTemplateFile("-", nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(result.Builders) != 1 { - t.Fatalf("bad: %#v", result.Builders) - } -} - -func TestParseTemplate_Basic(t *testing.T) { - data := ` - { - "builders": [{"type": "something"}] - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - if result == nil { - t.Fatal("should have result") - } - if len(result.Builders) != 1 { - t.Fatalf("bad: %#v", result.Builders) - } -} - -func TestParseTemplate_Description(t *testing.T) { - data := ` - { - "description": "Foo", - "builders": [{"type": "something"}] - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - if result == nil { - t.Fatal("should have result") - } - if result.Description != "Foo" { - t.Fatalf("bad: %#v", result.Description) - } -} - -func TestParseTemplate_Invalid(t *testing.T) { - // Note there is an extra comma below for a purposeful - // syntax error in the JSON. - data := ` - { - "builders": [], - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("shold have error") - } - if result != nil { - t.Fatal("should not have result") - } -} - -func TestParseTemplate_InvalidKeys(t *testing.T) { - // Note there is an extra comma below for a purposeful - // syntax error in the JSON. - data := ` - { - "builders": [{"type": "foo"}], - "what is this": "" - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } - if result != nil { - t.Fatal("should not have result") - } -} - -func TestParseTemplate_BuilderWithoutType(t *testing.T) { - data := ` - { - "builders": [{}] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestParseTemplate_BuilderWithNonStringType(t *testing.T) { - data := ` - { - "builders": [{ - "type": 42 - }] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestParseTemplate_BuilderWithoutName(t *testing.T) { - data := ` - { - "builders": [ - { - "type": "amazon-ebs" - } - ] - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - if result == nil { - t.Fatal("should have result") - } - if len(result.Builders) != 1 { - t.Fatalf("bad: %#v", result.Builders) - } - - builder, ok := result.Builders["amazon-ebs"] - if !ok { - t.Fatal("should be ok") - } - if builder.Type != "amazon-ebs" { - t.Fatalf("bad: %#v", builder.Type) - } -} - -func TestParseTemplate_BuilderWithName(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "bob", - "type": "amazon-ebs" - } - ] - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - if result == nil { - t.Fatal("should have result") - } - if len(result.Builders) != 1 { - t.Fatalf("bad: %#v", result.Builders) - } - - builder, ok := result.Builders["bob"] - if !ok { - t.Fatal("should be ok") - } - if builder.Type != "amazon-ebs" { - t.Fatalf("bad: %#v", builder.Type) - } - - RawConfig := builder.RawConfig - if RawConfig == nil { - t.Fatal("missing builder raw config") - } - - expected := map[string]interface{}{ - "type": "amazon-ebs", - } - - if !reflect.DeepEqual(RawConfig, expected) { - t.Fatalf("bad raw: %#v", RawConfig) - } -} - -func TestParseTemplate_BuilderWithConflictingName(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "bob", - "type": "amazon-ebs" - }, - { - "name": "bob", - "type": "foo", - } - ] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestParseTemplate_Hooks(t *testing.T) { - data := ` - { - - "builders": [{"type": "foo"}], - - "hooks": { - "event": ["foo", "bar"] - } - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - if result == nil { - t.Fatal("should have result") - } - if len(result.Hooks) != 1 { - t.Fatalf("bad: %#v", result.Hooks) - } - - hooks, ok := result.Hooks["event"] - if !ok { - t.Fatal("should be okay") - } - if !reflect.DeepEqual(hooks, []string{"foo", "bar"}) { - t.Fatalf("bad: %#v", hooks) - } -} - -func TestParseTemplate_PostProcessors(t *testing.T) { - data := ` - { - "builders": [{"type": "foo"}], - - "post-processors": [ - "simple", - - { "type": "detailed" }, - - [ "foo", { "type": "bar" } ] - ] - } - ` - - tpl, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("error parsing: %s", err) - } - - if len(tpl.PostProcessors) != 3 { - t.Fatalf("bad number of post-processors: %d", len(tpl.PostProcessors)) - } - - pp := tpl.PostProcessors[0] - if len(pp) != 1 { - t.Fatalf("wrong number of configs in simple: %d", len(pp)) - } - - if pp[0].Type != "simple" { - t.Fatalf("wrong type for simple: %s", pp[0].Type) - } - - pp = tpl.PostProcessors[1] - if len(pp) != 1 { - t.Fatalf("wrong number of configs in detailed: %d", len(pp)) - } - - if pp[0].Type != "detailed" { - t.Fatalf("wrong type for detailed: %s", pp[0].Type) - } - - pp = tpl.PostProcessors[2] - if len(pp) != 2 { - t.Fatalf("wrong number of configs for sequence: %d", len(pp)) - } - - if pp[0].Type != "foo" { - t.Fatalf("wrong type for sequence 0: %s", pp[0].Type) - } - - if pp[1].Type != "bar" { - t.Fatalf("wrong type for sequence 1: %s", pp[1].Type) - } -} - -func TestParseTemplate_ProvisionerWithoutType(t *testing.T) { - data := ` - { - "builders": [{"type": "foo"}], - - "provisioners": [{}] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("err should not be nil") - } -} - -func TestParseTemplate_ProvisionerWithNonStringType(t *testing.T) { - data := ` - { - "builders": [{"type": "foo"}], - - "provisioners": [{ - "type": 42 - }] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestParseTemplate_Provisioners(t *testing.T) { - data := ` - { - "builders": [{"type": "foo"}], - - "provisioners": [ - { - "type": "shell" - } - ] - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - if result == nil { - t.Fatal("should have result") - } - if len(result.Provisioners) != 1 { - t.Fatalf("bad: %#v", result.Provisioners) - } - if result.Provisioners[0].Type != "shell" { - t.Fatalf("bad: %#v", result.Provisioners[0].Type) - } - if result.Provisioners[0].RawConfig == nil { - t.Fatal("should have raw config") - } -} - -func TestParseTemplate_ProvisionerPauseBefore(t *testing.T) { - data := ` - { - "builders": [{"type": "foo"}], - - "provisioners": [ - { - "type": "shell", - "pause_before": "10s" - } - ] - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - if result == nil { - t.Fatal("should have result") - } - if len(result.Provisioners) != 1 { - t.Fatalf("bad: %#v", result.Provisioners) - } - if result.Provisioners[0].Type != "shell" { - t.Fatalf("bad: %#v", result.Provisioners[0].Type) - } - if result.Provisioners[0].pauseBefore != 10*time.Second { - t.Fatalf("bad: %s", result.Provisioners[0].pauseBefore) - } -} - -func TestParseTemplateFile_push(t *testing.T) { - data := ` - { - "builders": [{"type": "something"}], - - "push": { - "name": "hello", - "include": ["one"], - "exclude": ["two"] - } - } - ` - - tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } - tf.Write([]byte(data)) - tf.Close() - - result, err := ParseTemplateFile(tf.Name(), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := &PushConfig{ - Name: "hello", - Include: []string{"one"}, - Exclude: []string{"two"}, - } - if !reflect.DeepEqual(result.Push, expected) { - t.Fatalf("bad: %#v", result.Push) - } -} - -func TestParseTemplate_Variables(t *testing.T) { - data := ` - { - "variables": { - "foo": "bar", - "bar": null, - "baz": 27 - }, - - "builders": [{"type": "something"}] - } - ` - - result, err := ParseTemplate([]byte(data), map[string]string{ - "bar": "bar", - }) - if err != nil { - t.Fatalf("err: %s", err) - } - - if result.Variables == nil || len(result.Variables) != 3 { - t.Fatalf("bad vars: %#v", result.Variables) - } - - if result.Variables["foo"].Default != "bar" { - t.Fatal("foo default is not right") - } - if result.Variables["foo"].Required { - t.Fatal("foo should not be required") - } - if result.Variables["foo"].HasValue { - t.Fatal("foo should not have value") - } - - if result.Variables["bar"].Default != "" { - t.Fatal("default should be empty") - } - if !result.Variables["bar"].Required { - t.Fatal("bar should be required") - } - if !result.Variables["bar"].HasValue { - t.Fatal("bar should have value") - } - if result.Variables["bar"].Value != "bar" { - t.Fatal("bad value") - } - - if result.Variables["baz"].Default != "27" { - t.Fatal("default should be empty") - } - - if result.Variables["baz"].Required { - t.Fatal("baz should not be required") - } -} - -func TestParseTemplate_variablesSet(t *testing.T) { - data := ` - { - "variables": { - "foo": "bar" - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{ - "foo": "value", - }) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(template.Variables) != 1 { - t.Fatalf("bad vars: %#v", template.Variables) - } - if template.Variables["foo"].Value != "value" { - t.Fatalf("bad: %#v", template.Variables["foo"]) - } -} - -func TestParseTemplate_variablesSetUnknown(t *testing.T) { - data := ` - { - "variables": { - "foo": "bar" - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - _, err := ParseTemplate([]byte(data), map[string]string{ - "what": "value", - }) - if err == nil { - t.Fatal("should error") - } -} - -func TestParseTemplate_variablesBadDefault(t *testing.T) { - data := ` - { - "variables": { - "foo": 7, - }, - - "builders": [{"type": "something"}] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestTemplate_BuildNames(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "bob", - "type": "amazon-ebs" - }, - { - "name": "chris", - "type": "another" - } - ] - } - ` - - result, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - buildNames := result.BuildNames() - sort.Strings(buildNames) - if !reflect.DeepEqual(buildNames, []string{"bob", "chris"}) { - t.Fatalf("bad: %#v", buildNames) - } -} - -func TestTemplate_BuildUnknown(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("bad: %s", err) - } - - build, err := template.Build("nope", nil) - if build != nil { - t.Fatalf("build should be nil: %#v", build) - } - if err == nil { - t.Fatal("should have error") - } -} - -func TestTemplate_BuildUnknownBuilder(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - builderFactory := func(string) (Builder, error) { return nil, nil } - components := &ComponentFinder{Builder: builderFactory} - build, err := template.Build("test1", components) - if err == nil { - t.Fatal("should have error") - } - if build != nil { - t.Fatalf("bad: %#v", build) - } -} - -func TestTemplateBuild_envInVars(t *testing.T) { - data := ` - { - "variables": { - "foo": "{{env \"foo\"}}" - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - defer os.Setenv("foo", os.Getenv("foo")) - if err := os.Setenv("foo", "bar"); err != nil { - t.Fatalf("err: %s", err) - } - - template, err := ParseTemplate([]byte(data), map[string]string{}) - if err != nil { - t.Fatalf("err: %s", err) - } - - b, err := template.Build("test1", testComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - coreBuild, ok := b.(*coreBuild) - if !ok { - t.Fatal("should be ok") - } - - if coreBuild.variables["foo"] != "bar" { - t.Fatalf("bad: %#v", coreBuild.variables) - } -} - -func TestTemplateBuild_names(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2-{{user \"foo\"}}", - "type": "test-builder" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) - if err != nil { - t.Fatalf("err: %s", err) - } - - b, err := template.Build("test1", testComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - if b.Name() != "test1" { - t.Fatalf("bad: %#v", b.Name()) - } - - b, err = template.Build("test2-{{user \"foo\"}}", testComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - if b.Name() != "test2-bar" { - t.Fatalf("bad: %#v", b.Name()) - } -} - -func TestTemplate_Build_NilBuilderFunc(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - defer func() { - p := recover() - if p == nil { - t.Fatal("should panic") - } - - if p.(string) != "no builder function" { - t.Fatalf("bad panic: %s", p.(string)) - } - }() - - template.Build("test1", &ComponentFinder{}) -} - -func TestTemplate_Build_NilProvisionerFunc(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - defer func() { - p := recover() - if p == nil { - t.Fatal("should panic") - } - - if p.(string) != "no provisioner function" { - t.Fatalf("bad panic: %s", p.(string)) - } - }() - - template.Build("test1", &ComponentFinder{ - Builder: func(string) (Builder, error) { return nil, nil }, - }) -} - -func TestTemplate_Build_NilProvisionerFunc_WithNoProvisioners(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ], - - "provisioners": [] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - template.Build("test1", &ComponentFinder{ - Builder: func(string) (Builder, error) { return nil, nil }, - }) -} - -func TestTemplate_Build(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov" - } - ], - - "post-processors": [ - "simple", - [ - "simple", - { "type": "simple", "keep_input_artifact": true } - ] - ] - } - ` - - expectedConfig := map[string]interface{}{ - "type": "test-builder", - } - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - builder := new(MockBuilder) - builderMap := map[string]Builder{ - "test-builder": builder, - } - - provisioner := &MockProvisioner{} - provisionerMap := map[string]Provisioner{ - "test-prov": provisioner, - } - - pp := new(TestPostProcessor) - ppMap := map[string]PostProcessor{ - "simple": pp, - } - - builderFactory := func(n string) (Builder, error) { return builderMap[n], nil } - ppFactory := func(n string) (PostProcessor, error) { return ppMap[n], nil } - provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil } - components := &ComponentFinder{ - Builder: builderFactory, - PostProcessor: ppFactory, - Provisioner: provFactory, - } - - // Get the build, verifying we can get it without issue, but also - // that the proper builder was looked up and used for the build. - build, err := template.Build("test1", components) - if err != nil { - t.Fatalf("err: %s", err) - } - - coreBuild, ok := build.(*coreBuild) - if !ok { - t.Fatal("should be ok") - } - if coreBuild.builder != builder { - t.Fatalf("bad: %#v", coreBuild.builder) - } - if !reflect.DeepEqual(coreBuild.builderConfig, expectedConfig) { - t.Fatalf("bad: %#v", coreBuild.builderConfig) - } - if len(coreBuild.provisioners) != 1 { - t.Fatalf("bad: %#v", coreBuild.provisioners) - } - if len(coreBuild.postProcessors) != 2 { - t.Fatalf("bad: %#v", coreBuild.postProcessors) - } - - if len(coreBuild.postProcessors[0]) != 1 { - t.Fatalf("bad: %#v", coreBuild.postProcessors[0]) - } - if len(coreBuild.postProcessors[1]) != 2 { - t.Fatalf("bad: %#v", coreBuild.postProcessors[1]) - } - - if coreBuild.postProcessors[1][0].keepInputArtifact { - t.Fatal("postProcessors[1][0] should not keep input artifact") - } - if !coreBuild.postProcessors[1][1].keepInputArtifact { - t.Fatal("postProcessors[1][1] should keep input artifact") - } - - config := coreBuild.postProcessors[1][1].config - if _, ok := config["keep_input_artifact"]; ok { - t.Fatal("should not have keep_input_artifact") - } -} - -func TestTemplateBuild_exceptOnlyPP(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "post-processors": [ - { - "type": "test-pp", - "except": ["test1"], - "only": ["test1"] - } - ] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestTemplateBuild_exceptOnlyProv(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - "except": ["test1"], - "only": ["test1"] - } - ] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestTemplateBuild_exceptPPInvalid(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "post-processors": [ - { - "type": "test-pp", - "except": ["test5"] - } - ] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestTemplateBuild_exceptPP(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "post-processors": [ - { - "type": "test-pp", - "except": ["test1"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - // Verify test1 has no post-processors - build, err := template.Build("test1", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild := build.(*coreBuild) - if len(cbuild.postProcessors) > 0 { - t.Fatal("should have no postProcessors") - } - - // Verify test2 has one post-processors - build, err = template.Build("test2", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild = build.(*coreBuild) - if len(cbuild.postProcessors) != 1 { - t.Fatalf("invalid: %d", len(cbuild.postProcessors)) - } -} - -func TestTemplateBuild_exceptPPConfigTemplateName(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1-{{user \"foo\"}}", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "post-processors": [ - { - "type": "test-pp", - "except": ["test1-{{user \"foo\"}}"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) - if err != nil { - t.Fatalf("err: %s", err) - } - - // Verify test1 has no post-processors - build, err := template.Build("test1-{{user \"foo\"}}", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild := build.(*coreBuild) - if len(cbuild.postProcessors) > 0 { - t.Fatal("should have no postProcessors") - } - - // Verify test2 has one post-processors - build, err = template.Build("test2", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild = build.(*coreBuild) - if len(cbuild.postProcessors) != 1 { - t.Fatalf("invalid: %d", len(cbuild.postProcessors)) - } -} - -func TestTemplateBuild_exceptProvInvalid(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - "except": ["test5"] - } - ] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestTemplateBuild_exceptProv(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - "except": ["test1"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - // Verify test1 has no provisioners - build, err := template.Build("test1", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild := build.(*coreBuild) - if len(cbuild.provisioners) > 0 { - t.Fatal("should have no provisioners") - } - - // Verify test2 has one provisioners - build, err = template.Build("test2", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild = build.(*coreBuild) - if len(cbuild.provisioners) != 1 { - t.Fatalf("invalid: %d", len(cbuild.provisioners)) - } -} - -func TestTemplateBuild_exceptProvConfigTemplateName(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1-{{user \"foo\"}}", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - "except": ["test1-{{user \"foo\"}}"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) - if err != nil { - t.Fatalf("err: %s", err) - } - - // Verify test1 has no provisioners - build, err := template.Build("test1-{{user \"foo\"}}", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild := build.(*coreBuild) - if len(cbuild.provisioners) > 0 { - t.Fatal("should have no provisioners") - } - - // Verify test2 has one provisioners - build, err = template.Build("test2", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild = build.(*coreBuild) - if len(cbuild.provisioners) != 1 { - t.Fatalf("invalid: %d", len(cbuild.provisioners)) - } -} - -func TestTemplateBuild_onlyPPInvalid(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "post-processors": [ - { - "type": "test-pp", - "only": ["test5"] - } - ] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestTemplateBuild_onlyPP(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "post-processors": [ - { - "type": "test-pp", - "only": ["test2"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - // Verify test1 has no post-processors - build, err := template.Build("test1", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild := build.(*coreBuild) - if len(cbuild.postProcessors) > 0 { - t.Fatal("should have no postProcessors") - } - - // Verify test2 has one post-processors - build, err = template.Build("test2", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild = build.(*coreBuild) - if len(cbuild.postProcessors) != 1 { - t.Fatalf("invalid: %d", len(cbuild.postProcessors)) - } -} - -func TestTemplateBuild_onlyPPConfigTemplateName(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2-{{user \"foo\"}}", - "type": "test-builder" - } - ], - - "post-processors": [ - { - "type": "test-pp", - "only": ["test2-{{user \"foo\"}}"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) - if err != nil { - t.Fatalf("err: %s", err) - } - - // Verify test1 has no post-processors - build, err := template.Build("test1", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild := build.(*coreBuild) - if len(cbuild.postProcessors) > 0 { - t.Fatal("should have no postProcessors") - } - - // Verify test2 has one post-processors - build, err = template.Build("test2-{{user \"foo\"}}", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild = build.(*coreBuild) - if len(cbuild.postProcessors) != 1 { - t.Fatalf("invalid: %d", len(cbuild.postProcessors)) - } -} - -func TestTemplateBuild_onlyProvInvalid(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - "only": ["test5"] - } - ] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestTemplateBuild_onlyProv(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - "only": ["test2"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - // Verify test1 has no provisioners - build, err := template.Build("test1", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild := build.(*coreBuild) - if len(cbuild.provisioners) > 0 { - t.Fatal("should have no provisioners") - } - - // Verify test2 has one provisioners - build, err = template.Build("test2", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild = build.(*coreBuild) - if len(cbuild.provisioners) != 1 { - t.Fatalf("invalid: %d", len(cbuild.provisioners)) - } -} - -func TestTemplateBuild_onlyProvConfigTemplateName(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - }, - { - "name": "test2-{{user \"foo\"}}", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - "only": ["test2-{{user \"foo\"}}"] - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{"foo": "bar"}) - if err != nil { - t.Fatalf("err: %s", err) - } - - // Verify test1 has no provisioners - build, err := template.Build("test1", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild := build.(*coreBuild) - if len(cbuild.provisioners) > 0 { - t.Fatal("should have no provisioners") - } - - // Verify test2 has one provisioners - build, err = template.Build("test2-{{user \"foo\"}}", testTemplateComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - cbuild = build.(*coreBuild) - if len(cbuild.provisioners) != 1 { - t.Fatalf("invalid: %d", len(cbuild.provisioners)) - } -} - -func TestTemplate_Build_ProvisionerOverride(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - - "override": { - "test1": {} - } - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - RawConfig := template.Provisioners[0].RawConfig - if RawConfig == nil { - t.Fatal("missing provisioner raw config") - } - - expected := map[string]interface{}{ - "type": "test-prov", - } - - if !reflect.DeepEqual(RawConfig, expected) { - t.Fatalf("bad raw: %#v", RawConfig) - } - - builder := new(MockBuilder) - builderMap := map[string]Builder{ - "test-builder": builder, - } - - provisioner := &MockProvisioner{} - provisionerMap := map[string]Provisioner{ - "test-prov": provisioner, - } - - builderFactory := func(n string) (Builder, error) { return builderMap[n], nil } - provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil } - components := &ComponentFinder{ - Builder: builderFactory, - Provisioner: provFactory, - } - - // Get the build, verifying we can get it without issue, but also - // that the proper builder was looked up and used for the build. - build, err := template.Build("test1", components) - if err != nil { - t.Fatalf("err: %s", err) - } - - coreBuild, ok := build.(*coreBuild) - if !ok { - t.Fatal("should be okay") - } - if len(coreBuild.provisioners) != 1 { - t.Fatalf("bad: %#v", coreBuild.provisioners) - } - if len(coreBuild.provisioners[0].config) != 2 { - t.Fatalf("bad: %#v", coreBuild.provisioners[0].config) - } -} - -func TestTemplate_Build_ProvisionerOverrideBad(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - - "override": { - "testNope": {} - } - } - ] - } - ` - - _, err := ParseTemplate([]byte(data), nil) - if err == nil { - t.Fatal("should have error") - } -} - -func TestTemplateBuild_ProvisionerPauseBefore(t *testing.T) { - data := ` - { - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ], - - "provisioners": [ - { - "type": "test-prov", - "pause_before": "5s" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - builder := new(MockBuilder) - builderMap := map[string]Builder{ - "test-builder": builder, - } - - provisioner := &MockProvisioner{} - provisionerMap := map[string]Provisioner{ - "test-prov": provisioner, - } - - builderFactory := func(n string) (Builder, error) { return builderMap[n], nil } - provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil } - components := &ComponentFinder{ - Builder: builderFactory, - Provisioner: provFactory, - } - - // Get the build, verifying we can get it without issue, but also - // that the proper builder was looked up and used for the build. - build, err := template.Build("test1", components) - if err != nil { - t.Fatalf("err: %s", err) - } - - coreBuild, ok := build.(*coreBuild) - if !ok { - t.Fatal("should be okay") - } - if len(coreBuild.provisioners) != 1 { - t.Fatalf("bad: %#v", coreBuild.provisioners) - } - if pp, ok := coreBuild.provisioners[0].provisioner.(*PausedProvisioner); !ok { - t.Fatalf("should be paused provisioner") - } else { - if pp.PauseBefore != 5*time.Second { - t.Fatalf("bad: %#v", pp.PauseBefore) - } - } - - config := coreBuild.provisioners[0].config[0].(map[string]interface{}) - if _, ok := config["pause_before"]; ok { - t.Fatal("pause_before should be removed") - } -} - -func TestTemplateBuild_variables(t *testing.T) { - data := ` - { - "variables": { - "foo": "bar" - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), nil) - if err != nil { - t.Fatalf("err: %s", err) - } - - build, err := template.Build("test1", testComponentFinder()) - if err != nil { - t.Fatalf("err: %s", err) - } - - coreBuild, ok := build.(*coreBuild) - if !ok { - t.Fatalf("couldn't convert!") - } - - expected := map[string]string{"foo": "bar"} - if !reflect.DeepEqual(coreBuild.variables, expected) { - t.Fatalf("bad vars: %#v", coreBuild.variables) - } -} - -func TestTemplateBuild_variablesRequiredNotSet(t *testing.T) { - data := ` - { - "variables": { - "foo": null - }, - - "builders": [ - { - "name": "test1", - "type": "test-builder" - } - ] - } - ` - - template, err := ParseTemplate([]byte(data), map[string]string{}) - if err != nil { - t.Fatalf("err: %s", err) - } - - _, err = template.Build("test1", testComponentFinder()) - if err == nil { - t.Fatal("should error") - } -} diff --git a/packer/test-fixtures/build-basic-interpolated.json b/packer/test-fixtures/build-basic-interpolated.json new file mode 100644 index 000000000..c70677c52 --- /dev/null +++ b/packer/test-fixtures/build-basic-interpolated.json @@ -0,0 +1,6 @@ +{ + "builders": [{ + "name": "{{upper `name`}}", + "type": "test" + }] +} diff --git a/packer/test-fixtures/build-basic.json b/packer/test-fixtures/build-basic.json new file mode 100644 index 000000000..d14f6cad3 --- /dev/null +++ b/packer/test-fixtures/build-basic.json @@ -0,0 +1,5 @@ +{ + "builders": [{ + "type": "test" + }] +} diff --git a/packer/test-fixtures/build-names-basic.json b/packer/test-fixtures/build-names-basic.json new file mode 100644 index 000000000..1b0162551 --- /dev/null +++ b/packer/test-fixtures/build-names-basic.json @@ -0,0 +1,5 @@ +{ + "builders": [ + {"type": "something"} + ] +} diff --git a/packer/test-fixtures/build-names-func.json b/packer/test-fixtures/build-names-func.json new file mode 100644 index 000000000..feb28cf37 --- /dev/null +++ b/packer/test-fixtures/build-names-func.json @@ -0,0 +1,5 @@ +{ + "builders": [ + {"type": "{{upper `tubes`}}"} + ] +} diff --git a/packer/test-fixtures/build-pp.json b/packer/test-fixtures/build-pp.json new file mode 100644 index 000000000..e2b32cfac --- /dev/null +++ b/packer/test-fixtures/build-pp.json @@ -0,0 +1,7 @@ +{ + "builders": [{ + "type": "test" + }], + + "post-processors": ["test"] +} diff --git a/packer/test-fixtures/build-prov-override.json b/packer/test-fixtures/build-prov-override.json new file mode 100644 index 000000000..eb3554792 --- /dev/null +++ b/packer/test-fixtures/build-prov-override.json @@ -0,0 +1,14 @@ +{ + "builders": [{ + "type": "test" + }], + + "provisioners": [{ + "type": "test", + "override": { + "test": { + "foo": "bar" + } + } + }] +} diff --git a/packer/test-fixtures/build-prov-skip-include.json b/packer/test-fixtures/build-prov-skip-include.json new file mode 100644 index 000000000..2ba5e77de --- /dev/null +++ b/packer/test-fixtures/build-prov-skip-include.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "test" + }], + + "provisioners": [{ + "type": "test", + "only": ["test"] + }] +} diff --git a/packer/test-fixtures/build-prov-skip.json b/packer/test-fixtures/build-prov-skip.json new file mode 100644 index 000000000..bd9fa5072 --- /dev/null +++ b/packer/test-fixtures/build-prov-skip.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "test" + }], + + "provisioners": [{ + "type": "test", + "only": ["foo"] + }] +} diff --git a/packer/test-fixtures/build-prov.json b/packer/test-fixtures/build-prov.json new file mode 100644 index 000000000..332c28b1d --- /dev/null +++ b/packer/test-fixtures/build-prov.json @@ -0,0 +1,9 @@ +{ + "builders": [{ + "type": "test" + }], + + "provisioners": [{ + "type": "test" + }] +} diff --git a/packer/test-fixtures/validate-dup-builder.json b/packer/test-fixtures/validate-dup-builder.json new file mode 100644 index 000000000..21d206d35 --- /dev/null +++ b/packer/test-fixtures/validate-dup-builder.json @@ -0,0 +1,10 @@ +{ + "builders": [ + {"type": "foo"} + ], + + "provisioners": [{ + "type": "foo", + "only": ["bar"] + }] +} diff --git a/packer/test-fixtures/validate-req-variable.json b/packer/test-fixtures/validate-req-variable.json new file mode 100644 index 000000000..796d0b669 --- /dev/null +++ b/packer/test-fixtures/validate-req-variable.json @@ -0,0 +1,9 @@ +{ + "variables": { + "foo": null + }, + + "builders": [{ + "type": "foo" + }] +} diff --git a/packer/testing.go b/packer/testing.go new file mode 100644 index 000000000..7e7ad0b53 --- /dev/null +++ b/packer/testing.go @@ -0,0 +1,93 @@ +package packer + +import ( + "bytes" + "io/ioutil" + "os" + "testing" +) + +func TestCoreConfig(t *testing.T) *CoreConfig { + // Create some test components + components := ComponentFinder{ + Builder: func(n string) (Builder, error) { + if n != "test" { + return nil, nil + } + + return &MockBuilder{}, nil + }, + } + + return &CoreConfig{ + Cache: &FileCache{CacheDir: os.TempDir()}, + Components: components, + Ui: TestUi(t), + } +} + +func TestCore(t *testing.T, c *CoreConfig) *Core { + core, err := NewCore(c) + if err != nil { + t.Fatalf("err: %s", err) + } + + return core +} + +func TestUi(t *testing.T) Ui { + var buf bytes.Buffer + return &BasicUi{ + Reader: &buf, + Writer: ioutil.Discard, + ErrorWriter: ioutil.Discard, + } +} + +// TestBuilder sets the builder with the name n to the component finder +// and returns the mock. +func TestBuilder(t *testing.T, c *CoreConfig, n string) *MockBuilder { + var b MockBuilder + + c.Components.Builder = func(actual string) (Builder, error) { + if actual != n { + return nil, nil + } + + return &b, nil + } + + return &b +} + +// TestProvisioner sets the prov. with the name n to the component finder +// and returns the mock. +func TestProvisioner(t *testing.T, c *CoreConfig, n string) *MockProvisioner { + var b MockProvisioner + + c.Components.Provisioner = func(actual string) (Provisioner, error) { + if actual != n { + return nil, nil + } + + return &b, nil + } + + return &b +} + +// TestPostProcessor sets the prov. with the name n to the component finder +// and returns the mock. +func TestPostProcessor(t *testing.T, c *CoreConfig, n string) *MockPostProcessor { + var b MockPostProcessor + + c.Components.PostProcessor = func(actual string) (PostProcessor, error) { + if actual != n { + return nil, nil + } + + return &b, nil + } + + return &b +} diff --git a/signal.go b/signal.go index b198558d7..e63dd2fe5 100644 --- a/signal.go +++ b/signal.go @@ -10,7 +10,7 @@ import ( // Prepares the signal handlers so that we handle interrupts properly. // The signal handler exists in a goroutine. -func setupSignalHandlers(env packer.Environment) { +func setupSignalHandlers(ui packer.Ui) { ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt) @@ -20,13 +20,13 @@ func setupSignalHandlers(env packer.Environment) { <-ch log.Println("First interrupt. Ignoring to allow plugins to clean up.") - env.Ui().Error("Interrupt signal received. Cleaning up...") + ui.Error("Interrupt signal received. Cleaning up...") // Second interrupt. Go down hard. <-ch log.Println("Second interrupt. Exiting now.") - env.Ui().Error("Interrupt signal received twice. Forcefully exiting now.") + ui.Error("Interrupt signal received twice. Forcefully exiting now.") // Force kill all the plugins, but mark that we're killing them // first so that we don't get panics everywhere. diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go new file mode 100644 index 000000000..1ddbbe167 --- /dev/null +++ b/template/interpolate/funcs.go @@ -0,0 +1,110 @@ +package interpolate + +import ( + "errors" + "fmt" + "os" + "strconv" + "strings" + "text/template" + "time" + + "github.com/mitchellh/packer/common/uuid" +) + +// InitTime is the UTC time when this package was initialized. It is +// used as the timestamp for all configuration templates so that they +// match for a single build. +var InitTime time.Time + +func init() { + InitTime = time.Now().UTC() +} + +// Funcs are the interpolation funcs that are available within interpolations. +var FuncGens = map[string]FuncGenerator{ + "env": funcGenEnv, + "isotime": funcGenIsotime, + "pwd": funcGenPwd, + "timestamp": funcGenTimestamp, + "uuid": funcGenUuid, + "user": funcGenUser, + + "upper": funcGenPrimitive(strings.ToUpper), + "lower": funcGenPrimitive(strings.ToLower), +} + +// FuncGenerator is a function that given a context generates a template +// function for the template. +type FuncGenerator func(*Context) interface{} + +// Funcs returns the functions that can be used for interpolation given +// a context. +func Funcs(ctx *Context) template.FuncMap { + result := make(map[string]interface{}) + for k, v := range FuncGens { + result[k] = v(ctx) + } + + return template.FuncMap(result) +} + +func funcGenEnv(ctx *Context) interface{} { + return func(k string) (string, error) { + if !ctx.EnableEnv { + // The error message doesn't have to be that detailed since + // semantic checks should catch this. + return "", errors.New("env vars are not allowed here") + } + + return os.Getenv(k), nil + } +} + +func funcGenIsotime(ctx *Context) interface{} { + return func(format ...string) (string, error) { + if len(format) == 0 { + return time.Now().UTC().Format(time.RFC3339), nil + } + + if len(format) > 1 { + return "", fmt.Errorf("too many values, 1 needed: %v", format) + } + + return time.Now().UTC().Format(format[0]), nil + } +} + +func funcGenPrimitive(value interface{}) FuncGenerator { + return func(ctx *Context) interface{} { + return value + } +} + +func funcGenPwd(ctx *Context) interface{} { + return func() (string, error) { + return os.Getwd() + } +} + +func funcGenTimestamp(ctx *Context) interface{} { + return func() string { + return strconv.FormatInt(InitTime.Unix(), 10) + } +} + +func funcGenUser(ctx *Context) interface{} { + return func(k string) string { + if ctx == nil || ctx.UserVariables == nil { + return "" + } + + return ctx.UserVariables[k] + } +} + +func funcGenUuid(ctx *Context) interface{} { + return func() string { + return uuid.TimeOrderedUUID() + } +} diff --git a/template/interpolate/funcs_test.go b/template/interpolate/funcs_test.go new file mode 100644 index 000000000..aad05d376 --- /dev/null +++ b/template/interpolate/funcs_test.go @@ -0,0 +1,178 @@ +package interpolate + +import ( + "os" + "strconv" + "testing" + "time" +) + +func TestFuncEnv(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + { + `{{env "PACKER_TEST_ENV"}}`, + `foo`, + }, + + { + `{{env "PACKER_TEST_ENV_NOPE"}}`, + ``, + }, + } + + os.Setenv("PACKER_TEST_ENV", "foo") + defer os.Setenv("PACKER_TEST_ENV", "") + + ctx := &Context{EnableEnv: true} + for _, tc := range cases { + i := &I{Value: tc.Input} + result, err := i.Render(ctx) + if err != nil { + t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err) + } + + if result != tc.Output { + t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result) + } + } +} + +func TestFuncEnv_disable(t *testing.T) { + cases := []struct { + Input string + Output string + Error bool + }{ + { + `{{env "PACKER_TEST_ENV"}}`, + "", + true, + }, + } + + ctx := &Context{EnableEnv: false} + for _, tc := range cases { + i := &I{Value: tc.Input} + result, err := i.Render(ctx) + if (err != nil) != tc.Error { + t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err) + } + + if result != tc.Output { + t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result) + } + } +} + +func TestFuncIsotime(t *testing.T) { + ctx := &Context{} + i := &I{Value: "{{isotime}}"} + result, err := i.Render(ctx) + if err != nil { + t.Fatalf("err: %s", err) + } + + val, err := time.Parse(time.RFC3339, result) + if err != nil { + t.Fatalf("err: %s", err) + } + + currentTime := time.Now().UTC() + if currentTime.Sub(val) > 2*time.Second { + t.Fatalf("val: %d (current: %d)", val, currentTime) + } +} + +func TestFuncPwd(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatalf("err: %s", err) + } + + cases := []struct { + Input string + Output string + }{ + { + `{{pwd}}`, + wd, + }, + } + + ctx := &Context{} + for _, tc := range cases { + i := &I{Value: tc.Input} + result, err := i.Render(ctx) + if err != nil { + t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err) + } + + if result != tc.Output { + t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result) + } + } +} + +func TestFuncTimestamp(t *testing.T) { + expected := strconv.FormatInt(InitTime.Unix(), 10) + + cases := []struct { + Input string + Output string + }{ + { + `{{timestamp}}`, + expected, + }, + } + + ctx := &Context{} + for _, tc := range cases { + i := &I{Value: tc.Input} + result, err := i.Render(ctx) + if err != nil { + t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err) + } + + if result != tc.Output { + t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result) + } + } +} + +func TestFuncUser(t *testing.T) { + cases := []struct { + Input string + Output string + }{ + { + `{{user "foo"}}`, + `foo`, + }, + + { + `{{user "what"}}`, + ``, + }, + } + + ctx := &Context{ + UserVariables: map[string]string{ + "foo": "foo", + }, + } + for _, tc := range cases { + i := &I{Value: tc.Input} + result, err := i.Render(ctx) + if err != nil { + t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err) + } + + if result != tc.Output { + t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result) + } + } +} diff --git a/template/interpolate/i.go b/template/interpolate/i.go new file mode 100644 index 000000000..d52653fcf --- /dev/null +++ b/template/interpolate/i.go @@ -0,0 +1,54 @@ +package interpolate + +import ( + "bytes" + "text/template" +) + +// Context is the context that an interpolation is done in. This defines +// things such as available variables. +type Context struct { + // Data is the data for the template that is available + Data interface{} + + // UserVariables is the mapping of user variables that the + // "user" function reads from. + UserVariables map[string]string + + // EnableEnv enables the env function + EnableEnv bool +} + +// Render is shorthand for constructing an I and calling Render. +func Render(v string, ctx *Context) (string, error) { + return (&I{Value: v}).Render(ctx) +} + +// I stands for "interpolation" and is the main interpolation struct +// in order to render values. +type I struct { + Value string +} + +// Render renders the interpolation with the given context. +func (i *I) Render(ctx *Context) (string, error) { + tpl, err := i.template(ctx) + if err != nil { + return "", err + } + + var result bytes.Buffer + var data interface{} + if ctx != nil { + data = ctx.Data + } + if err := tpl.Execute(&result, data); err != nil { + return "", err + } + + return result.String(), nil +} + +func (i *I) template(ctx *Context) (*template.Template, error) { + return template.New("root").Funcs(Funcs(ctx)).Parse(i.Value) +} diff --git a/template/interpolate/i_test.go b/template/interpolate/i_test.go new file mode 100644 index 000000000..a678afbc4 --- /dev/null +++ b/template/interpolate/i_test.go @@ -0,0 +1,32 @@ +package interpolate + +import ( + "testing" +) + +func TestIRender(t *testing.T) { + cases := map[string]struct { + Ctx *Context + Value string + Result string + }{ + "basic": { + nil, + "foo", + "foo", + }, + } + + for k, tc := range cases { + i := &I{Value: tc.Value} + result, err := i.Render(tc.Ctx) + if err != nil { + t.Fatalf("%s\n\ninput: %s\n\nerr: %s", k, tc.Value, err) + } + if result != tc.Result { + t.Fatalf( + "%s\n\ninput: %s\n\nexpected: %s\n\ngot: %s", + k, tc.Value, tc.Result, result) + } + } +} diff --git a/template/interpolate/parse.go b/template/interpolate/parse.go new file mode 100644 index 000000000..b18079510 --- /dev/null +++ b/template/interpolate/parse.go @@ -0,0 +1,42 @@ +package interpolate + +import ( + "fmt" + "text/template" + "text/template/parse" +) + +// functionsCalled returns a map (to be used as a set) of the functions +// that are called from the given text template. +func functionsCalled(t *template.Template) map[string]struct{} { + result := make(map[string]struct{}) + functionsCalledWalk(t.Tree.Root, result) + return result +} + +func functionsCalledWalk(raw parse.Node, r map[string]struct{}) { + switch node := raw.(type) { + case *parse.ActionNode: + functionsCalledWalk(node.Pipe, r) + case *parse.CommandNode: + if in, ok := node.Args[0].(*parse.IdentifierNode); ok { + r[in.Ident] = struct{}{} + } + + for _, n := range node.Args[1:] { + functionsCalledWalk(n, r) + } + case *parse.ListNode: + for _, n := range node.Nodes { + functionsCalledWalk(n, r) + } + case *parse.PipeNode: + for _, n := range node.Cmds { + functionsCalledWalk(n, r) + } + case *parse.StringNode, *parse.TextNode: + // Ignore + default: + panic(fmt.Sprintf("unknown type: %T", node)) + } +} diff --git a/template/interpolate/parse_test.go b/template/interpolate/parse_test.go new file mode 100644 index 000000000..3398ddbf1 --- /dev/null +++ b/template/interpolate/parse_test.go @@ -0,0 +1,39 @@ +package interpolate + +import ( + "reflect" + "testing" + "text/template" +) + +func TestFunctionsCalled(t *testing.T) { + cases := []struct { + Input string + Result map[string]struct{} + }{ + { + "foo", + map[string]struct{}{}, + }, + + { + "foo {{user `bar`}}", + map[string]struct{}{ + "user": struct{}{}, + }, + }, + } + + funcs := Funcs(&Context{}) + for _, tc := range cases { + tpl, err := template.New("root").Funcs(funcs).Parse(tc.Input) + if err != nil { + t.Fatalf("err parsing: %v\n\n%s", tc.Input, err) + } + + actual := functionsCalled(tpl) + if !reflect.DeepEqual(actual, tc.Result) { + t.Fatalf("bad: %v\n\ngot: %#v", tc.Input, actual) + } + } +} diff --git a/template/parse.go b/template/parse.go new file mode 100644 index 000000000..5566e31cc --- /dev/null +++ b/template/parse.go @@ -0,0 +1,314 @@ +package template + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "sort" + + "github.com/hashicorp/go-multierror" + "github.com/mitchellh/mapstructure" +) + +// rawTemplate is the direct JSON document format of the template file. +// This is what is decoded directly from the file, and then it is turned +// into a Template object thereafter. +type rawTemplate struct { + MinVersion string `mapstructure:"min_packer_version"` + Description string + + Builders []map[string]interface{} + Push map[string]interface{} + PostProcessors []interface{} `mapstructure:"post-processors"` + Provisioners []map[string]interface{} + Variables map[string]interface{} + + RawContents []byte +} + +// Template returns the actual Template object built from this raw +// structure. +func (r *rawTemplate) Template() (*Template, error) { + var result Template + var errs error + + // Copy some literals + result.Description = r.Description + result.MinVersion = r.MinVersion + result.RawContents = r.RawContents + + // Gather the variables + if len(r.Variables) > 0 { + result.Variables = make(map[string]*Variable, len(r.Variables)) + } + for k, rawV := range r.Variables { + var v Variable + + // Variable is required if the value is exactly nil + v.Required = rawV == nil + + // Weak decode the default if we have one + if err := r.decoder(&v.Default, nil).Decode(rawV); err != nil { + errs = multierror.Append(errs, fmt.Errorf( + "variable %s: %s", k, err)) + continue + } + + result.Variables[k] = &v + } + + // Let's start by gathering all the builders + if len(r.Builders) > 0 { + result.Builders = make(map[string]*Builder, len(r.Builders)) + } + for i, rawB := range r.Builders { + var b Builder + if err := mapstructure.WeakDecode(rawB, &b); err != nil { + errs = multierror.Append(errs, fmt.Errorf( + "builder %d: %s", i+1, err)) + continue + } + + // Set the raw configuration and delete any special keys + b.Config = rawB + delete(b.Config, "name") + delete(b.Config, "type") + if len(b.Config) == 0 { + b.Config = nil + } + + // If there is no type set, it is an error + if b.Type == "" { + errs = multierror.Append(errs, fmt.Errorf( + "builder %d: missing 'type'", i+1)) + continue + } + + // The name defaults to the type if it isn't set + if b.Name == "" { + b.Name = b.Type + } + + // If this builder already exists, it is an error + if _, ok := result.Builders[b.Name]; ok { + errs = multierror.Append(errs, fmt.Errorf( + "builder %d: builder with name '%s' already exists", + i+1, b.Name)) + continue + } + + // Append the builders + result.Builders[b.Name] = &b + } + + // Gather all the post-processors + if len(r.PostProcessors) > 0 { + result.PostProcessors = make([][]*PostProcessor, 0, len(r.PostProcessors)) + } + for i, v := range r.PostProcessors { + // Parse the configurations. We need to do this because post-processors + // can take three different formats. + configs, err := r.parsePostProcessor(i, v) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + + // Parse the PostProcessors out of the configs + pps := make([]*PostProcessor, 0, len(configs)) + for j, c := range configs { + var pp PostProcessor + if err := r.decoder(&pp, nil).Decode(c); err != nil { + errs = multierror.Append(errs, fmt.Errorf( + "post-processor %d.%d: %s", i+1, j+1, err)) + continue + } + + // Type is required + if pp.Type == "" { + errs = multierror.Append(errs, fmt.Errorf( + "post-processor %d.%d: type is required", i+1, j+1)) + continue + } + + // Set the configuration + delete(c, "keep_input_artifact") + delete(c, "type") + if len(c) > 0 { + pp.Config = c + } + + pps = append(pps, &pp) + } + + result.PostProcessors = append(result.PostProcessors, pps) + } + + // Gather all the provisioners + if len(r.Provisioners) > 0 { + result.Provisioners = make([]*Provisioner, 0, len(r.Provisioners)) + } + for i, v := range r.Provisioners { + var p Provisioner + if err := r.decoder(&p, nil).Decode(v); err != nil { + errs = multierror.Append(errs, fmt.Errorf( + "provisioner %d: %s", i+1, err)) + continue + } + + // Type is required before any richer validation + if p.Type == "" { + errs = multierror.Append(errs, fmt.Errorf( + "provisioner %d: missing 'type'", i+1)) + continue + } + + // Copy the configuration + delete(v, "except") + delete(v, "only") + delete(v, "override") + delete(v, "pause_before") + delete(v, "type") + if len(v) > 0 { + p.Config = v + } + + // TODO: stuff + result.Provisioners = append(result.Provisioners, &p) + } + + // Push + if len(r.Push) > 0 { + var p Push + if err := r.decoder(&p, nil).Decode(r.Push); err != nil { + errs = multierror.Append(errs, fmt.Errorf( + "push: %s", err)) + } + + result.Push = &p + } + + // If we have errors, return those with a nil result + if errs != nil { + return nil, errs + } + + return &result, nil +} + +func (r *rawTemplate) decoder( + result interface{}, + md *mapstructure.Metadata) *mapstructure.Decoder { + d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: mapstructure.StringToTimeDurationHookFunc(), + Metadata: md, + Result: result, + }) + if err != nil { + // This really shouldn't happen since we have firm control over + // all the arguments and they're all unit tested. So we use a + // panic here to note this would definitely be a bug. + panic(err) + } + return d +} + +func (r *rawTemplate) parsePostProcessor( + i int, raw interface{}) ([]map[string]interface{}, error) { + switch v := raw.(type) { + case string: + return []map[string]interface{}{ + {"type": v}, + }, nil + case map[string]interface{}: + return []map[string]interface{}{v}, nil + case []interface{}: + var err error + result := make([]map[string]interface{}, len(v)) + for j, innerRaw := range v { + switch innerV := innerRaw.(type) { + case string: + result[j] = map[string]interface{}{"type": innerV} + case map[string]interface{}: + result[j] = innerV + case []interface{}: + err = multierror.Append(err, fmt.Errorf( + "post-processor %d.%d: sequence not allowed to be nested in a sequence", + i+1, j+1)) + default: + err = multierror.Append(err, fmt.Errorf( + "post-processor %d.%d: unknown format", + i+1, j+1)) + } + } + + if err != nil { + return nil, err + } + + return result, nil + default: + return nil, fmt.Errorf("post-processor %d: bad format", i+1) + } +} + +// Parse takes the given io.Reader and parses a Template object out of it. +func Parse(r io.Reader) (*Template, error) { + // Create a buffer to copy what we read + var buf bytes.Buffer + r = io.TeeReader(r, &buf) + + // First, decode the object into an interface{}. We do this instead of + // the rawTemplate directly because we'd rather use mapstructure to + // decode since it has richer errors. + var raw interface{} + if err := json.NewDecoder(r).Decode(&raw); err != nil { + return nil, err + } + + // Create our decoder + var md mapstructure.Metadata + var rawTpl rawTemplate + rawTpl.RawContents = buf.Bytes() + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Metadata: &md, + Result: &rawTpl, + }) + if err != nil { + return nil, err + } + + // Do the actual decode into our structure + if err := decoder.Decode(raw); err != nil { + return nil, err + } + + // Build an error if there are unused root level keys + if len(md.Unused) > 0 { + sort.Strings(md.Unused) + for _, unused := range md.Unused { + err = multierror.Append(err, fmt.Errorf( + "Unknown root level key in template: '%s'", unused)) + } + + // Return early for these errors + return nil, err + } + + // Return the template parsed from the raw structure + return rawTpl.Template() +} + +// ParseFile is the same as Parse but is a helper to automatically open +// a file for parsing. +func ParseFile(path string) (*Template, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + return Parse(f) +} diff --git a/template/parse_test.go b/template/parse_test.go new file mode 100644 index 000000000..e99f35e51 --- /dev/null +++ b/template/parse_test.go @@ -0,0 +1,300 @@ +package template + +import ( + "reflect" + "strings" + "testing" + "time" +) + +func TestParse(t *testing.T) { + cases := []struct { + File string + Result *Template + Err bool + }{ + /* + * Builders + */ + { + "parse-basic.json", + &Template{ + Builders: map[string]*Builder{ + "something": &Builder{ + Name: "something", + Type: "something", + }, + }, + }, + false, + }, + { + "parse-builder-no-type.json", + nil, + true, + }, + { + "parse-builder-repeat.json", + nil, + true, + }, + + /* + * Provisioners + */ + { + "parse-provisioner-basic.json", + &Template{ + Provisioners: []*Provisioner{ + &Provisioner{ + Type: "something", + }, + }, + }, + false, + }, + + { + "parse-provisioner-pause-before.json", + &Template{ + Provisioners: []*Provisioner{ + &Provisioner{ + Type: "something", + PauseBefore: 1 * time.Second, + }, + }, + }, + false, + }, + + { + "parse-provisioner-only.json", + &Template{ + Provisioners: []*Provisioner{ + &Provisioner{ + Type: "something", + OnlyExcept: OnlyExcept{ + Only: []string{"foo"}, + }, + }, + }, + }, + false, + }, + + { + "parse-provisioner-except.json", + &Template{ + Provisioners: []*Provisioner{ + &Provisioner{ + Type: "something", + OnlyExcept: OnlyExcept{ + Except: []string{"foo"}, + }, + }, + }, + }, + false, + }, + + { + "parse-provisioner-override.json", + &Template{ + Provisioners: []*Provisioner{ + &Provisioner{ + Type: "something", + Override: map[string]interface{}{ + "foo": map[string]interface{}{}, + }, + }, + }, + }, + false, + }, + + { + "parse-provisioner-no-type.json", + nil, + true, + }, + + { + "parse-variable-default.json", + &Template{ + Variables: map[string]*Variable{ + "foo": &Variable{ + Default: "foo", + }, + }, + }, + false, + }, + + { + "parse-variable-required.json", + &Template{ + Variables: map[string]*Variable{ + "foo": &Variable{ + Required: true, + }, + }, + }, + false, + }, + + { + "parse-pp-basic.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + Config: map[string]interface{}{ + "foo": "bar", + }, + }, + }, + }, + }, + false, + }, + + { + "parse-pp-keep.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + KeepInputArtifact: true, + }, + }, + }, + }, + false, + }, + + { + "parse-pp-string.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + }, + }, + }, + }, + false, + }, + + { + "parse-pp-map.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + }, + }, + }, + }, + false, + }, + + { + "parse-pp-slice.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + }, + }, + []*PostProcessor{ + &PostProcessor{ + Type: "bar", + }, + }, + }, + }, + false, + }, + + { + "parse-pp-multi.json", + &Template{ + PostProcessors: [][]*PostProcessor{ + []*PostProcessor{ + &PostProcessor{ + Type: "foo", + }, + &PostProcessor{ + Type: "bar", + }, + }, + }, + }, + false, + }, + + { + "parse-pp-no-type.json", + nil, + true, + }, + + { + "parse-description.json", + &Template{ + Description: "foo", + }, + false, + }, + + { + "parse-min-version.json", + &Template{ + MinVersion: "1.2", + }, + false, + }, + + { + "parse-push.json", + &Template{ + Push: &Push{ + Name: "foo", + }, + }, + false, + }, + } + + for _, tc := range cases { + tpl, err := ParseFile(fixtureDir(tc.File)) + if (err != nil) != tc.Err { + t.Fatalf("err: %s", err) + } + + if tpl != nil { + tpl.RawContents = nil + } + if !reflect.DeepEqual(tpl, tc.Result) { + t.Fatalf("bad: %s\n\n%#v\n\n%#v", tc.File, tpl, tc.Result) + } + } +} + +func TestParse_contents(t *testing.T) { + tpl, err := ParseFile(fixtureDir("parse-contents.json")) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(string(tpl.RawContents)) + expected := `{"builders":[{"type":"test"}]}` + if actual != expected { + t.Fatalf("bad: %s\n\n%s", actual, expected) + } +} diff --git a/template/template.go b/template/template.go new file mode 100644 index 000000000..1ab3c668e --- /dev/null +++ b/template/template.go @@ -0,0 +1,197 @@ +package template + +import ( + "errors" + "fmt" + "time" + + "github.com/hashicorp/go-multierror" +) + +// Template represents the parsed template that is used to configure +// Packer builds. +type Template struct { + Description string + MinVersion string + + Variables map[string]*Variable + Builders map[string]*Builder + Provisioners []*Provisioner + PostProcessors [][]*PostProcessor + Push *Push + + // RawContents is just the raw data for this template + RawContents []byte +} + +// Builder represents a builder configured in the template +type Builder struct { + Name string + Type string + Config map[string]interface{} +} + +// PostProcessor represents a post-processor within the template. +type PostProcessor struct { + OnlyExcept `mapstructure:",squash"` + + Type string + KeepInputArtifact bool `mapstructure:"keep_input_artifact"` + Config map[string]interface{} +} + +// Provisioner represents a provisioner within the template. +type Provisioner struct { + OnlyExcept `mapstructure:",squash"` + + Type string + Config map[string]interface{} + Override map[string]interface{} + PauseBefore time.Duration `mapstructure:"pause_before"` +} + +// Push represents the configuration for pushing the template to Atlas. +type Push struct { + Name string + Address string + BaseDir string `mapstructure:"base_dir"` + Include []string + Exclude []string + Token string + VCS bool +} + +// Variable represents a variable within the template +type Variable struct { + Default string + Required bool +} + +// OnlyExcept is a struct that is meant to be embedded that contains the +// logic required for "only" and "except" meta-parameters. +type OnlyExcept struct { + Only []string + Except []string +} + +//------------------------------------------------------------------- +// Functions +//------------------------------------------------------------------- + +// Validate does some basic validation of the template on top of the +// validation that occurs while parsing. If possible, we try to defer +// validation to here. The validation errors that occur during parsing +// are the minimal necessary to make sure parsing builds a reasonable +// Template structure. +func (t *Template) Validate() error { + var err error + + // At least one builder must be defined + if len(t.Builders) == 0 { + err = multierror.Append(err, errors.New( + "at least one builder must be defined")) + } + + // Verify that the provisioner overrides target builders that exist + for i, p := range t.Provisioners { + // Validate only/except + if verr := p.OnlyExcept.Validate(t); verr != nil { + for _, e := range multierror.Append(verr).Errors { + err = multierror.Append(err, fmt.Errorf( + "provisioner %d: %s", i+1, e)) + } + } + + // Validate overrides + for name, _ := range p.Override { + if _, ok := t.Builders[name]; !ok { + err = multierror.Append(err, fmt.Errorf( + "provisioner %d: override '%s' doesn't exist", + i+1, name)) + } + } + } + + // Verify post-processors + for i, chain := range t.PostProcessors { + for j, p := range chain { + // Validate only/except + if verr := p.OnlyExcept.Validate(t); verr != nil { + for _, e := range multierror.Append(verr).Errors { + err = multierror.Append(err, fmt.Errorf( + "post-processor %d.%d: %s", i+1, j+1, e)) + } + } + } + } + + return err +} + +// Skip says whether or not to skip the build with the given name. +func (o *OnlyExcept) Skip(n string) bool { + if len(o.Only) > 0 { + for _, v := range o.Only { + if v == n { + return false + } + } + + return true + } + + if len(o.Except) > 0 { + for _, v := range o.Except { + if v == n { + return true + } + } + + return false + } + + return false +} + +// Validate validates that the OnlyExcept settings are correct for a thing. +func (o *OnlyExcept) Validate(t *Template) error { + if len(o.Only) > 0 && len(o.Except) > 0 { + return errors.New("only one of 'only' or 'except' may be specified") + } + + var err error + for _, n := range o.Only { + if _, ok := t.Builders[n]; !ok { + err = multierror.Append(err, fmt.Errorf( + "'only' specified builder '%s' not found", n)) + } + } + for _, n := range o.Except { + if _, ok := t.Builders[n]; !ok { + err = multierror.Append(err, fmt.Errorf( + "'except' specified builder '%s' not found", n)) + } + } + + return err +} + +//------------------------------------------------------------------- +// GoStringer +//------------------------------------------------------------------- + +func (b *Builder) GoString() string { + return fmt.Sprintf("*%#v", *b) +} + +func (p *Provisioner) GoString() string { + return fmt.Sprintf("*%#v", *p) +} + +func (p *PostProcessor) GoString() string { + return fmt.Sprintf("*%#v", *p) +} + +func (v *Variable) GoString() string { + return fmt.Sprintf("*%#v", *v) +} diff --git a/template/template_test.go b/template/template_test.go new file mode 100644 index 000000000..6fa39ab88 --- /dev/null +++ b/template/template_test.go @@ -0,0 +1,137 @@ +package template + +import ( + "os" + "path/filepath" + "testing" +) + +const FixturesDir = "./test-fixtures" + +// fixtureDir returns the path to a test fixtures directory +func fixtureDir(n string) string { + return filepath.Join(FixturesDir, n) +} + +func TestTemplateValidate(t *testing.T) { + cases := []struct { + File string + Err bool + }{ + { + "validate-no-builders.json", + true, + }, + + { + "validate-bad-override.json", + true, + }, + + { + "validate-good-override.json", + false, + }, + + { + "validate-bad-prov-only.json", + true, + }, + + { + "validate-good-prov-only.json", + false, + }, + + { + "validate-bad-prov-except.json", + true, + }, + + { + "validate-good-prov-except.json", + false, + }, + + { + "validate-bad-pp-only.json", + true, + }, + + { + "validate-good-pp-only.json", + false, + }, + + { + "validate-bad-pp-except.json", + true, + }, + + { + "validate-good-pp-except.json", + false, + }, + } + + for _, tc := range cases { + f, err := os.Open(fixtureDir(tc.File)) + if err != nil { + t.Fatalf("err: %s", err) + } + + tpl, err := Parse(f) + f.Close() + if err != nil { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + + err = tpl.Validate() + if (err != nil) != tc.Err { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + } +} + +func TestOnlyExceptSkip(t *testing.T) { + cases := []struct { + Only, Except []string + Input string + Result bool + }{ + { + []string{"foo"}, + nil, + "foo", + false, + }, + + { + nil, + []string{"foo"}, + "foo", + true, + }, + + { + nil, + nil, + "foo", + false, + }, + } + + for _, tc := range cases { + oe := &OnlyExcept{ + Only: tc.Only, + Except: tc.Except, + } + + actual := oe.Skip(tc.Input) + if actual != tc.Result { + t.Fatalf( + "bad: %#v\n\n%#v\n\n%#v\n\n%#v", + actual, tc.Only, tc.Except, tc.Input) + } + } +} diff --git a/template/test-fixtures/parse-basic.json b/template/test-fixtures/parse-basic.json new file mode 100644 index 000000000..43b7a7898 --- /dev/null +++ b/template/test-fixtures/parse-basic.json @@ -0,0 +1,3 @@ +{ + "builders": [{"type": "something"}] +} diff --git a/template/test-fixtures/parse-builder-no-type.json b/template/test-fixtures/parse-builder-no-type.json new file mode 100644 index 000000000..1729d0827 --- /dev/null +++ b/template/test-fixtures/parse-builder-no-type.json @@ -0,0 +1,3 @@ +{ + "builders": [{"foo": "something"}] +} diff --git a/template/test-fixtures/parse-builder-repeat.json b/template/test-fixtures/parse-builder-repeat.json new file mode 100644 index 000000000..258b75883 --- /dev/null +++ b/template/test-fixtures/parse-builder-repeat.json @@ -0,0 +1,6 @@ +{ + "builders": [ + {"type": "something"}, + {"type": "something"} + ] +} diff --git a/template/test-fixtures/parse-contents.json b/template/test-fixtures/parse-contents.json new file mode 100644 index 000000000..edd70c12a --- /dev/null +++ b/template/test-fixtures/parse-contents.json @@ -0,0 +1 @@ +{"builders":[{"type":"test"}]} diff --git a/template/test-fixtures/parse-description.json b/template/test-fixtures/parse-description.json new file mode 100644 index 000000000..c72a24eb8 --- /dev/null +++ b/template/test-fixtures/parse-description.json @@ -0,0 +1,3 @@ +{ + "description": "foo" +} diff --git a/template/test-fixtures/parse-min-version.json b/template/test-fixtures/parse-min-version.json new file mode 100644 index 000000000..f98101efb --- /dev/null +++ b/template/test-fixtures/parse-min-version.json @@ -0,0 +1,3 @@ +{ + "min_packer_version": "1.2" +} diff --git a/template/test-fixtures/parse-pp-basic.json b/template/test-fixtures/parse-pp-basic.json new file mode 100644 index 000000000..56a1145e6 --- /dev/null +++ b/template/test-fixtures/parse-pp-basic.json @@ -0,0 +1,6 @@ +{ + "post-processors": [{ + "type": "foo", + "foo": "bar" + }] +} diff --git a/template/test-fixtures/parse-pp-keep.json b/template/test-fixtures/parse-pp-keep.json new file mode 100644 index 000000000..d0bd513e5 --- /dev/null +++ b/template/test-fixtures/parse-pp-keep.json @@ -0,0 +1,6 @@ +{ + "post-processors": [{ + "type": "foo", + "keep_input_artifact": true + }] +} diff --git a/template/test-fixtures/parse-pp-map.json b/template/test-fixtures/parse-pp-map.json new file mode 100644 index 000000000..43cc6abb8 --- /dev/null +++ b/template/test-fixtures/parse-pp-map.json @@ -0,0 +1,5 @@ +{ + "post-processors": [{ + "type": "foo" + }] +} diff --git a/template/test-fixtures/parse-pp-multi.json b/template/test-fixtures/parse-pp-multi.json new file mode 100644 index 000000000..32a60fa34 --- /dev/null +++ b/template/test-fixtures/parse-pp-multi.json @@ -0,0 +1,5 @@ +{ + "post-processors": [[{ + "type": "foo" + }, "bar"]] +} diff --git a/template/test-fixtures/parse-pp-no-type.json b/template/test-fixtures/parse-pp-no-type.json new file mode 100644 index 000000000..f4dda63e8 --- /dev/null +++ b/template/test-fixtures/parse-pp-no-type.json @@ -0,0 +1,5 @@ +{ + "post-processors": [{ + "keep_input_artifact": true + }] +} diff --git a/template/test-fixtures/parse-pp-slice.json b/template/test-fixtures/parse-pp-slice.json new file mode 100644 index 000000000..94c3a5247 --- /dev/null +++ b/template/test-fixtures/parse-pp-slice.json @@ -0,0 +1,5 @@ +{ + "post-processors": [{ + "type": "foo" + }, "bar"] +} diff --git a/template/test-fixtures/parse-pp-string.json b/template/test-fixtures/parse-pp-string.json new file mode 100644 index 000000000..8e77358ea --- /dev/null +++ b/template/test-fixtures/parse-pp-string.json @@ -0,0 +1,3 @@ +{ + "post-processors": ["foo"] +} diff --git a/template/test-fixtures/parse-provisioner-basic.json b/template/test-fixtures/parse-provisioner-basic.json new file mode 100644 index 000000000..bf0d8d910 --- /dev/null +++ b/template/test-fixtures/parse-provisioner-basic.json @@ -0,0 +1,5 @@ +{ + "provisioners": [ + {"type": "something"} + ] +} diff --git a/template/test-fixtures/parse-provisioner-except.json b/template/test-fixtures/parse-provisioner-except.json new file mode 100644 index 000000000..8c7f0c8f5 --- /dev/null +++ b/template/test-fixtures/parse-provisioner-except.json @@ -0,0 +1,8 @@ +{ + "provisioners": [ + { + "type": "something", + "except": ["foo"] + } + ] +} diff --git a/template/test-fixtures/parse-provisioner-no-type.json b/template/test-fixtures/parse-provisioner-no-type.json new file mode 100644 index 000000000..40bc214d2 --- /dev/null +++ b/template/test-fixtures/parse-provisioner-no-type.json @@ -0,0 +1,5 @@ +{ + "provisioners": [ + {"foo": "something"} + ] +} diff --git a/template/test-fixtures/parse-provisioner-only.json b/template/test-fixtures/parse-provisioner-only.json new file mode 100644 index 000000000..3bbb534b2 --- /dev/null +++ b/template/test-fixtures/parse-provisioner-only.json @@ -0,0 +1,8 @@ +{ + "provisioners": [ + { + "type": "something", + "only": ["foo"] + } + ] +} diff --git a/template/test-fixtures/parse-provisioner-override.json b/template/test-fixtures/parse-provisioner-override.json new file mode 100644 index 000000000..5b55099ba --- /dev/null +++ b/template/test-fixtures/parse-provisioner-override.json @@ -0,0 +1,10 @@ +{ + "provisioners": [ + { + "type": "something", + "override": { + "foo": {} + } + } + ] +} diff --git a/template/test-fixtures/parse-provisioner-pause-before.json b/template/test-fixtures/parse-provisioner-pause-before.json new file mode 100644 index 000000000..70640847b --- /dev/null +++ b/template/test-fixtures/parse-provisioner-pause-before.json @@ -0,0 +1,8 @@ +{ + "provisioners": [ + { + "type": "something", + "pause_before": "1s" + } + ] +} diff --git a/template/test-fixtures/parse-push.json b/template/test-fixtures/parse-push.json new file mode 100644 index 000000000..2529eedc4 --- /dev/null +++ b/template/test-fixtures/parse-push.json @@ -0,0 +1,5 @@ +{ + "push": { + "name": "foo" + } +} diff --git a/template/test-fixtures/parse-variable-default.json b/template/test-fixtures/parse-variable-default.json new file mode 100644 index 000000000..05192b64d --- /dev/null +++ b/template/test-fixtures/parse-variable-default.json @@ -0,0 +1,5 @@ +{ + "variables": { + "foo": "foo" + } +} diff --git a/template/test-fixtures/parse-variable-required.json b/template/test-fixtures/parse-variable-required.json new file mode 100644 index 000000000..ca6458aaa --- /dev/null +++ b/template/test-fixtures/parse-variable-required.json @@ -0,0 +1,5 @@ +{ + "variables": { + "foo": null + } +} diff --git a/template/test-fixtures/validate-bad-override.json b/template/test-fixtures/validate-bad-override.json new file mode 100644 index 000000000..7f6c64588 --- /dev/null +++ b/template/test-fixtures/validate-bad-override.json @@ -0,0 +1,12 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "override": { + "bar": {} + } + }] +} diff --git a/template/test-fixtures/validate-bad-pp-except.json b/template/test-fixtures/validate-bad-pp-except.json new file mode 100644 index 000000000..3f66cd8ca --- /dev/null +++ b/template/test-fixtures/validate-bad-pp-except.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "post-processors": [{ + "type": "bar", + "except": ["bar"] + }] +} diff --git a/template/test-fixtures/validate-bad-pp-only.json b/template/test-fixtures/validate-bad-pp-only.json new file mode 100644 index 000000000..a79edcb80 --- /dev/null +++ b/template/test-fixtures/validate-bad-pp-only.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "post-processors": [{ + "type": "bar", + "only": ["bar"] + }] +} diff --git a/template/test-fixtures/validate-bad-prov-except.json b/template/test-fixtures/validate-bad-prov-except.json new file mode 100644 index 000000000..0a24bf58e --- /dev/null +++ b/template/test-fixtures/validate-bad-prov-except.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "except": ["bar"] + }] +} diff --git a/template/test-fixtures/validate-bad-prov-only.json b/template/test-fixtures/validate-bad-prov-only.json new file mode 100644 index 000000000..fa8c9ccab --- /dev/null +++ b/template/test-fixtures/validate-bad-prov-only.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "only": ["bar"] + }] +} diff --git a/template/test-fixtures/validate-good-override.json b/template/test-fixtures/validate-good-override.json new file mode 100644 index 000000000..4d7e0f757 --- /dev/null +++ b/template/test-fixtures/validate-good-override.json @@ -0,0 +1,12 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "override": { + "foo": {} + } + }] +} diff --git a/template/test-fixtures/validate-good-pp-except.json b/template/test-fixtures/validate-good-pp-except.json new file mode 100644 index 000000000..79a1b2a24 --- /dev/null +++ b/template/test-fixtures/validate-good-pp-except.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "post-processors": [{ + "type": "bar", + "except": ["foo"] + }] +} diff --git a/template/test-fixtures/validate-good-pp-only.json b/template/test-fixtures/validate-good-pp-only.json new file mode 100644 index 000000000..24ef7c95d --- /dev/null +++ b/template/test-fixtures/validate-good-pp-only.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "post-processors": [{ + "type": "bar", + "only": ["foo"] + }] +} diff --git a/template/test-fixtures/validate-good-prov-except.json b/template/test-fixtures/validate-good-prov-except.json new file mode 100644 index 000000000..e075d09ca --- /dev/null +++ b/template/test-fixtures/validate-good-prov-except.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "except": ["foo"] + }] +} diff --git a/template/test-fixtures/validate-good-prov-only.json b/template/test-fixtures/validate-good-prov-only.json new file mode 100644 index 000000000..db162fa28 --- /dev/null +++ b/template/test-fixtures/validate-good-prov-only.json @@ -0,0 +1,10 @@ +{ + "builders": [{ + "type": "foo" + }], + + "provisioners": [{ + "type": "bar", + "only": ["foo"] + }] +} diff --git a/template/test-fixtures/validate-no-builders.json b/template/test-fixtures/validate-no-builders.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/template/test-fixtures/validate-no-builders.json @@ -0,0 +1 @@ +{}