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,
......
This diff is collapsed.
This diff is collapsed.
{
"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)
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
{
"builders": [{"type": "something"}]
}
{
"builders": [{"foo": "something"}]
}
{
"builders": [
{"type": "something"},
{"type": "something"}
]
}
{"builders":[{"type":"test"}]}
{
"post-processors": [{
"type": "foo",
"foo": "bar"
}]
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
{
"post-processors": ["foo"]
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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