Commit 3a24cf9e authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

Merge pull request #2134 from mitchellh/f-template-new

Refactor core, move template parsing into a sep package
parents 168fe986 8df1bca5
......@@ -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))
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 {
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
}
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())
// 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
}
userVars, err := buildOptions.AllUserVars()
// Get the core
core, err := c.Meta.Core(tpl)
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(err.Error())
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 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 {
env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err))
return 1
c.Ui.Error(fmt.Sprintf(
"Failed to initialize build '%s': %s",
n, err))
}
// The component finder for our builds
components := &packer.ComponentFinder{
Builder: env.Builder,
Hook: env.Hook,
PostProcessor: env.PostProcessor,
Provisioner: env.Provisioner,
}
// Go through each builder and compile the builds that we care about
builds, err := buildOptions.Builds(tpl, components)
if err != nil {
env.Ui().Error(err.Error())
return 1
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 {
......
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,
},
}
}
......@@ -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))
return 1
}
cmdFlags := flag.NewFlagSet("fix", flag.ContinueOnError)
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
flags := c.Meta.FlagSet("fix", FlagSetNone)
flags.Usage = func() { c.Ui.Say(c.Help()) }
if err := flags.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
}
......
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 != "" {
......
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
}
// 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
}
func (m *Meta) Environment() (packer.Environment, error) {
return packer.NewEnvironment(m.EnvConfig)
// ValidateFlags should be called after parsing flags to validate the
// given flags
func (m *Meta) ValidateFlags() error {
// TODO
return nil
}
......@@ -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
}
......
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())
// 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
}
userVars, err := buildOptions.AllUserVars()
if err != nil {
env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err))
env.Ui().Error("")
env.Ui().Error(c.Help())
return 1
// If we're only checking syntax, then we're done already
if cfgSyntaxOnly {
c.Ui.Say("Syntax-only check passed. Everything looks okay.")
return 0
}
// Parse the template into a machine-usable format
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
}
if cfgSyntaxOnly {
env.Ui().Say("Syntax-only check passed. Everything looks okay.")
return 0
}
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
}
......
......@@ -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))
......
......@@ -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{
......
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
}
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")
}
}
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
}
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
}
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")
}
}
......@@ -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"`
......
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
}
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
}
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)
}
}
}
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)
}
}
}
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
}
package command
package sliceflag
import (
"flag"
......@@ -6,16 +6,16 @@ import (
"testing"
)
func TestAppendSliceValue_implements(t *testing.T) {
func TestStringFlag_implements(t *testing.T) {
var raw interface{}
raw = new(AppendSliceValue)
raw = new(StringFlag)
if _, ok := raw.(flag.Value); !ok {
t.Fatalf("AppendSliceValue should be a Value")
t.Fatalf("StringFlag should be a Value")
}
}
func TestAppendSliceValueSet(t *testing.T) {
sv := new(AppendSliceValue)
func TestStringFlagSet(t *testing.T) {
sv := new(StringFlag)
err := sv.Set("foo")
if err != nil {
t.Fatalf("err: %s", err)
......@@ -31,24 +31,3 @@ func TestAppendSliceValueSet(t *testing.T) {
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)
}
}
......@@ -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,
}
......
......@@ -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,
},
},
}
......
......@@ -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
......
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
}
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
}
// 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
}
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())
}
}
package packer
import (
"path/filepath"
)
const FixtureDir = "./test-fixtures"
func fixtureDir(n string) string {
return filepath.Join(FixtureDir, n)
}
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
}
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
}
......@@ -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,
......
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
}
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)
}
......@@ -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,
......
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
}
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")
}
}
{
"builders": [{
"name": "{{upper `name`}}",
"type": "test"
}]
}
{
"builders": [{
"type": "test"
}]
}
{
"builders": [
{"type": "something"}
]
}
{
"builders": [
{"type": "{{upper `tubes`}}"}
]
}
{
"builders": [{
"type": "test"
}],
"post-processors": ["test"]
}
{
"builders": [{
"type": "test"
}],
"provisioners": [{
"type": "test",
"override": {
"test": {
"foo": "bar"
}
}
}]
}
{
"builders": [{
"type": "test"
}],
"provisioners": [{
"type": "test",
"only": ["test"]
}]
}
{
"builders": [{
"type": "test"
}],
"provisioners": [{
"type": "test",
"only": ["foo"]
}]
}
{
"builders": [{
"type": "test"
}],
"provisioners": [{
"type": "test"
}]
}
{
"builders": [
{"type": "foo"}
],
"provisioners": [{
"type": "foo",
"only": ["bar"]
}]
}
{
"variables": {
"foo": null
},
"builders": [{
"type": "foo"
}]
}
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
}
......@@ -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.
......
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()
}
}
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)
}
}
}
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)
}
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)
}
}
}
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))
}
}
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)
}
}
}
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)
}
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)
}
}
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)
}
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)
}
}
}
{
"builders": [{"type": "something"}]
}
{
"builders": [{"foo": "something"}]
}
{
"builders": [
{"type": "something"},
{"type": "something"}
]
}
{"builders":[{"type":"test"}]}
{
"post-processors": [{
"type": "foo",
"foo": "bar"
}]
}
{
"post-processors": [{
"type": "foo",
"keep_input_artifact": true
}]
}
{
"post-processors": [{
"type": "foo"
}]
}
{
"post-processors": [[{
"type": "foo"
}, "bar"]]
}
{
"post-processors": [{
"keep_input_artifact": true
}]
}
{
"post-processors": [{
"type": "foo"
}, "bar"]
}
{
"post-processors": ["foo"]
}
{
"provisioners": [
{"type": "something"}
]
}
{
"provisioners": [
{
"type": "something",
"except": ["foo"]
}
]
}
{
"provisioners": [
{"foo": "something"}
]
}
{
"provisioners": [
{
"type": "something",
"only": ["foo"]
}
]
}
{
"provisioners": [
{
"type": "something",
"override": {
"foo": {}
}
}
]
}
{
"provisioners": [
{
"type": "something",
"pause_before": "1s"
}
]
}
{
"push": {
"name": "foo"
}
}
{
"builders": [{
"type": "foo"
}],
"provisioners": [{
"type": "bar",
"override": {
"bar": {}
}
}]
}
{
"builders": [{
"type": "foo"
}],
"post-processors": [{
"type": "bar",
"except": ["bar"]
}]
}
{
"builders": [{
"type": "foo"
}],
"post-processors": [{
"type": "bar",
"only": ["bar"]
}]
}
{
"builders": [{
"type": "foo"
}],
"provisioners": [{
"type": "bar",
"except": ["bar"]
}]
}
{
"builders": [{
"type": "foo"
}],
"provisioners": [{
"type": "bar",
"only": ["bar"]
}]
}
{
"builders": [{
"type": "foo"
}],
"provisioners": [{
"type": "bar",
"override": {
"foo": {}
}
}]
}
{
"builders": [{
"type": "foo"
}],
"post-processors": [{
"type": "bar",
"except": ["foo"]
}]
}
{
"builders": [{
"type": "foo"
}],
"post-processors": [{
"type": "bar",
"only": ["foo"]
}]
}
{
"builders": [{
"type": "foo"
}],
"provisioners": [{
"type": "bar",
"except": ["foo"]
}]
}
{
"builders": [{
"type": "foo"
}],
"provisioners": [{
"type": "bar",
"only": ["foo"]
}]
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment