Merge pull request #2134 from mitchellh/f-template-new
Refactor core, move template parsing into a sep package
This commit is contained in:
commit
3a24cf9e91
114
command/build.go
114
command/build.go
|
@ -2,16 +2,16 @@ package command
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
cmdcommon "github.com/mitchellh/packer/common/command"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template"
|
||||
)
|
||||
|
||||
type BuildCommand struct {
|
||||
|
@ -20,71 +20,52 @@ type BuildCommand struct {
|
|||
|
||||
func (c BuildCommand) Run(args []string) int {
|
||||
var cfgColor, cfgDebug, cfgForce, cfgParallel bool
|
||||
buildOptions := new(cmdcommon.BuildOptions)
|
||||
|
||||
env, err := c.Meta.Environment()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
|
||||
flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars)
|
||||
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
||||
flags.BoolVar(&cfgColor, "color", true, "")
|
||||
flags.BoolVar(&cfgDebug, "debug", false, "")
|
||||
flags.BoolVar(&cfgForce, "force", false, "")
|
||||
flags.BoolVar(&cfgParallel, "parallel", true, "")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError)
|
||||
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
|
||||
cmdFlags.BoolVar(&cfgColor, "color", true, "enable or disable color")
|
||||
cmdFlags.BoolVar(&cfgDebug, "debug", false, "debug mode for builds")
|
||||
cmdFlags.BoolVar(&cfgForce, "force", false, "force a build if artifacts exist")
|
||||
cmdFlags.BoolVar(&cfgParallel, "parallel", true, "enable/disable parallelization")
|
||||
cmdcommon.BuildOptionFlags(cmdFlags, buildOptions)
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
args = cmdFlags.Args()
|
||||
args = flags.Args()
|
||||
if len(args) != 1 {
|
||||
cmdFlags.Usage()
|
||||
flags.Usage()
|
||||
return 1
|
||||
}
|
||||
|
||||
if err := buildOptions.Validate(); err != nil {
|
||||
env.Ui().Error(err.Error())
|
||||
env.Ui().Error("")
|
||||
env.Ui().Error(c.Help())
|
||||
return 1
|
||||
}
|
||||
|
||||
userVars, err := buildOptions.AllUserVars()
|
||||
// Parse the template
|
||||
tpl, err := template.ParseFile(args[0])
|
||||
if err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err))
|
||||
env.Ui().Error("")
|
||||
env.Ui().Error(c.Help())
|
||||
c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Read the file into a byte array so that we can parse the template
|
||||
log.Printf("Reading template: %s", args[0])
|
||||
tpl, err := packer.ParseTemplateFile(args[0], userVars)
|
||||
// Get the core
|
||||
core, err := c.Meta.Core(tpl)
|
||||
if err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err))
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
// The component finder for our builds
|
||||
components := &packer.ComponentFinder{
|
||||
Builder: env.Builder,
|
||||
Hook: env.Hook,
|
||||
PostProcessor: env.PostProcessor,
|
||||
Provisioner: env.Provisioner,
|
||||
}
|
||||
// Get the builds we care about
|
||||
buildNames := c.Meta.BuildNames(core)
|
||||
builds := make([]packer.Build, 0, len(buildNames))
|
||||
for _, n := range buildNames {
|
||||
b, err := core.Build(n)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Failed to initialize build '%s': %s",
|
||||
n, err))
|
||||
}
|
||||
|
||||
// Go through each builder and compile the builds that we care about
|
||||
builds, err := buildOptions.Builds(tpl, components)
|
||||
if err != nil {
|
||||
env.Ui().Error(err.Error())
|
||||
return 1
|
||||
builds = append(builds, b)
|
||||
}
|
||||
|
||||
if cfgDebug {
|
||||
env.Ui().Say("Debug mode enabled. Builds will not be parallelized.")
|
||||
c.Ui.Say("Debug mode enabled. Builds will not be parallelized.")
|
||||
}
|
||||
|
||||
// Compile all the UIs for the builds
|
||||
|
@ -95,24 +76,23 @@ func (c BuildCommand) Run(args []string) int {
|
|||
packer.UiColorYellow,
|
||||
packer.UiColorBlue,
|
||||
}
|
||||
|
||||
buildUis := make(map[string]packer.Ui)
|
||||
for i, b := range builds {
|
||||
for i, b := range buildNames {
|
||||
var ui packer.Ui
|
||||
ui = env.Ui()
|
||||
ui = c.Ui
|
||||
if cfgColor {
|
||||
ui = &packer.ColoredUi{
|
||||
Color: colors[i%len(colors)],
|
||||
Ui: env.Ui(),
|
||||
Ui: ui,
|
||||
}
|
||||
}
|
||||
|
||||
buildUis[b.Name()] = ui
|
||||
ui.Say(fmt.Sprintf("%s output will be in this color.", b.Name()))
|
||||
buildUis[b] = ui
|
||||
ui.Say(fmt.Sprintf("%s output will be in this color.", b))
|
||||
}
|
||||
|
||||
// Add a newline between the color output and the actual output
|
||||
env.Ui().Say("")
|
||||
c.Ui.Say("")
|
||||
|
||||
log.Printf("Build debug mode: %v", cfgDebug)
|
||||
log.Printf("Force build: %v", cfgForce)
|
||||
|
@ -125,7 +105,7 @@ func (c BuildCommand) Run(args []string) int {
|
|||
|
||||
warnings, err := b.Prepare()
|
||||
if err != nil {
|
||||
env.Ui().Error(err.Error())
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if len(warnings) > 0 {
|
||||
|
@ -169,7 +149,7 @@ func (c BuildCommand) Run(args []string) int {
|
|||
name := b.Name()
|
||||
log.Printf("Starting build run: %s", name)
|
||||
ui := buildUis[name]
|
||||
runArtifacts, err := b.Run(ui, env.Cache())
|
||||
runArtifacts, err := b.Run(ui, c.CoreConfig.Cache)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err))
|
||||
|
@ -205,34 +185,34 @@ func (c BuildCommand) Run(args []string) int {
|
|||
interruptWg.Wait()
|
||||
|
||||
if interrupted {
|
||||
env.Ui().Say("Cleanly cancelled builds after being interrupted.")
|
||||
c.Ui.Say("Cleanly cancelled builds after being interrupted.")
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
env.Ui().Machine("error-count", strconv.FormatInt(int64(len(errors)), 10))
|
||||
c.Ui.Machine("error-count", strconv.FormatInt(int64(len(errors)), 10))
|
||||
|
||||
env.Ui().Error("\n==> Some builds didn't complete successfully and had errors:")
|
||||
c.Ui.Error("\n==> Some builds didn't complete successfully and had errors:")
|
||||
for name, err := range errors {
|
||||
// Create a UI for the machine readable stuff to be targetted
|
||||
ui := &packer.TargettedUi{
|
||||
Target: name,
|
||||
Ui: env.Ui(),
|
||||
Ui: c.Ui,
|
||||
}
|
||||
|
||||
ui.Machine("error", err.Error())
|
||||
|
||||
env.Ui().Error(fmt.Sprintf("--> %s: %s", name, err))
|
||||
c.Ui.Error(fmt.Sprintf("--> %s: %s", name, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(artifacts) > 0 {
|
||||
env.Ui().Say("\n==> Builds finished. The artifacts of successful builds are:")
|
||||
c.Ui.Say("\n==> Builds finished. The artifacts of successful builds are:")
|
||||
for name, buildArtifacts := range artifacts {
|
||||
// Create a UI for the machine readable stuff to be targetted
|
||||
ui := &packer.TargettedUi{
|
||||
Target: name,
|
||||
Ui: env.Ui(),
|
||||
Ui: c.Ui,
|
||||
}
|
||||
|
||||
// Machine-readable helpful
|
||||
|
@ -267,11 +247,11 @@ func (c BuildCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
ui.Machine("artifact", iStr, "end")
|
||||
env.Ui().Say(message.String())
|
||||
c.Ui.Say(message.String())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
env.Ui().Say("\n==> Builds finished but no artifacts were created.")
|
||||
c.Ui.Say("\n==> Builds finished but no artifacts were created.")
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
const fixturesDir = "./test-fixtures"
|
||||
|
||||
func fatalCommand(t *testing.T, m Meta) {
|
||||
ui := m.Ui.(*cli.MockUi)
|
||||
ui := m.Ui.(*packer.BasicUi)
|
||||
out := ui.Writer.(*bytes.Buffer)
|
||||
err := ui.ErrorWriter.(*bytes.Buffer)
|
||||
t.Fatalf(
|
||||
"Bad exit code.\n\nStdout:\n\n%s\n\nStderr:\n\n%s",
|
||||
ui.OutputWriter.String(),
|
||||
ui.ErrorWriter.String())
|
||||
out.String(),
|
||||
err.String())
|
||||
}
|
||||
|
||||
func testFixture(n string) string {
|
||||
|
@ -22,7 +25,12 @@ func testFixture(n string) string {
|
|||
}
|
||||
|
||||
func testMeta(t *testing.T) Meta {
|
||||
var out, err bytes.Buffer
|
||||
|
||||
return Meta{
|
||||
Ui: new(cli.MockUi),
|
||||
Ui: &packer.BasicUi{
|
||||
Writer: &out,
|
||||
ErrorWriter: &err,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package command
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
@ -17,28 +16,22 @@ type FixCommand struct {
|
|||
}
|
||||
|
||||
func (c *FixCommand) Run(args []string) int {
|
||||
env, err := c.Meta.Environment()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
|
||||
flags := c.Meta.FlagSet("fix", FlagSetNone)
|
||||
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
cmdFlags := flag.NewFlagSet("fix", flag.ContinueOnError)
|
||||
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
args = cmdFlags.Args()
|
||||
args = flags.Args()
|
||||
if len(args) != 1 {
|
||||
cmdFlags.Usage()
|
||||
flags.Usage()
|
||||
return 1
|
||||
}
|
||||
|
||||
// Read the file for decoding
|
||||
tplF, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error opening template: %s", err))
|
||||
c.Ui.Error(fmt.Sprintf("Error opening template: %s", err))
|
||||
return 1
|
||||
}
|
||||
defer tplF.Close()
|
||||
|
@ -47,7 +40,7 @@ func (c *FixCommand) Run(args []string) int {
|
|||
var templateData map[string]interface{}
|
||||
decoder := json.NewDecoder(tplF)
|
||||
if err := decoder.Decode(&templateData); err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error parsing template: %s", err))
|
||||
c.Ui.Error(fmt.Sprintf("Error parsing template: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
|
@ -65,7 +58,7 @@ func (c *FixCommand) Run(args []string) int {
|
|||
log.Printf("Running fixer: %s", name)
|
||||
input, err = fixer.Fix(input)
|
||||
if err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error fixing: %s", err))
|
||||
c.Ui.Error(fmt.Sprintf("Error fixing: %s", err))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
@ -73,20 +66,20 @@ func (c *FixCommand) Run(args []string) int {
|
|||
var output bytes.Buffer
|
||||
encoder := json.NewEncoder(&output)
|
||||
if err := encoder.Encode(input); err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error encoding: %s", err))
|
||||
c.Ui.Error(fmt.Sprintf("Error encoding: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
var indented bytes.Buffer
|
||||
if err := json.Indent(&indented, output.Bytes(), "", " "); err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error encoding: %s", err))
|
||||
c.Ui.Error(fmt.Sprintf("Error encoding: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
result := indented.String()
|
||||
result = strings.Replace(result, `\u003c`, "<", -1)
|
||||
result = strings.Replace(result, `\u003e`, ">", -1)
|
||||
env.Ui().Say(result)
|
||||
c.Ui.Say(result)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,20 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/packer/template"
|
||||
)
|
||||
|
||||
type InspectCommand struct{
|
||||
type InspectCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *InspectCommand) Run(args []string) int {
|
||||
env, err := c.Meta.Environment()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
flags := flag.NewFlagSet("inspect", flag.ContinueOnError)
|
||||
flags.Usage = func() { env.Ui().Say(c.Help()) }
|
||||
flags := c.Meta.FlagSet("inspect", FlagSetNone)
|
||||
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
@ -32,16 +25,15 @@ func (c *InspectCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
// Read the file into a byte array so that we can parse the template
|
||||
log.Printf("Reading template: %#v", args[0])
|
||||
tpl, err := packer.ParseTemplateFile(args[0], nil)
|
||||
// Parse the template
|
||||
tpl, err := template.ParseFile(args[0])
|
||||
if err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err))
|
||||
c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Convenience...
|
||||
ui := env.Ui()
|
||||
ui := c.Ui
|
||||
|
||||
// Description
|
||||
if tpl.Description != "" {
|
||||
|
|
143
command/meta.go
143
command/meta.go
|
@ -1,15 +1,148 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/cli"
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/mitchellh/packer/helper/flag-kv"
|
||||
"github.com/mitchellh/packer/helper/flag-slice"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template"
|
||||
)
|
||||
|
||||
// FlagSetFlags is an enum to define what flags are present in the
|
||||
// default FlagSet returned by Meta.FlagSet
|
||||
type FlagSetFlags uint
|
||||
|
||||
const (
|
||||
FlagSetNone FlagSetFlags = 0
|
||||
FlagSetBuildFilter FlagSetFlags = 1 << iota
|
||||
FlagSetVars
|
||||
)
|
||||
|
||||
// Meta contains the meta-options and functionality that nearly every
|
||||
// Packer command inherits.
|
||||
type Meta struct {
|
||||
EnvConfig *packer.EnvironmentConfig
|
||||
Ui cli.Ui
|
||||
CoreConfig *packer.CoreConfig
|
||||
Ui packer.Ui
|
||||
|
||||
// These are set by command-line flags
|
||||
flagBuildExcept []string
|
||||
flagBuildOnly []string
|
||||
flagVars map[string]string
|
||||
}
|
||||
|
||||
func (m *Meta) Environment() (packer.Environment, error) {
|
||||
return packer.NewEnvironment(m.EnvConfig)
|
||||
// Core returns the core for the given template given the configured
|
||||
// CoreConfig and user variables on this Meta.
|
||||
func (m *Meta) Core(tpl *template.Template) (*packer.Core, error) {
|
||||
// Copy the config so we don't modify it
|
||||
config := *m.CoreConfig
|
||||
config.Template = tpl
|
||||
config.Variables = m.flagVars
|
||||
|
||||
// Init the core
|
||||
core, err := packer.NewCore(&config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error initializing core: %s", err)
|
||||
}
|
||||
|
||||
// Validate it
|
||||
if err := core.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return core, nil
|
||||
}
|
||||
|
||||
// BuildNames returns the list of builds that are in the given core
|
||||
// that we care about taking into account the only and except flags.
|
||||
func (m *Meta) BuildNames(c *packer.Core) []string {
|
||||
// TODO: test
|
||||
|
||||
// Filter the "only"
|
||||
if len(m.flagBuildOnly) > 0 {
|
||||
// Build a set of all the available names
|
||||
nameSet := make(map[string]struct{})
|
||||
for _, n := range c.BuildNames() {
|
||||
nameSet[n] = struct{}{}
|
||||
}
|
||||
|
||||
// Build our result set which we pre-allocate some sane number
|
||||
result := make([]string, 0, len(m.flagBuildOnly))
|
||||
for _, n := range m.flagBuildOnly {
|
||||
if _, ok := nameSet[n]; ok {
|
||||
result = append(result, n)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Filter the "except"
|
||||
if len(m.flagBuildExcept) > 0 {
|
||||
// Build a set of the things we don't want
|
||||
nameSet := make(map[string]struct{})
|
||||
for _, n := range m.flagBuildExcept {
|
||||
nameSet[n] = struct{}{}
|
||||
}
|
||||
|
||||
// Build our result set which is the names of all builds except
|
||||
// those in the given set.
|
||||
names := c.BuildNames()
|
||||
result := make([]string, 0, len(names))
|
||||
for _, n := range names {
|
||||
if _, ok := nameSet[n]; !ok {
|
||||
result = append(result, n)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// We care about everything
|
||||
return c.BuildNames()
|
||||
}
|
||||
|
||||
// FlagSet returns a FlagSet with the common flags that every
|
||||
// command implements. The exact behavior of FlagSet can be configured
|
||||
// using the flags as the second parameter, for example to disable
|
||||
// build settings on the commands that don't handle builds.
|
||||
func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet {
|
||||
f := flag.NewFlagSet(n, flag.ContinueOnError)
|
||||
|
||||
// FlagSetBuildFilter tells us to enable the settings for selecting
|
||||
// builds we care about.
|
||||
if fs&FlagSetBuildFilter != 0 {
|
||||
f.Var((*sliceflag.StringFlag)(&m.flagBuildExcept), "except", "")
|
||||
f.Var((*sliceflag.StringFlag)(&m.flagBuildOnly), "only", "")
|
||||
}
|
||||
|
||||
// FlagSetVars tells us what variables to use
|
||||
if fs&FlagSetVars != 0 {
|
||||
f.Var((*kvflag.Flag)(&m.flagVars), "var", "")
|
||||
f.Var((*kvflag.FlagJSON)(&m.flagVars), "var-file", "")
|
||||
}
|
||||
|
||||
// Create an io.Writer that writes to our Ui properly for errors.
|
||||
// This is kind of a hack, but it does the job. Basically: create
|
||||
// a pipe, use a scanner to break it into lines, and output each line
|
||||
// to the UI. Do this forever.
|
||||
errR, errW := io.Pipe()
|
||||
errScanner := bufio.NewScanner(errR)
|
||||
go func() {
|
||||
for errScanner.Scan() {
|
||||
m.Ui.Error(errScanner.Text())
|
||||
}
|
||||
}()
|
||||
f.SetOutput(errW)
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// ValidateFlags should be called after parsing flags to validate the
|
||||
// given flags
|
||||
func (m *Meta) ValidateFlags() error {
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
cmdcommon "github.com/mitchellh/packer/common/command"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template"
|
||||
)
|
||||
|
||||
type ValidateCommand struct {
|
||||
|
@ -15,72 +15,54 @@ type ValidateCommand struct {
|
|||
|
||||
func (c *ValidateCommand) Run(args []string) int {
|
||||
var cfgSyntaxOnly bool
|
||||
buildOptions := new(cmdcommon.BuildOptions)
|
||||
|
||||
env, err := c.Meta.Environment()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
|
||||
flags := c.Meta.FlagSet("validate", FlagSetBuildFilter|FlagSetVars)
|
||||
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
||||
flags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only")
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
cmdFlags := flag.NewFlagSet("validate", flag.ContinueOnError)
|
||||
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
|
||||
cmdFlags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only")
|
||||
cmdcommon.BuildOptionFlags(cmdFlags, buildOptions)
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
args = cmdFlags.Args()
|
||||
args = flags.Args()
|
||||
if len(args) != 1 {
|
||||
cmdFlags.Usage()
|
||||
flags.Usage()
|
||||
return 1
|
||||
}
|
||||
|
||||
if err := buildOptions.Validate(); err != nil {
|
||||
env.Ui().Error(err.Error())
|
||||
env.Ui().Error("")
|
||||
env.Ui().Error(c.Help())
|
||||
return 1
|
||||
}
|
||||
|
||||
userVars, err := buildOptions.AllUserVars()
|
||||
// Parse the template
|
||||
tpl, err := template.ParseFile(args[0])
|
||||
if err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err))
|
||||
env.Ui().Error("")
|
||||
env.Ui().Error(c.Help())
|
||||
return 1
|
||||
}
|
||||
|
||||
// Parse the template into a machine-usable format
|
||||
log.Printf("Reading template: %s", args[0])
|
||||
tpl, err := packer.ParseTemplateFile(args[0], userVars)
|
||||
if err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err))
|
||||
c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// If we're only checking syntax, then we're done already
|
||||
if cfgSyntaxOnly {
|
||||
env.Ui().Say("Syntax-only check passed. Everything looks okay.")
|
||||
c.Ui.Say("Syntax-only check passed. Everything looks okay.")
|
||||
return 0
|
||||
}
|
||||
|
||||
// Get the core
|
||||
core, err := c.Meta.Core(tpl)
|
||||
if err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
errs := make([]error, 0)
|
||||
warnings := make(map[string][]string)
|
||||
|
||||
// The component finder for our builds
|
||||
components := &packer.ComponentFinder{
|
||||
Builder: env.Builder,
|
||||
Hook: env.Hook,
|
||||
PostProcessor: env.PostProcessor,
|
||||
Provisioner: env.Provisioner,
|
||||
}
|
||||
// Get the builds we care about
|
||||
buildNames := c.Meta.BuildNames(core)
|
||||
builds := make([]packer.Build, 0, len(buildNames))
|
||||
for _, n := range buildNames {
|
||||
b, err := core.Build(n)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Failed to initialize build '%s': %s",
|
||||
n, err))
|
||||
}
|
||||
|
||||
// Otherwise, get all the builds
|
||||
builds, err := buildOptions.Builds(tpl, components)
|
||||
if err != nil {
|
||||
env.Ui().Error(err.Error())
|
||||
return 1
|
||||
builds = append(builds, b)
|
||||
}
|
||||
|
||||
// Check the configuration of all builds
|
||||
|
@ -96,12 +78,12 @@ func (c *ValidateCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
env.Ui().Error("Template validation failed. Errors are shown below.\n")
|
||||
c.Ui.Error("Template validation failed. Errors are shown below.\n")
|
||||
for i, err := range errs {
|
||||
env.Ui().Error(err.Error())
|
||||
c.Ui.Error(err.Error())
|
||||
|
||||
if (i + 1) < len(errs) {
|
||||
env.Ui().Error("")
|
||||
c.Ui.Error("")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,21 +91,21 @@ func (c *ValidateCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
if len(warnings) > 0 {
|
||||
env.Ui().Say("Template validation succeeded, but there were some warnings.")
|
||||
env.Ui().Say("These are ONLY WARNINGS, and Packer will attempt to build the")
|
||||
env.Ui().Say("template despite them, but they should be paid attention to.\n")
|
||||
c.Ui.Say("Template validation succeeded, but there were some warnings.")
|
||||
c.Ui.Say("These are ONLY WARNINGS, and Packer will attempt to build the")
|
||||
c.Ui.Say("template despite them, but they should be paid attention to.\n")
|
||||
|
||||
for build, warns := range warnings {
|
||||
env.Ui().Say(fmt.Sprintf("Warnings for build '%s':\n", build))
|
||||
c.Ui.Say(fmt.Sprintf("Warnings for build '%s':\n", build))
|
||||
for _, warning := range warns {
|
||||
env.Ui().Say(fmt.Sprintf("* %s", warning))
|
||||
c.Ui.Say(fmt.Sprintf("* %s", warning))
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
env.Ui().Say("Template validated successfully.")
|
||||
c.Ui.Say("Template validated successfully.")
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
17
commands.go
17
commands.go
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/packer/command"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// Commands is the mapping of all the available Terraform commands.
|
||||
|
@ -18,17 +19,13 @@ const ErrorPrefix = "e:"
|
|||
const OutputPrefix = "o:"
|
||||
|
||||
func init() {
|
||||
Ui = &cli.PrefixedUi{
|
||||
AskPrefix: OutputPrefix,
|
||||
OutputPrefix: OutputPrefix,
|
||||
InfoPrefix: OutputPrefix,
|
||||
ErrorPrefix: ErrorPrefix,
|
||||
Ui: &cli.BasicUi{Writer: os.Stdout},
|
||||
}
|
||||
|
||||
meta := command.Meta{
|
||||
EnvConfig: &EnvConfig,
|
||||
Ui: Ui,
|
||||
CoreConfig: &CoreConfig,
|
||||
Ui: &packer.BasicUi{
|
||||
Reader: os.Stdin,
|
||||
Writer: os.Stdout,
|
||||
ErrorWriter: os.Stdout,
|
||||
},
|
||||
}
|
||||
|
||||
Commands = map[string]cli.CommandFactory{
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BuildOptionFlags sets the proper command line flags needed for
|
||||
// build options.
|
||||
func BuildOptionFlags(fs *flag.FlagSet, f *BuildOptions) {
|
||||
fs.Var((*SliceValue)(&f.Except), "except", "build all builds except these")
|
||||
fs.Var((*SliceValue)(&f.Only), "only", "only build the given builds by name")
|
||||
fs.Var((*userVarValue)(&f.UserVars), "var", "specify a user variable")
|
||||
fs.Var((*AppendSliceValue)(&f.UserVarFiles), "var-file", "file with user variables")
|
||||
}
|
||||
|
||||
// userVarValue is a flag.Value that parses out user variables in
|
||||
// the form of 'key=value' and sets it on this map.
|
||||
type userVarValue map[string]string
|
||||
|
||||
func (v *userVarValue) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (v *userVarValue) Set(raw string) error {
|
||||
idx := strings.Index(raw, "=")
|
||||
if idx == -1 {
|
||||
return fmt.Errorf("No '=' value in arg: %s", raw)
|
||||
}
|
||||
|
||||
if *v == nil {
|
||||
*v = make(map[string]string)
|
||||
}
|
||||
|
||||
key, value := raw[0:idx], raw[idx+1:]
|
||||
(*v)[key] = value
|
||||
return nil
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildOptionFlags(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
fs := flag.NewFlagSet("test", flag.ContinueOnError)
|
||||
BuildOptionFlags(fs, opts)
|
||||
|
||||
args := []string{
|
||||
"-except=foo,bar,baz",
|
||||
"-only=a,b",
|
||||
"-var=foo=bar",
|
||||
"-var", "bar=baz",
|
||||
"-var=foo=bang",
|
||||
"-var-file=foo",
|
||||
"-var-file=bar",
|
||||
}
|
||||
|
||||
err := fs.Parse(args)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{"foo", "bar", "baz"}
|
||||
if !reflect.DeepEqual(opts.Except, expected) {
|
||||
t.Fatalf("bad: %#v", opts.Except)
|
||||
}
|
||||
|
||||
expected = []string{"a", "b"}
|
||||
if !reflect.DeepEqual(opts.Only, expected) {
|
||||
t.Fatalf("bad: %#v", opts.Only)
|
||||
}
|
||||
|
||||
if len(opts.UserVars) != 2 {
|
||||
t.Fatalf("bad: %#v", opts.UserVars)
|
||||
}
|
||||
|
||||
if opts.UserVars["foo"] != "bang" {
|
||||
t.Fatalf("bad: %#v", opts.UserVars)
|
||||
}
|
||||
|
||||
if opts.UserVars["bar"] != "baz" {
|
||||
t.Fatalf("bad: %#v", opts.UserVars)
|
||||
}
|
||||
|
||||
expected = []string{"foo", "bar"}
|
||||
if !reflect.DeepEqual(opts.UserVarFiles, expected) {
|
||||
t.Fatalf("bad: %#v", opts.UserVarFiles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserVarValue_implements(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(userVarValue)
|
||||
if _, ok := raw.(flag.Value); !ok {
|
||||
t.Fatalf("userVarValue should be a Value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserVarValueSet(t *testing.T) {
|
||||
sv := new(userVarValue)
|
||||
err := sv.Set("key=value")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
vars := map[string]string(*sv)
|
||||
if vars["key"] != "value" {
|
||||
t.Fatalf("Bad: %#v", vars)
|
||||
}
|
||||
|
||||
// Empty value
|
||||
err = sv.Set("key=")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
vars = map[string]string(*sv)
|
||||
if vars["key"] != "" {
|
||||
t.Fatalf("Bad: %#v", vars)
|
||||
}
|
||||
|
||||
// Equal in value
|
||||
err = sv.Set("key=foo=bar")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
vars = map[string]string(*sv)
|
||||
if vars["key"] != "foo=bar" {
|
||||
t.Fatalf("Bad: %#v", vars)
|
||||
}
|
||||
|
||||
// No equal
|
||||
err = sv.Set("key")
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package command
|
||||
|
||||
import "strings"
|
||||
|
||||
// AppendSliceValue implements the flag.Value interface and allows multiple
|
||||
// calls to the same variable to append a list.
|
||||
type AppendSliceValue []string
|
||||
|
||||
func (s *AppendSliceValue) String() string {
|
||||
return strings.Join(*s, ",")
|
||||
}
|
||||
|
||||
func (s *AppendSliceValue) Set(value string) error {
|
||||
if *s == nil {
|
||||
*s = make([]string, 0, 1)
|
||||
}
|
||||
|
||||
*s = append(*s, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SliceValue implements the flag.Value interface and allows a list of
|
||||
// strings to be given on the command line and properly parsed into a slice
|
||||
// of strings internally.
|
||||
type SliceValue []string
|
||||
|
||||
func (s *SliceValue) String() string {
|
||||
return strings.Join(*s, ",")
|
||||
}
|
||||
|
||||
func (s *SliceValue) Set(value string) error {
|
||||
*s = strings.Split(value, ",")
|
||||
return nil
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAppendSliceValue_implements(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(AppendSliceValue)
|
||||
if _, ok := raw.(flag.Value); !ok {
|
||||
t.Fatalf("AppendSliceValue should be a Value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendSliceValueSet(t *testing.T) {
|
||||
sv := new(AppendSliceValue)
|
||||
err := sv.Set("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
err = sv.Set("bar")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{"foo", "bar"}
|
||||
if !reflect.DeepEqual([]string(*sv), expected) {
|
||||
t.Fatalf("Bad: %#v", sv)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceValue_implements(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(SliceValue)
|
||||
if _, ok := raw.(flag.Value); !ok {
|
||||
t.Fatalf("SliceValue should be a Value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceValueSet(t *testing.T) {
|
||||
sv := new(SliceValue)
|
||||
err := sv.Set("foo,bar,baz")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{"foo", "bar", "baz"}
|
||||
if !reflect.DeepEqual([]string(*sv), expected) {
|
||||
t.Fatalf("Bad: %#v", sv)
|
||||
}
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
jsonutil "github.com/mitchellh/packer/common/json"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// BuildOptions is a set of options related to builds that can be set
|
||||
// from the command line.
|
||||
type BuildOptions struct {
|
||||
UserVarFiles []string
|
||||
UserVars map[string]string
|
||||
Except []string
|
||||
Only []string
|
||||
}
|
||||
|
||||
// Validate validates the options
|
||||
func (f *BuildOptions) Validate() error {
|
||||
if len(f.Except) > 0 && len(f.Only) > 0 {
|
||||
return errors.New("Only one of '-except' or '-only' may be specified.")
|
||||
}
|
||||
|
||||
if len(f.UserVarFiles) > 0 {
|
||||
for _, path := range f.UserVarFiles {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return fmt.Errorf("Cannot access: %s", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllUserVars returns the user variables, compiled from both the
|
||||
// file paths and the vars on the command line.
|
||||
func (f *BuildOptions) AllUserVars() (map[string]string, error) {
|
||||
all := make(map[string]string)
|
||||
|
||||
// Copy in the variables from the files
|
||||
for _, path := range f.UserVarFiles {
|
||||
fileVars, err := readFileVars(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range fileVars {
|
||||
all[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Copy in the command-line vars
|
||||
for k, v := range f.UserVars {
|
||||
all[k] = v
|
||||
}
|
||||
|
||||
return all, nil
|
||||
}
|
||||
|
||||
// Builds returns the builds out of the given template that pass the
|
||||
// configured options.
|
||||
func (f *BuildOptions) Builds(t *packer.Template, cf *packer.ComponentFinder) ([]packer.Build, error) {
|
||||
buildNames := t.BuildNames()
|
||||
|
||||
// Process the name
|
||||
tpl, _, err := t.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
checks := make(map[string][]string)
|
||||
checks["except"] = f.Except
|
||||
checks["only"] = f.Only
|
||||
for t, ns := range checks {
|
||||
for _, n := range ns {
|
||||
found := false
|
||||
for _, actual := range buildNames {
|
||||
var processed string
|
||||
processed, err = tpl.Process(actual, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if actual == n || processed == n {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf(
|
||||
"Unknown build in '%s' flag: %s", t, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builds := make([]packer.Build, 0, len(buildNames))
|
||||
for _, buildName := range buildNames {
|
||||
var processedBuildName string
|
||||
processedBuildName, err = tpl.Process(buildName, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(f.Except) > 0 {
|
||||
found := false
|
||||
for _, except := range f.Except {
|
||||
if buildName == except || processedBuildName == except {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
log.Printf("Skipping build '%s' because specified by -except.", processedBuildName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.Only) > 0 {
|
||||
found := false
|
||||
for _, only := range f.Only {
|
||||
if buildName == only || processedBuildName == only {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
log.Printf("Skipping build '%s' because not specified by -only.", processedBuildName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Creating build: %s", processedBuildName)
|
||||
build, err := t.Build(buildName, cf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create build '%s': \n\n%s", buildName, err)
|
||||
}
|
||||
|
||||
builds = append(builds, build)
|
||||
}
|
||||
|
||||
return builds, nil
|
||||
}
|
||||
|
||||
func readFileVars(path string) (map[string]string, error) {
|
||||
bytes, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vars := make(map[string]string)
|
||||
err = jsonutil.Unmarshal(bytes, &vars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vars, nil
|
||||
}
|
|
@ -1,228 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testTemplate() (*packer.Template, *packer.ComponentFinder) {
|
||||
tplData := `{
|
||||
"variables": {
|
||||
"foo": null
|
||||
},
|
||||
|
||||
"builders": [
|
||||
{
|
||||
"type": "foo"
|
||||
},
|
||||
{
|
||||
"name": "{{user \"foo\"}}",
|
||||
"type": "bar"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
tpl, err := packer.ParseTemplate([]byte(tplData), map[string]string{"foo": "bar"})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cf := &packer.ComponentFinder{
|
||||
Builder: func(string) (packer.Builder, error) { return new(packer.MockBuilder), nil },
|
||||
}
|
||||
|
||||
return tpl, cf
|
||||
}
|
||||
|
||||
func TestBuildOptionsBuilds(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
bs, err := opts.Builds(testTemplate())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(bs) != 2 {
|
||||
t.Fatalf("bad: %d", len(bs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOptionsBuilds_except(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Except = []string{"foo"}
|
||||
|
||||
bs, err := opts.Builds(testTemplate())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(bs) != 1 {
|
||||
t.Fatalf("bad: %d", len(bs))
|
||||
}
|
||||
|
||||
if bs[0].Name() != "bar" {
|
||||
t.Fatalf("bad: %s", bs[0].Name())
|
||||
}
|
||||
}
|
||||
|
||||
//Test to make sure the build name pattern matches
|
||||
func TestBuildOptionsBuilds_exceptConfigTemplateRaw(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Except = []string{"{{user \"foo\"}}"}
|
||||
|
||||
bs, err := opts.Builds(testTemplate())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(bs) != 1 {
|
||||
t.Fatalf("bad: %d", len(bs))
|
||||
}
|
||||
|
||||
if bs[0].Name() != "foo" {
|
||||
t.Fatalf("bad: %s", bs[0].Name())
|
||||
}
|
||||
}
|
||||
|
||||
//Test to make sure the processed build name matches
|
||||
func TestBuildOptionsBuilds_exceptConfigTemplateProcessed(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Except = []string{"bar"}
|
||||
|
||||
bs, err := opts.Builds(testTemplate())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(bs) != 1 {
|
||||
t.Fatalf("bad: %d", len(bs))
|
||||
}
|
||||
|
||||
if bs[0].Name() != "foo" {
|
||||
t.Fatalf("bad: %s", bs[0].Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOptionsBuilds_only(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Only = []string{"foo"}
|
||||
|
||||
bs, err := opts.Builds(testTemplate())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(bs) != 1 {
|
||||
t.Fatalf("bad: %d", len(bs))
|
||||
}
|
||||
|
||||
if bs[0].Name() != "foo" {
|
||||
t.Fatalf("bad: %s", bs[0].Name())
|
||||
}
|
||||
}
|
||||
|
||||
//Test to make sure the build name pattern matches
|
||||
func TestBuildOptionsBuilds_onlyConfigTemplateRaw(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Only = []string{"{{user \"foo\"}}"}
|
||||
|
||||
bs, err := opts.Builds(testTemplate())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(bs) != 1 {
|
||||
t.Fatalf("bad: %d", len(bs))
|
||||
}
|
||||
|
||||
if bs[0].Name() != "bar" {
|
||||
t.Fatalf("bad: %s", bs[0].Name())
|
||||
}
|
||||
}
|
||||
|
||||
//Test to make sure the processed build name matches
|
||||
func TestBuildOptionsBuilds_onlyConfigTemplateProcessed(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Only = []string{"bar"}
|
||||
|
||||
bs, err := opts.Builds(testTemplate())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(bs) != 1 {
|
||||
t.Fatalf("bad: %d", len(bs))
|
||||
}
|
||||
|
||||
if bs[0].Name() != "bar" {
|
||||
t.Fatalf("bad: %s", bs[0].Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOptionsBuilds_exceptNonExistent(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Except = []string{"i-dont-exist"}
|
||||
|
||||
_, err := opts.Builds(testTemplate())
|
||||
if err == nil {
|
||||
t.Fatal("err should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOptionsBuilds_onlyNonExistent(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Only = []string{"i-dont-exist"}
|
||||
|
||||
_, err := opts.Builds(testTemplate())
|
||||
if err == nil {
|
||||
t.Fatal("err should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOptionsValidate(t *testing.T) {
|
||||
bf := new(BuildOptions)
|
||||
|
||||
err := bf.Validate()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Both set
|
||||
bf.Except = make([]string, 1)
|
||||
bf.Only = make([]string, 1)
|
||||
err = bf.Validate()
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
// One set
|
||||
bf.Except = make([]string, 1)
|
||||
bf.Only = make([]string, 0)
|
||||
err = bf.Validate()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
bf.Except = make([]string, 0)
|
||||
bf.Only = make([]string, 1)
|
||||
err = bf.Validate()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOptionsValidate_userVarFiles(t *testing.T) {
|
||||
bf := new(BuildOptions)
|
||||
|
||||
err := bf.Validate()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Non-existent file
|
||||
bf.UserVarFiles = []string{"ireallyshouldntexistanywhere"}
|
||||
err = bf.Validate()
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
|
@ -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"`
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package kvflag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Flag is a flag.Value implementation for parsing user variables
|
||||
// from the command-line in the format of '-var key=value'.
|
||||
type Flag map[string]string
|
||||
|
||||
func (v *Flag) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (v *Flag) Set(raw string) error {
|
||||
idx := strings.Index(raw, "=")
|
||||
if idx == -1 {
|
||||
return fmt.Errorf("No '=' value in arg: %s", raw)
|
||||
}
|
||||
|
||||
if *v == nil {
|
||||
*v = make(map[string]string)
|
||||
}
|
||||
|
||||
key, value := raw[0:idx], raw[idx+1:]
|
||||
(*v)[key] = value
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package kvflag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// FlagJSON is a flag.Value implementation for parsing user variables
|
||||
// from the command-line using JSON files.
|
||||
type FlagJSON map[string]string
|
||||
|
||||
func (v *FlagJSON) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (v *FlagJSON) Set(raw string) error {
|
||||
f, err := os.Open(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if *v == nil {
|
||||
*v = make(map[string]string)
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(f).Decode(v); err != nil {
|
||||
return fmt.Errorf(
|
||||
"Error reading variables in '%s': %s", raw, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package kvflag
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFlagJSON_impl(t *testing.T) {
|
||||
var _ flag.Value = new(FlagJSON)
|
||||
}
|
||||
|
||||
func TestFlagJSON(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Initial map[string]string
|
||||
Output map[string]string
|
||||
Error bool
|
||||
}{
|
||||
{
|
||||
"basic.json",
|
||||
nil,
|
||||
map[string]string{"key": "value"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"basic.json",
|
||||
map[string]string{"foo": "bar"},
|
||||
map[string]string{"foo": "bar", "key": "value"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"basic.json",
|
||||
map[string]string{"key": "bar"},
|
||||
map[string]string{"key": "value"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
f := new(FlagJSON)
|
||||
if tc.Initial != nil {
|
||||
f = (*FlagJSON)(&tc.Initial)
|
||||
}
|
||||
|
||||
err := f.Set(filepath.Join("./test-fixtures", tc.Input))
|
||||
if (err != nil) != tc.Error {
|
||||
t.Fatalf("bad error. Input: %#v\n\n%s", tc.Input, err)
|
||||
}
|
||||
|
||||
actual := map[string]string(*f)
|
||||
if !reflect.DeepEqual(actual, tc.Output) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package kvflag
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFlag_impl(t *testing.T) {
|
||||
var _ flag.Value = new(Flag)
|
||||
}
|
||||
|
||||
func TestFlag(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Output map[string]string
|
||||
Error bool
|
||||
}{
|
||||
{
|
||||
"key=value",
|
||||
map[string]string{"key": "value"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"key=",
|
||||
map[string]string{"key": ""},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"key=foo=bar",
|
||||
map[string]string{"key": "foo=bar"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"key",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
f := new(Flag)
|
||||
err := f.Set(tc.Input)
|
||||
if (err != nil) != tc.Error {
|
||||
t.Fatalf("bad error. Input: %#v", tc.Input)
|
||||
}
|
||||
|
||||
actual := map[string]string(*f)
|
||||
if !reflect.DeepEqual(actual, tc.Output) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"key": "value"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package sliceflag
|
||||
|
||||
import "strings"
|
||||
|
||||
// StringFlag implements the flag.Value interface and allows multiple
|
||||
// calls to the same variable to append a list.
|
||||
type StringFlag []string
|
||||
|
||||
func (s *StringFlag) String() string {
|
||||
return strings.Join(*s, ",")
|
||||
}
|
||||
|
||||
func (s *StringFlag) Set(value string) error {
|
||||
*s = append(*s, value)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package sliceflag
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringFlag_implements(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StringFlag)
|
||||
if _, ok := raw.(flag.Value); !ok {
|
||||
t.Fatalf("StringFlag should be a Value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringFlagSet(t *testing.T) {
|
||||
sv := new(StringFlag)
|
||||
err := sv.Set("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
err = sv.Set("bar")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{"foo", "bar"}
|
||||
if !reflect.DeepEqual([]string(*sv), expected) {
|
||||
t.Fatalf("Bad: %#v", sv)
|
||||
}
|
||||
}
|
13
main.go
13
main.go
|
@ -140,14 +140,13 @@ func wrappedMain() int {
|
|||
defer plugin.CleanupClients()
|
||||
|
||||
// Create the environment configuration
|
||||
EnvConfig = *packer.DefaultEnvironmentConfig()
|
||||
EnvConfig.Cache = cache
|
||||
EnvConfig.Components.Builder = config.LoadBuilder
|
||||
EnvConfig.Components.Hook = config.LoadHook
|
||||
EnvConfig.Components.PostProcessor = config.LoadPostProcessor
|
||||
EnvConfig.Components.Provisioner = config.LoadProvisioner
|
||||
CoreConfig.Cache = cache
|
||||
CoreConfig.Components.Builder = config.LoadBuilder
|
||||
CoreConfig.Components.Hook = config.LoadHook
|
||||
CoreConfig.Components.PostProcessor = config.LoadPostProcessor
|
||||
CoreConfig.Components.Provisioner = config.LoadProvisioner
|
||||
if machineReadable {
|
||||
EnvConfig.Ui = &packer.MachineReadableUi{
|
||||
CoreConfig.Ui = &packer.MachineReadableUi{
|
||||
Writer: os.Stdout,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
package packer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mitchellh/packer/template"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// Core is the main executor of Packer. If Packer is being used as a
|
||||
// library, this is the struct you'll want to instantiate to get anything done.
|
||||
type Core struct {
|
||||
cache Cache
|
||||
components ComponentFinder
|
||||
ui Ui
|
||||
template *template.Template
|
||||
variables map[string]string
|
||||
builds map[string]*template.Builder
|
||||
}
|
||||
|
||||
// CoreConfig is the structure for initializing a new Core. Once a CoreConfig
|
||||
// is used to initialize a Core, it shouldn't be re-used or modified again.
|
||||
type CoreConfig struct {
|
||||
Cache Cache
|
||||
Components ComponentFinder
|
||||
Ui Ui
|
||||
Template *template.Template
|
||||
Variables map[string]string
|
||||
}
|
||||
|
||||
// The function type used to lookup Builder implementations.
|
||||
type BuilderFunc func(name string) (Builder, error)
|
||||
|
||||
// The function type used to lookup Hook implementations.
|
||||
type HookFunc func(name string) (Hook, error)
|
||||
|
||||
// The function type used to lookup PostProcessor implementations.
|
||||
type PostProcessorFunc func(name string) (PostProcessor, error)
|
||||
|
||||
// The function type used to lookup Provisioner implementations.
|
||||
type ProvisionerFunc func(name string) (Provisioner, error)
|
||||
|
||||
// ComponentFinder is a struct that contains the various function
|
||||
// pointers necessary to look up components of Packer such as builders,
|
||||
// commands, etc.
|
||||
type ComponentFinder struct {
|
||||
Builder BuilderFunc
|
||||
Hook HookFunc
|
||||
PostProcessor PostProcessorFunc
|
||||
Provisioner ProvisionerFunc
|
||||
}
|
||||
|
||||
// NewCore creates a new Core.
|
||||
func NewCore(c *CoreConfig) (*Core, error) {
|
||||
if c.Ui == nil {
|
||||
c.Ui = &BasicUi{
|
||||
Reader: os.Stdin,
|
||||
Writer: os.Stdout,
|
||||
ErrorWriter: os.Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
// Go through and interpolate all the build names. We shuld be able
|
||||
// to do this at this point with the variables.
|
||||
builds := make(map[string]*template.Builder)
|
||||
for _, b := range c.Template.Builders {
|
||||
v, err := interpolate.Render(b.Name, &interpolate.Context{
|
||||
UserVariables: c.Variables,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Error interpolating builder '%s': %s",
|
||||
b.Name, err)
|
||||
}
|
||||
|
||||
builds[v] = b
|
||||
}
|
||||
|
||||
return &Core{
|
||||
cache: c.Cache,
|
||||
components: c.Components,
|
||||
ui: c.Ui,
|
||||
template: c.Template,
|
||||
variables: c.Variables,
|
||||
builds: builds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuildNames returns the builds that are available in this configured core.
|
||||
func (c *Core) BuildNames() []string {
|
||||
r := make([]string, 0, len(c.builds))
|
||||
for n, _ := range c.builds {
|
||||
r = append(r, n)
|
||||
}
|
||||
sort.Strings(r)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Build returns the Build object for the given name.
|
||||
func (c *Core) Build(n string) (Build, error) {
|
||||
// Setup the builder
|
||||
configBuilder, ok := c.builds[n]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no such build found: %s", n)
|
||||
}
|
||||
builder, err := c.components.Builder(configBuilder.Type)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"error initializing builder '%s': %s",
|
||||
configBuilder.Type, err)
|
||||
}
|
||||
if builder == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"builder type not found: %s", configBuilder.Type)
|
||||
}
|
||||
|
||||
// rawName is the uninterpolated name that we use for various lookups
|
||||
rawName := configBuilder.Name
|
||||
|
||||
// Setup the provisioners for this build
|
||||
provisioners := make([]coreBuildProvisioner, 0, len(c.template.Provisioners))
|
||||
for _, rawP := range c.template.Provisioners {
|
||||
// If we're skipping this, then ignore it
|
||||
if rawP.Skip(rawName) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the provisioner
|
||||
provisioner, err := c.components.Provisioner(rawP.Type)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"error initializing provisioner '%s': %s",
|
||||
rawP.Type, err)
|
||||
}
|
||||
if provisioner == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"provisioner type not found: %s", rawP.Type)
|
||||
}
|
||||
|
||||
// Get the configuration
|
||||
config := make([]interface{}, 1, 2)
|
||||
config[0] = rawP.Config
|
||||
if rawP.Override != nil {
|
||||
if override, ok := rawP.Override[rawName]; ok {
|
||||
config = append(config, override)
|
||||
}
|
||||
}
|
||||
|
||||
// If we're pausing, we wrap the provisioner in a special pauser.
|
||||
if rawP.PauseBefore > 0 {
|
||||
provisioner = &PausedProvisioner{
|
||||
PauseBefore: rawP.PauseBefore,
|
||||
Provisioner: provisioner,
|
||||
}
|
||||
}
|
||||
|
||||
provisioners = append(provisioners, coreBuildProvisioner{
|
||||
provisioner: provisioner,
|
||||
config: config,
|
||||
})
|
||||
}
|
||||
|
||||
// Setup the post-processors
|
||||
postProcessors := make([][]coreBuildPostProcessor, 0, len(c.template.PostProcessors))
|
||||
for _, rawPs := range c.template.PostProcessors {
|
||||
current := make([]coreBuildPostProcessor, 0, len(rawPs))
|
||||
for _, rawP := range rawPs {
|
||||
// If we skip, ignore
|
||||
if rawP.Skip(rawName) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the post-processor
|
||||
postProcessor, err := c.components.PostProcessor(rawP.Type)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"error initializing post-processor '%s': %s",
|
||||
rawP.Type, err)
|
||||
}
|
||||
if postProcessor == nil {
|
||||
return nil, fmt.Errorf(
|
||||
"post-processor type not found: %s", rawP.Type)
|
||||
}
|
||||
|
||||
current = append(current, coreBuildPostProcessor{
|
||||
processor: postProcessor,
|
||||
processorType: rawP.Type,
|
||||
config: rawP.Config,
|
||||
keepInputArtifact: rawP.KeepInputArtifact,
|
||||
})
|
||||
}
|
||||
|
||||
// If we have no post-processors in this chain, just continue.
|
||||
if len(current) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
postProcessors = append(postProcessors, current)
|
||||
}
|
||||
|
||||
// TODO hooks one day
|
||||
|
||||
return &coreBuild{
|
||||
name: n,
|
||||
builder: builder,
|
||||
builderConfig: configBuilder.Config,
|
||||
builderType: configBuilder.Type,
|
||||
postProcessors: postProcessors,
|
||||
provisioners: provisioners,
|
||||
variables: c.variables,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Validate does a full validation of the template.
|
||||
//
|
||||
// This will automatically call template.Validate() in addition to doing
|
||||
// richer semantic checks around variables and so on.
|
||||
func (c *Core) Validate() error {
|
||||
// First validate the template in general, we can't do anything else
|
||||
// unless the template itself is valid.
|
||||
if err := c.template.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate variables are set
|
||||
var err error
|
||||
for n, v := range c.template.Variables {
|
||||
if v.Required {
|
||||
if _, ok := c.variables[n]; !ok {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"required variable not set: %s", n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: validate all builders exist
|
||||
// TODO: ^^ provisioner
|
||||
// TODO: ^^ post-processor
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,378 @@
|
|||
package packer
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/template"
|
||||
)
|
||||
|
||||
func TestCoreBuildNames(t *testing.T) {
|
||||
cases := []struct {
|
||||
File string
|
||||
Vars map[string]string
|
||||
Result []string
|
||||
}{
|
||||
{
|
||||
"build-names-basic.json",
|
||||
nil,
|
||||
[]string{"something"},
|
||||
},
|
||||
|
||||
{
|
||||
"build-names-func.json",
|
||||
nil,
|
||||
[]string{"TUBES"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tpl, err := template.ParseFile(fixtureDir(tc.File))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s\n\n%s", tc.File, err)
|
||||
}
|
||||
|
||||
core, err := NewCore(&CoreConfig{
|
||||
Template: tpl,
|
||||
Variables: tc.Vars,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s\n\n%s", tc.File, err)
|
||||
}
|
||||
|
||||
names := core.BuildNames()
|
||||
if !reflect.DeepEqual(names, tc.Result) {
|
||||
t.Fatalf("err: %s\n\n%#v", tc.File, names)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreBuild_basic(t *testing.T) {
|
||||
config := TestCoreConfig(t)
|
||||
testCoreTemplate(t, config, fixtureDir("build-basic.json"))
|
||||
b := TestBuilder(t, config, "test")
|
||||
core := TestCore(t, config)
|
||||
|
||||
b.ArtifactId = "hello"
|
||||
|
||||
build, err := core.Build("test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, err := build.Prepare(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(artifact) != 1 {
|
||||
t.Fatalf("bad: %#v", artifact)
|
||||
}
|
||||
|
||||
if artifact[0].Id() != b.ArtifactId {
|
||||
t.Fatalf("bad: %s", artifact[0].Id())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreBuild_basicInterpolated(t *testing.T) {
|
||||
config := TestCoreConfig(t)
|
||||
testCoreTemplate(t, config, fixtureDir("build-basic-interpolated.json"))
|
||||
b := TestBuilder(t, config, "test")
|
||||
core := TestCore(t, config)
|
||||
|
||||
b.ArtifactId = "hello"
|
||||
|
||||
build, err := core.Build("NAME")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, err := build.Prepare(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(artifact) != 1 {
|
||||
t.Fatalf("bad: %#v", artifact)
|
||||
}
|
||||
|
||||
if artifact[0].Id() != b.ArtifactId {
|
||||
t.Fatalf("bad: %s", artifact[0].Id())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreBuild_nonExist(t *testing.T) {
|
||||
config := TestCoreConfig(t)
|
||||
testCoreTemplate(t, config, fixtureDir("build-basic.json"))
|
||||
TestBuilder(t, config, "test")
|
||||
core := TestCore(t, config)
|
||||
|
||||
_, err := core.Build("nope")
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreBuild_prov(t *testing.T) {
|
||||
config := TestCoreConfig(t)
|
||||
testCoreTemplate(t, config, fixtureDir("build-prov.json"))
|
||||
b := TestBuilder(t, config, "test")
|
||||
p := TestProvisioner(t, config, "test")
|
||||
core := TestCore(t, config)
|
||||
|
||||
b.ArtifactId = "hello"
|
||||
|
||||
build, err := core.Build("test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, err := build.Prepare(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(artifact) != 1 {
|
||||
t.Fatalf("bad: %#v", artifact)
|
||||
}
|
||||
|
||||
if artifact[0].Id() != b.ArtifactId {
|
||||
t.Fatalf("bad: %s", artifact[0].Id())
|
||||
}
|
||||
if !p.ProvCalled {
|
||||
t.Fatal("provisioner not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreBuild_provSkip(t *testing.T) {
|
||||
config := TestCoreConfig(t)
|
||||
testCoreTemplate(t, config, fixtureDir("build-prov-skip.json"))
|
||||
b := TestBuilder(t, config, "test")
|
||||
p := TestProvisioner(t, config, "test")
|
||||
core := TestCore(t, config)
|
||||
|
||||
b.ArtifactId = "hello"
|
||||
|
||||
build, err := core.Build("test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, err := build.Prepare(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(artifact) != 1 {
|
||||
t.Fatalf("bad: %#v", artifact)
|
||||
}
|
||||
|
||||
if artifact[0].Id() != b.ArtifactId {
|
||||
t.Fatalf("bad: %s", artifact[0].Id())
|
||||
}
|
||||
if p.ProvCalled {
|
||||
t.Fatal("provisioner should not be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreBuild_provSkipInclude(t *testing.T) {
|
||||
config := TestCoreConfig(t)
|
||||
testCoreTemplate(t, config, fixtureDir("build-prov-skip-include.json"))
|
||||
b := TestBuilder(t, config, "test")
|
||||
p := TestProvisioner(t, config, "test")
|
||||
core := TestCore(t, config)
|
||||
|
||||
b.ArtifactId = "hello"
|
||||
|
||||
build, err := core.Build("test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, err := build.Prepare(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(artifact) != 1 {
|
||||
t.Fatalf("bad: %#v", artifact)
|
||||
}
|
||||
|
||||
if artifact[0].Id() != b.ArtifactId {
|
||||
t.Fatalf("bad: %s", artifact[0].Id())
|
||||
}
|
||||
if !p.ProvCalled {
|
||||
t.Fatal("provisioner should be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreBuild_provOverride(t *testing.T) {
|
||||
config := TestCoreConfig(t)
|
||||
testCoreTemplate(t, config, fixtureDir("build-prov-override.json"))
|
||||
b := TestBuilder(t, config, "test")
|
||||
p := TestProvisioner(t, config, "test")
|
||||
core := TestCore(t, config)
|
||||
|
||||
b.ArtifactId = "hello"
|
||||
|
||||
build, err := core.Build("test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, err := build.Prepare(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(artifact) != 1 {
|
||||
t.Fatalf("bad: %#v", artifact)
|
||||
}
|
||||
|
||||
if artifact[0].Id() != b.ArtifactId {
|
||||
t.Fatalf("bad: %s", artifact[0].Id())
|
||||
}
|
||||
if !p.ProvCalled {
|
||||
t.Fatal("provisioner not called")
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, raw := range p.PrepConfigs {
|
||||
if m, ok := raw.(map[string]interface{}); ok {
|
||||
if _, ok := m["foo"]; ok {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("override not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreBuild_postProcess(t *testing.T) {
|
||||
config := TestCoreConfig(t)
|
||||
testCoreTemplate(t, config, fixtureDir("build-pp.json"))
|
||||
b := TestBuilder(t, config, "test")
|
||||
p := TestPostProcessor(t, config, "test")
|
||||
core := TestCore(t, config)
|
||||
ui := TestUi(t)
|
||||
|
||||
b.ArtifactId = "hello"
|
||||
p.ArtifactId = "goodbye"
|
||||
|
||||
build, err := core.Build("test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, err := build.Prepare(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(ui, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(artifact) != 1 {
|
||||
t.Fatalf("bad: %#v", artifact)
|
||||
}
|
||||
|
||||
if artifact[0].Id() != p.ArtifactId {
|
||||
t.Fatalf("bad: %s", artifact[0].Id())
|
||||
}
|
||||
if p.PostProcessArtifact.Id() != b.ArtifactId {
|
||||
t.Fatalf("bad: %s", p.PostProcessArtifact.Id())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreValidate(t *testing.T) {
|
||||
cases := []struct {
|
||||
File string
|
||||
Vars map[string]string
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
"validate-dup-builder.json",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
// Required variable not set
|
||||
{
|
||||
"validate-req-variable.json",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"validate-req-variable.json",
|
||||
map[string]string{"foo": "bar"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
f, err := os.Open(fixtureDir(tc.File))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
tpl, err := template.Parse(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s\n\n%s", tc.File, err)
|
||||
}
|
||||
|
||||
core, err := NewCore(&CoreConfig{
|
||||
Template: tpl,
|
||||
Variables: tc.Vars,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s\n\n%s", tc.File, err)
|
||||
}
|
||||
|
||||
if err := core.Validate(); (err != nil) != tc.Err {
|
||||
t.Fatalf("err: %s\n\n%s", tc.File, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testComponentFinder() *ComponentFinder {
|
||||
builderFactory := func(n string) (Builder, error) { return new(MockBuilder), nil }
|
||||
ppFactory := func(n string) (PostProcessor, error) { return new(MockPostProcessor), nil }
|
||||
provFactory := func(n string) (Provisioner, error) { return new(MockProvisioner), nil }
|
||||
return &ComponentFinder{
|
||||
Builder: builderFactory,
|
||||
PostProcessor: ppFactory,
|
||||
Provisioner: provFactory,
|
||||
}
|
||||
}
|
||||
|
||||
func testCoreTemplate(t *testing.T, c *CoreConfig, p string) {
|
||||
tpl, err := template.ParseFile(p)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s\n\n%s", p, err)
|
||||
}
|
||||
|
||||
c.Template = tpl
|
||||
}
|
|
@ -1,183 +0,0 @@
|
|||
// The packer package contains the core components of Packer.
|
||||
package packer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// The function type used to lookup Builder implementations.
|
||||
type BuilderFunc func(name string) (Builder, error)
|
||||
|
||||
// The function type used to lookup Hook implementations.
|
||||
type HookFunc func(name string) (Hook, error)
|
||||
|
||||
// The function type used to lookup PostProcessor implementations.
|
||||
type PostProcessorFunc func(name string) (PostProcessor, error)
|
||||
|
||||
// The function type used to lookup Provisioner implementations.
|
||||
type ProvisionerFunc func(name string) (Provisioner, error)
|
||||
|
||||
// ComponentFinder is a struct that contains the various function
|
||||
// pointers necessary to look up components of Packer such as builders,
|
||||
// commands, etc.
|
||||
type ComponentFinder struct {
|
||||
Builder BuilderFunc
|
||||
Hook HookFunc
|
||||
PostProcessor PostProcessorFunc
|
||||
Provisioner ProvisionerFunc
|
||||
}
|
||||
|
||||
// The environment interface provides access to the configuration and
|
||||
// state of a single Packer run.
|
||||
//
|
||||
// It allows for things such as executing CLI commands, getting the
|
||||
// list of available builders, and more.
|
||||
type Environment interface {
|
||||
Builder(string) (Builder, error)
|
||||
Cache() Cache
|
||||
Hook(string) (Hook, error)
|
||||
PostProcessor(string) (PostProcessor, error)
|
||||
Provisioner(string) (Provisioner, error)
|
||||
Ui() Ui
|
||||
}
|
||||
|
||||
// An implementation of an Environment that represents the Packer core
|
||||
// environment.
|
||||
type coreEnvironment struct {
|
||||
cache Cache
|
||||
components ComponentFinder
|
||||
ui Ui
|
||||
}
|
||||
|
||||
// This struct configures new environments.
|
||||
type EnvironmentConfig struct {
|
||||
Cache Cache
|
||||
Components ComponentFinder
|
||||
Ui Ui
|
||||
}
|
||||
|
||||
// DefaultEnvironmentConfig returns a default EnvironmentConfig that can
|
||||
// be used to create a new enviroment with NewEnvironment with sane defaults.
|
||||
func DefaultEnvironmentConfig() *EnvironmentConfig {
|
||||
config := &EnvironmentConfig{}
|
||||
config.Ui = &BasicUi{
|
||||
Reader: os.Stdin,
|
||||
Writer: os.Stdout,
|
||||
ErrorWriter: os.Stdout,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// This creates a new environment
|
||||
func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error) {
|
||||
if config == nil {
|
||||
err = errors.New("config must be given to initialize environment")
|
||||
return
|
||||
}
|
||||
|
||||
env := &coreEnvironment{}
|
||||
env.cache = config.Cache
|
||||
env.components = config.Components
|
||||
env.ui = config.Ui
|
||||
|
||||
// We want to make sure the components have valid function pointers.
|
||||
// If a function pointer was not given, we assume that the function
|
||||
// will just return a nil component.
|
||||
if env.components.Builder == nil {
|
||||
env.components.Builder = func(string) (Builder, error) { return nil, nil }
|
||||
}
|
||||
|
||||
if env.components.Hook == nil {
|
||||
env.components.Hook = func(string) (Hook, error) { return nil, nil }
|
||||
}
|
||||
|
||||
if env.components.PostProcessor == nil {
|
||||
env.components.PostProcessor = func(string) (PostProcessor, error) { return nil, nil }
|
||||
}
|
||||
|
||||
if env.components.Provisioner == nil {
|
||||
env.components.Provisioner = func(string) (Provisioner, error) { return nil, nil }
|
||||
}
|
||||
|
||||
// The default cache is just the system temporary directory
|
||||
if env.cache == nil {
|
||||
env.cache = &FileCache{CacheDir: os.TempDir()}
|
||||
}
|
||||
|
||||
resultEnv = env
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a builder of the given name that is registered with this
|
||||
// environment.
|
||||
func (e *coreEnvironment) Builder(name string) (b Builder, err error) {
|
||||
b, err = e.components.Builder(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if b == nil {
|
||||
err = fmt.Errorf("No builder returned for name: %s", name)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the cache for this environment
|
||||
func (e *coreEnvironment) Cache() Cache {
|
||||
return e.cache
|
||||
}
|
||||
|
||||
// Returns a hook of the given name that is registered with this
|
||||
// environment.
|
||||
func (e *coreEnvironment) Hook(name string) (h Hook, err error) {
|
||||
h, err = e.components.Hook(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if h == nil {
|
||||
err = fmt.Errorf("No hook returned for name: %s", name)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a PostProcessor for the given name that is registered with this
|
||||
// environment.
|
||||
func (e *coreEnvironment) PostProcessor(name string) (p PostProcessor, err error) {
|
||||
p, err = e.components.PostProcessor(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
err = fmt.Errorf("No post processor found for name: %s", name)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a provisioner for the given name that is registered with this
|
||||
// environment.
|
||||
func (e *coreEnvironment) Provisioner(name string) (p Provisioner, err error) {
|
||||
p, err = e.components.Provisioner(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
err = fmt.Errorf("No provisioner returned for name: %s", name)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the UI for the environment. The UI is the interface that should
|
||||
// be used for all communication with the outside world.
|
||||
func (e *coreEnvironment) Ui() Ui {
|
||||
return e.ui
|
||||
}
|
|
@ -1,310 +0,0 @@
|
|||
package packer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Disable log output for tests
|
||||
log.SetOutput(ioutil.Discard)
|
||||
}
|
||||
|
||||
func testComponentFinder() *ComponentFinder {
|
||||
builderFactory := func(n string) (Builder, error) { return new(MockBuilder), nil }
|
||||
ppFactory := func(n string) (PostProcessor, error) { return new(TestPostProcessor), nil }
|
||||
provFactory := func(n string) (Provisioner, error) { return new(MockProvisioner), nil }
|
||||
return &ComponentFinder{
|
||||
Builder: builderFactory,
|
||||
PostProcessor: ppFactory,
|
||||
Provisioner: provFactory,
|
||||
}
|
||||
}
|
||||
|
||||
func testEnvironment() Environment {
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Ui = &BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
ErrorWriter: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
env, err := NewEnvironment(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func TestEnvironment_DefaultConfig_Ui(t *testing.T) {
|
||||
config := DefaultEnvironmentConfig()
|
||||
if config.Ui == nil {
|
||||
t.Fatal("config.Ui should not be nil")
|
||||
}
|
||||
|
||||
rwUi, ok := config.Ui.(*BasicUi)
|
||||
if !ok {
|
||||
t.Fatal("default UI should be BasicUi")
|
||||
}
|
||||
if rwUi.Writer != os.Stdout {
|
||||
t.Fatal("default UI should go to stdout")
|
||||
}
|
||||
if rwUi.Reader != os.Stdin {
|
||||
t.Fatal("default UI reader should go to stdin")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewEnvironment_NoConfig(t *testing.T) {
|
||||
env, err := NewEnvironment(nil)
|
||||
if env != nil {
|
||||
t.Fatal("env should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_NilComponents(t *testing.T) {
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components = *new(ComponentFinder)
|
||||
|
||||
env, err := NewEnvironment(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// All of these should not cause panics... so we don't assert
|
||||
// anything but if there is a panic in the test then yeah, something
|
||||
// went wrong.
|
||||
env.Builder("foo")
|
||||
env.Hook("foo")
|
||||
env.PostProcessor("foo")
|
||||
env.Provisioner("foo")
|
||||
}
|
||||
|
||||
func TestEnvironment_Builder(t *testing.T) {
|
||||
builder := &MockBuilder{}
|
||||
builders := make(map[string]Builder)
|
||||
builders["foo"] = builder
|
||||
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.Builder = func(n string) (Builder, error) { return builders[n], nil }
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returnedBuilder, err := env.Builder("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if returnedBuilder != builder {
|
||||
t.Fatalf("bad: %#v", returnedBuilder)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_Builder_NilError(t *testing.T) {
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.Builder = func(n string) (Builder, error) { return nil, nil }
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returnedBuilder, err := env.Builder("foo")
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if returnedBuilder != nil {
|
||||
t.Fatalf("bad: %#v", returnedBuilder)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_Builder_Error(t *testing.T) {
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.Builder = func(n string) (Builder, error) { return nil, errors.New("foo") }
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returnedBuilder, err := env.Builder("foo")
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if err.Error() != "foo" {
|
||||
t.Fatalf("bad err: %s", err)
|
||||
}
|
||||
if returnedBuilder != nil {
|
||||
t.Fatalf("should be nil: %#v", returnedBuilder)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_Cache(t *testing.T) {
|
||||
config := DefaultEnvironmentConfig()
|
||||
env, _ := NewEnvironment(config)
|
||||
if env.Cache() == nil {
|
||||
t.Fatal("cache should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_Hook(t *testing.T) {
|
||||
hook := &MockHook{}
|
||||
hooks := make(map[string]Hook)
|
||||
hooks["foo"] = hook
|
||||
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.Hook = func(n string) (Hook, error) { return hooks[n], nil }
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returned, err := env.Hook("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if returned != hook {
|
||||
t.Fatalf("bad: %#v", returned)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_Hook_NilError(t *testing.T) {
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.Hook = func(n string) (Hook, error) { return nil, nil }
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returned, err := env.Hook("foo")
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if returned != nil {
|
||||
t.Fatalf("bad: %#v", returned)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_Hook_Error(t *testing.T) {
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.Hook = func(n string) (Hook, error) { return nil, errors.New("foo") }
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returned, err := env.Hook("foo")
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if err.Error() != "foo" {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if returned != nil {
|
||||
t.Fatalf("bad: %#v", returned)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_PostProcessor(t *testing.T) {
|
||||
pp := &TestPostProcessor{}
|
||||
pps := make(map[string]PostProcessor)
|
||||
pps["foo"] = pp
|
||||
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.PostProcessor = func(n string) (PostProcessor, error) { return pps[n], nil }
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returned, err := env.PostProcessor("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if returned != pp {
|
||||
t.Fatalf("bad: %#v", returned)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_PostProcessor_NilError(t *testing.T) {
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.PostProcessor = func(n string) (PostProcessor, error) { return nil, nil }
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returned, err := env.PostProcessor("foo")
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if returned != nil {
|
||||
t.Fatalf("bad: %#v", returned)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_PostProcessor_Error(t *testing.T) {
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.PostProcessor = func(n string) (PostProcessor, error) { return nil, errors.New("foo") }
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returned, err := env.PostProcessor("foo")
|
||||
if err == nil {
|
||||
t.Fatal("should be an error")
|
||||
}
|
||||
if err.Error() != "foo" {
|
||||
t.Fatalf("bad err: %s", err)
|
||||
}
|
||||
if returned != nil {
|
||||
t.Fatalf("bad: %#v", returned)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironmentProvisioner(t *testing.T) {
|
||||
p := &MockProvisioner{}
|
||||
ps := make(map[string]Provisioner)
|
||||
ps["foo"] = p
|
||||
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.Provisioner = func(n string) (Provisioner, error) { return ps[n], nil }
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returned, err := env.Provisioner("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if returned != p {
|
||||
t.Fatalf("bad: %#v", returned)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironmentProvisioner_NilError(t *testing.T) {
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.Provisioner = func(n string) (Provisioner, error) { return nil, nil }
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returned, err := env.Provisioner("foo")
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if returned != nil {
|
||||
t.Fatalf("bad: %#v", returned)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironmentProvisioner_Error(t *testing.T) {
|
||||
config := DefaultEnvironmentConfig()
|
||||
config.Components.Provisioner = func(n string) (Provisioner, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
returned, err := env.Provisioner("foo")
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
if err.Error() != "foo" {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if returned != nil {
|
||||
t.Fatalf("bad: %#v", returned)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_SettingUi(t *testing.T) {
|
||||
ui := &BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
config := &EnvironmentConfig{}
|
||||
config.Ui = ui
|
||||
|
||||
env, _ := NewEnvironment(config)
|
||||
|
||||
if env.Ui() != ui {
|
||||
t.Fatalf("UI should be equal: %#v", env.Ui())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package packer
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const FixtureDir = "./test-fixtures"
|
||||
|
||||
func fixtureDir(n string) string {
|
||||
return filepath.Join(FixtureDir, n)
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package packer
|
||||
|
||||
// MockPostProcessor is an implementation of PostProcessor that can be
|
||||
// used for tests.
|
||||
type MockPostProcessor struct {
|
||||
ArtifactId string
|
||||
Keep bool
|
||||
Error error
|
||||
|
||||
ConfigureCalled bool
|
||||
ConfigureConfigs []interface{}
|
||||
ConfigureError error
|
||||
|
||||
PostProcessCalled bool
|
||||
PostProcessArtifact Artifact
|
||||
PostProcessUi Ui
|
||||
}
|
||||
|
||||
func (t *MockPostProcessor) Configure(configs ...interface{}) error {
|
||||
t.ConfigureCalled = true
|
||||
t.ConfigureConfigs = configs
|
||||
return t.ConfigureError
|
||||
}
|
||||
|
||||
func (t *MockPostProcessor) PostProcess(ui Ui, a Artifact) (Artifact, bool, error) {
|
||||
t.PostProcessCalled = true
|
||||
t.PostProcessArtifact = a
|
||||
t.PostProcessUi = ui
|
||||
|
||||
return &MockArtifact{
|
||||
IdValue: t.ArtifactId,
|
||||
}, t.Keep, t.Error
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package packer
|
||||
|
||||
type TestPostProcessor struct {
|
||||
artifactId string
|
||||
keep bool
|
||||
configCalled bool
|
||||
configVal []interface{}
|
||||
ppCalled bool
|
||||
ppArtifact Artifact
|
||||
ppUi Ui
|
||||
}
|
||||
|
||||
func (pp *TestPostProcessor) Configure(v ...interface{}) error {
|
||||
pp.configCalled = true
|
||||
pp.configVal = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pp *TestPostProcessor) PostProcess(ui Ui, a Artifact) (Artifact, bool, error) {
|
||||
pp.ppCalled = true
|
||||
pp.ppArtifact = a
|
||||
pp.ppUi = ui
|
||||
return &TestArtifact{id: pp.artifactId}, pp.keep, nil
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -1,178 +0,0 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"net/rpc"
|
||||
)
|
||||
|
||||
// A Environment is an implementation of the packer.Environment interface
|
||||
// where the actual environment is executed over an RPC connection.
|
||||
type Environment struct {
|
||||
client *rpc.Client
|
||||
mux *muxBroker
|
||||
}
|
||||
|
||||
// A EnvironmentServer wraps a packer.Environment and makes it exportable
|
||||
// as part of a Golang RPC server.
|
||||
type EnvironmentServer struct {
|
||||
env packer.Environment
|
||||
mux *muxBroker
|
||||
}
|
||||
|
||||
func (e *Environment) Builder(name string) (b packer.Builder, err error) {
|
||||
var streamId uint32
|
||||
err = e.client.Call("Environment.Builder", name, &streamId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, err := newClientWithMux(e.mux, streamId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = client.Builder()
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Environment) Cache() packer.Cache {
|
||||
var streamId uint32
|
||||
if err := e.client.Call("Environment.Cache", new(interface{}), &streamId); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client, err := newClientWithMux(e.mux, streamId)
|
||||
if err != nil {
|
||||
log.Printf("[ERR] Error getting cache client: %s", err)
|
||||
return nil
|
||||
}
|
||||
return client.Cache()
|
||||
}
|
||||
|
||||
func (e *Environment) Hook(name string) (h packer.Hook, err error) {
|
||||
var streamId uint32
|
||||
err = e.client.Call("Environment.Hook", name, &streamId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, err := newClientWithMux(e.mux, streamId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.Hook(), nil
|
||||
}
|
||||
|
||||
func (e *Environment) PostProcessor(name string) (p packer.PostProcessor, err error) {
|
||||
var streamId uint32
|
||||
err = e.client.Call("Environment.PostProcessor", name, &streamId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, err := newClientWithMux(e.mux, streamId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = client.PostProcessor()
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Environment) Provisioner(name string) (p packer.Provisioner, err error) {
|
||||
var streamId uint32
|
||||
err = e.client.Call("Environment.Provisioner", name, &streamId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, err := newClientWithMux(e.mux, streamId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = client.Provisioner()
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Environment) Ui() packer.Ui {
|
||||
var streamId uint32
|
||||
e.client.Call("Environment.Ui", new(interface{}), &streamId)
|
||||
|
||||
client, err := newClientWithMux(e.mux, streamId)
|
||||
if err != nil {
|
||||
log.Printf("[ERR] Error connecting to Ui: %s", err)
|
||||
return nil
|
||||
}
|
||||
return client.Ui()
|
||||
}
|
||||
|
||||
func (e *EnvironmentServer) Builder(name string, reply *uint32) error {
|
||||
builder, err := e.env.Builder(name)
|
||||
if err != nil {
|
||||
return NewBasicError(err)
|
||||
}
|
||||
|
||||
*reply = e.mux.NextId()
|
||||
server := newServerWithMux(e.mux, *reply)
|
||||
server.RegisterBuilder(builder)
|
||||
go server.Serve()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EnvironmentServer) Cache(args *interface{}, reply *uint32) error {
|
||||
cache := e.env.Cache()
|
||||
|
||||
*reply = e.mux.NextId()
|
||||
server := newServerWithMux(e.mux, *reply)
|
||||
server.RegisterCache(cache)
|
||||
go server.Serve()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EnvironmentServer) Hook(name string, reply *uint32) error {
|
||||
hook, err := e.env.Hook(name)
|
||||
if err != nil {
|
||||
return NewBasicError(err)
|
||||
}
|
||||
|
||||
*reply = e.mux.NextId()
|
||||
server := newServerWithMux(e.mux, *reply)
|
||||
server.RegisterHook(hook)
|
||||
go server.Serve()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EnvironmentServer) PostProcessor(name string, reply *uint32) error {
|
||||
pp, err := e.env.PostProcessor(name)
|
||||
if err != nil {
|
||||
return NewBasicError(err)
|
||||
}
|
||||
|
||||
*reply = e.mux.NextId()
|
||||
server := newServerWithMux(e.mux, *reply)
|
||||
server.RegisterPostProcessor(pp)
|
||||
go server.Serve()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EnvironmentServer) Provisioner(name string, reply *uint32) error {
|
||||
prov, err := e.env.Provisioner(name)
|
||||
if err != nil {
|
||||
return NewBasicError(err)
|
||||
}
|
||||
|
||||
*reply = e.mux.NextId()
|
||||
server := newServerWithMux(e.mux, *reply)
|
||||
server.RegisterProvisioner(prov)
|
||||
go server.Serve()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EnvironmentServer) Ui(args *interface{}, reply *uint32) error {
|
||||
ui := e.env.Ui()
|
||||
|
||||
*reply = e.mux.NextId()
|
||||
server := newServerWithMux(e.mux, *reply)
|
||||
server.RegisterUi(ui)
|
||||
go server.Serve()
|
||||
return nil
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testEnvBuilder = &packer.MockBuilder{}
|
||||
var testEnvCache = &testCache{}
|
||||
var testEnvUi = &testUi{}
|
||||
|
||||
type testEnvironment struct {
|
||||
builderCalled bool
|
||||
builderName string
|
||||
cliCalled bool
|
||||
cliArgs []string
|
||||
hookCalled bool
|
||||
hookName string
|
||||
ppCalled bool
|
||||
ppName string
|
||||
provCalled bool
|
||||
provName string
|
||||
uiCalled bool
|
||||
}
|
||||
|
||||
func (e *testEnvironment) Builder(name string) (packer.Builder, error) {
|
||||
e.builderCalled = true
|
||||
e.builderName = name
|
||||
return testEnvBuilder, nil
|
||||
}
|
||||
|
||||
func (e *testEnvironment) Cache() packer.Cache {
|
||||
return testEnvCache
|
||||
}
|
||||
|
||||
func (e *testEnvironment) Cli(args []string) (int, error) {
|
||||
e.cliCalled = true
|
||||
e.cliArgs = args
|
||||
return 42, nil
|
||||
}
|
||||
|
||||
func (e *testEnvironment) Hook(name string) (packer.Hook, error) {
|
||||
e.hookCalled = true
|
||||
e.hookName = name
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e *testEnvironment) PostProcessor(name string) (packer.PostProcessor, error) {
|
||||
e.ppCalled = true
|
||||
e.ppName = name
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e *testEnvironment) Provisioner(name string) (packer.Provisioner, error) {
|
||||
e.provCalled = true
|
||||
e.provName = name
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e *testEnvironment) Ui() packer.Ui {
|
||||
e.uiCalled = true
|
||||
return testEnvUi
|
||||
}
|
||||
|
||||
func TestEnvironmentRPC(t *testing.T) {
|
||||
// Create the interface to test
|
||||
e := &testEnvironment{}
|
||||
|
||||
// Start the server
|
||||
client, server := testClientServer(t)
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
server.RegisterEnvironment(e)
|
||||
eClient := client.Environment()
|
||||
|
||||
// Test Builder
|
||||
builder, _ := eClient.Builder("foo")
|
||||
if !e.builderCalled {
|
||||
t.Fatal("builder should be called")
|
||||
}
|
||||
if e.builderName != "foo" {
|
||||
t.Fatalf("bad: %#v", e.builderName)
|
||||
}
|
||||
|
||||
builder.Prepare(nil)
|
||||
if !testEnvBuilder.PrepareCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
|
||||
// Test Cache
|
||||
cache := eClient.Cache()
|
||||
cache.Lock("foo")
|
||||
if !testEnvCache.lockCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
|
||||
// Test Provisioner
|
||||
_, _ = eClient.Provisioner("foo")
|
||||
if !e.provCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if e.provName != "foo" {
|
||||
t.Fatalf("bad: %s", e.provName)
|
||||
}
|
||||
|
||||
// Test Ui
|
||||
ui := eClient.Ui()
|
||||
if !e.uiCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
|
||||
// Test calls on the Ui
|
||||
ui.Say("format")
|
||||
if !testEnvUi.sayCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if testEnvUi.sayMessage != "format" {
|
||||
t.Fatalf("bad: %#v", testEnvUi.sayMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvironment_ImplementsEnvironment(t *testing.T) {
|
||||
var _ packer.Environment = new(Environment)
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -1,734 +0,0 @@
|
|||
package packer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
jsonutil "github.com/mitchellh/packer/common/json"
|
||||
)
|
||||
|
||||
// The rawTemplate struct represents the structure of a template read
|
||||
// directly from a file. The builders and other components map just to
|
||||
// "interface{}" pointers since we actually don't know what their contents
|
||||
// are until we read the "type" field.
|
||||
type rawTemplate struct {
|
||||
MinimumPackerVersion string `mapstructure:"min_packer_version"`
|
||||
|
||||
Description string
|
||||
Builders []map[string]interface{}
|
||||
Hooks map[string][]string
|
||||
Push PushConfig
|
||||
PostProcessors []interface{} `mapstructure:"post-processors"`
|
||||
Provisioners []map[string]interface{}
|
||||
Variables map[string]interface{}
|
||||
}
|
||||
|
||||
// The Template struct represents a parsed template, parsed into the most
|
||||
// completed form it can be without additional processing by the caller.
|
||||
type Template struct {
|
||||
RawContents []byte
|
||||
Description string
|
||||
Variables map[string]RawVariable
|
||||
Builders map[string]RawBuilderConfig
|
||||
Hooks map[string][]string
|
||||
Push *PushConfig
|
||||
PostProcessors [][]RawPostProcessorConfig
|
||||
Provisioners []RawProvisionerConfig
|
||||
}
|
||||
|
||||
// PushConfig is the configuration structure for the push settings.
|
||||
type PushConfig struct {
|
||||
Name string
|
||||
Address string
|
||||
BaseDir string `mapstructure:"base_dir"`
|
||||
Include []string
|
||||
Exclude []string
|
||||
Token string
|
||||
VCS bool
|
||||
}
|
||||
|
||||
// The RawBuilderConfig struct represents a raw, unprocessed builder
|
||||
// configuration. It contains the name of the builder as well as the
|
||||
// raw configuration. If requested, this is used to compile into a full
|
||||
// builder configuration at some point.
|
||||
type RawBuilderConfig struct {
|
||||
Name string
|
||||
Type string
|
||||
|
||||
RawConfig interface{}
|
||||
}
|
||||
|
||||
// RawPostProcessorConfig represents a raw, unprocessed post-processor
|
||||
// configuration. It contains the type of the post processor as well as the
|
||||
// raw configuration that is handed to the post-processor for it to process.
|
||||
type RawPostProcessorConfig struct {
|
||||
TemplateOnlyExcept `mapstructure:",squash"`
|
||||
|
||||
Type string
|
||||
KeepInputArtifact bool `mapstructure:"keep_input_artifact"`
|
||||
RawConfig map[string]interface{}
|
||||
}
|
||||
|
||||
// RawProvisionerConfig represents a raw, unprocessed provisioner configuration.
|
||||
// It contains the type of the provisioner as well as the raw configuration
|
||||
// that is handed to the provisioner for it to process.
|
||||
type RawProvisionerConfig struct {
|
||||
TemplateOnlyExcept `mapstructure:",squash"`
|
||||
|
||||
Type string
|
||||
Override map[string]interface{}
|
||||
RawPauseBefore string `mapstructure:"pause_before"`
|
||||
|
||||
RawConfig interface{}
|
||||
|
||||
pauseBefore time.Duration
|
||||
}
|
||||
|
||||
// RawVariable represents a variable configuration within a template.
|
||||
type RawVariable struct {
|
||||
Default string // The default value for this variable
|
||||
Required bool // If the variable is required or not
|
||||
Value string // The set value for this variable
|
||||
HasValue bool // True if the value was set
|
||||
}
|
||||
|
||||
// ParseTemplate takes a byte slice and parses a Template from it, returning
|
||||
// the template and possibly errors while loading the template. The error
|
||||
// could potentially be a MultiError, representing multiple errors. Knowing
|
||||
// and checking for this can be useful, if you wish to format it in a certain
|
||||
// way.
|
||||
//
|
||||
// The second parameter, vars, are the values for a set of user variables.
|
||||
func ParseTemplate(data []byte, vars map[string]string) (t *Template, err error) {
|
||||
var rawTplInterface interface{}
|
||||
err = jsonutil.Unmarshal(data, &rawTplInterface)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the raw template interface into the actual rawTemplate
|
||||
// structure, checking for any extranneous keys along the way.
|
||||
var md mapstructure.Metadata
|
||||
var rawTpl rawTemplate
|
||||
decoderConfig := &mapstructure.DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: &rawTpl,
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(decoderConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = decoder.Decode(rawTplInterface)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if rawTpl.MinimumPackerVersion != "" {
|
||||
// TODO: NOPE! Replace this
|
||||
Version := "1.0"
|
||||
vCur, err := version.NewVersion(Version)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vReq, err := version.NewVersion(rawTpl.MinimumPackerVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"'minimum_packer_version' error: %s", err)
|
||||
}
|
||||
|
||||
if vCur.LessThan(vReq) {
|
||||
return nil, fmt.Errorf(
|
||||
"Template requires Packer version %s. "+
|
||||
"Running version is %s.",
|
||||
vReq, vCur)
|
||||
}
|
||||
}
|
||||
|
||||
errors := make([]error, 0)
|
||||
|
||||
if len(md.Unused) > 0 {
|
||||
sort.Strings(md.Unused)
|
||||
for _, unused := range md.Unused {
|
||||
errors = append(
|
||||
errors, fmt.Errorf("Unknown root level key in template: '%s'", unused))
|
||||
}
|
||||
}
|
||||
|
||||
t = &Template{}
|
||||
t.RawContents = data
|
||||
t.Description = rawTpl.Description
|
||||
t.Variables = make(map[string]RawVariable)
|
||||
t.Builders = make(map[string]RawBuilderConfig)
|
||||
t.Hooks = rawTpl.Hooks
|
||||
t.Push = &rawTpl.Push
|
||||
t.PostProcessors = make([][]RawPostProcessorConfig, len(rawTpl.PostProcessors))
|
||||
t.Provisioners = make([]RawProvisionerConfig, len(rawTpl.Provisioners))
|
||||
|
||||
// Gather all the variables
|
||||
for k, v := range rawTpl.Variables {
|
||||
var variable RawVariable
|
||||
variable.Required = v == nil
|
||||
|
||||
// Create a new mapstructure decoder in order to decode the default
|
||||
// value since this is the only value in the regular template that
|
||||
// can be weakly typed.
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
Result: &variable.Default,
|
||||
WeaklyTypedInput: true,
|
||||
})
|
||||
if err != nil {
|
||||
// This should never happen.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = decoder.Decode(v)
|
||||
if err != nil {
|
||||
errors = append(errors,
|
||||
fmt.Errorf("Error decoding default value for user var '%s': %s", k, err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Set the value of this variable if we have it
|
||||
if val, ok := vars[k]; ok {
|
||||
variable.HasValue = true
|
||||
variable.Value = val
|
||||
delete(vars, k)
|
||||
}
|
||||
|
||||
t.Variables[k] = variable
|
||||
}
|
||||
|
||||
// Gather all the builders
|
||||
for i, v := range rawTpl.Builders {
|
||||
var raw RawBuilderConfig
|
||||
if err := mapstructure.Decode(v, &raw); err != nil {
|
||||
if merr, ok := err.(*mapstructure.Error); ok {
|
||||
for _, err := range merr.Errors {
|
||||
errors = append(errors, fmt.Errorf("builder %d: %s", i+1, err))
|
||||
}
|
||||
} else {
|
||||
errors = append(errors, fmt.Errorf("builder %d: %s", i+1, err))
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if raw.Type == "" {
|
||||
errors = append(errors, fmt.Errorf("builder %d: missing 'type'", i+1))
|
||||
continue
|
||||
}
|
||||
|
||||
// Attempt to get the name of the builder. If the "name" key
|
||||
// missing, use the "type" field, which is guaranteed to exist
|
||||
// at this point.
|
||||
if raw.Name == "" {
|
||||
raw.Name = raw.Type
|
||||
}
|
||||
|
||||
// Check if we already have a builder with this name and error if so
|
||||
if _, ok := t.Builders[raw.Name]; ok {
|
||||
errors = append(errors, fmt.Errorf("builder with name '%s' already exists", raw.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
// Now that we have the name, remove it from the config - as the builder
|
||||
// itself doesn't know about, and it will cause a validation error.
|
||||
delete(v, "name")
|
||||
|
||||
raw.RawConfig = v
|
||||
|
||||
t.Builders[raw.Name] = raw
|
||||
}
|
||||
|
||||
// Gather all the post-processors. This is a complicated process since there
|
||||
// are actually three different formats that the user can use to define
|
||||
// a post-processor.
|
||||
for i, rawV := range rawTpl.PostProcessors {
|
||||
rawPP, err := parsePostProcessor(i, rawV)
|
||||
if err != nil {
|
||||
errors = append(errors, err...)
|
||||
continue
|
||||
}
|
||||
|
||||
configs := make([]RawPostProcessorConfig, 0, len(rawPP))
|
||||
for j, pp := range rawPP {
|
||||
var config RawPostProcessorConfig
|
||||
if err := mapstructure.Decode(pp, &config); err != nil {
|
||||
if merr, ok := err.(*mapstructure.Error); ok {
|
||||
for _, err := range merr.Errors {
|
||||
errors = append(errors,
|
||||
fmt.Errorf("Post-processor #%d.%d: %s", i+1, j+1, err))
|
||||
}
|
||||
} else {
|
||||
errors = append(errors,
|
||||
fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err))
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if config.Type == "" {
|
||||
errors = append(errors,
|
||||
fmt.Errorf("Post-processor %d.%d: missing 'type'", i+1, j+1))
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove the input keep_input_artifact option
|
||||
config.TemplateOnlyExcept.Prune(pp)
|
||||
delete(pp, "keep_input_artifact")
|
||||
|
||||
// Verify that the only settings are good
|
||||
if errs := config.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
errors = append(errors,
|
||||
fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err))
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
config.RawConfig = pp
|
||||
|
||||
// Add it to the list of configs
|
||||
configs = append(configs, config)
|
||||
}
|
||||
|
||||
t.PostProcessors[i] = configs
|
||||
}
|
||||
|
||||
// Gather all the provisioners
|
||||
for i, v := range rawTpl.Provisioners {
|
||||
raw := &t.Provisioners[i]
|
||||
if err := mapstructure.Decode(v, raw); err != nil {
|
||||
if merr, ok := err.(*mapstructure.Error); ok {
|
||||
for _, err := range merr.Errors {
|
||||
errors = append(errors, fmt.Errorf("provisioner %d: %s", i+1, err))
|
||||
}
|
||||
} else {
|
||||
errors = append(errors, fmt.Errorf("provisioner %d: %s", i+1, err))
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if raw.Type == "" {
|
||||
errors = append(errors, fmt.Errorf("provisioner %d: missing 'type'", i+1))
|
||||
continue
|
||||
}
|
||||
|
||||
// Delete the keys that we used
|
||||
raw.TemplateOnlyExcept.Prune(v)
|
||||
delete(v, "override")
|
||||
|
||||
// Verify that the override keys exist...
|
||||
for name, _ := range raw.Override {
|
||||
if _, ok := t.Builders[name]; !ok {
|
||||
errors = append(
|
||||
errors, fmt.Errorf("provisioner %d: build '%s' not found for override", i+1, name))
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the only settings are good
|
||||
if errs := raw.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
errors = append(errors,
|
||||
fmt.Errorf("provisioner %d: %s", i+1, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the pause settings
|
||||
if raw.RawPauseBefore != "" {
|
||||
duration, err := time.ParseDuration(raw.RawPauseBefore)
|
||||
if err != nil {
|
||||
errors = append(
|
||||
errors, fmt.Errorf(
|
||||
"provisioner %d: pause_before invalid: %s",
|
||||
i+1, err))
|
||||
}
|
||||
|
||||
raw.pauseBefore = duration
|
||||
}
|
||||
|
||||
// Remove the pause_before setting if it is there so that we don't
|
||||
// get template validation errors later.
|
||||
delete(v, "pause_before")
|
||||
|
||||
raw.RawConfig = v
|
||||
}
|
||||
|
||||
if len(t.Builders) == 0 {
|
||||
errors = append(errors, fmt.Errorf("No builders are defined in the template."))
|
||||
}
|
||||
|
||||
// Verify that all the variable sets were for real variables.
|
||||
for k, _ := range vars {
|
||||
errors = append(errors, fmt.Errorf("Unknown user variables: %s", k))
|
||||
}
|
||||
|
||||
// If there were errors, we put it into a MultiError and return
|
||||
if len(errors) > 0 {
|
||||
err = &MultiError{errors}
|
||||
t = nil
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ParseTemplateFile takes the given template file and parses it into
|
||||
// a single template.
|
||||
func ParseTemplateFile(path string, vars map[string]string) (*Template, error) {
|
||||
var data []byte
|
||||
|
||||
if path == "-" {
|
||||
// Read from stdin...
|
||||
buf := new(bytes.Buffer)
|
||||
_, err := io.Copy(buf, os.Stdin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data = buf.Bytes()
|
||||
} else {
|
||||
var err error
|
||||
data, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ParseTemplate(data, vars)
|
||||
}
|
||||
|
||||
func parsePostProcessor(i int, rawV interface{}) (result []map[string]interface{}, errors []error) {
|
||||
switch v := rawV.(type) {
|
||||
case string:
|
||||
result = []map[string]interface{}{
|
||||
{"type": v},
|
||||
}
|
||||
case map[string]interface{}:
|
||||
result = []map[string]interface{}{v}
|
||||
case []interface{}:
|
||||
result = make([]map[string]interface{}, len(v))
|
||||
errors = make([]error, 0)
|
||||
for j, innerRawV := range v {
|
||||
switch innerV := innerRawV.(type) {
|
||||
case string:
|
||||
result[j] = map[string]interface{}{"type": innerV}
|
||||
case map[string]interface{}:
|
||||
result[j] = innerV
|
||||
case []interface{}:
|
||||
errors = append(
|
||||
errors,
|
||||
fmt.Errorf("Post-processor %d.%d: sequences not allowed to be nested in sequences", i+1, j+1))
|
||||
default:
|
||||
errors = append(errors, fmt.Errorf("Post-processor %d.%d is in a bad format.", i+1, j+1))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) == 0 {
|
||||
errors = nil
|
||||
}
|
||||
default:
|
||||
result = nil
|
||||
errors = []error{fmt.Errorf("Post-processor %d is in a bad format.", i+1)}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// BuildNames returns a slice of the available names of builds that
|
||||
// this template represents.
|
||||
func (t *Template) BuildNames() []string {
|
||||
names := make([]string, 0, len(t.Builders))
|
||||
for name, _ := range t.Builders {
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// Build returns a Build for the given name.
|
||||
//
|
||||
// If the build does not exist as part of this template, an error is
|
||||
// returned.
|
||||
func (t *Template) Build(name string, components *ComponentFinder) (b Build, err error) {
|
||||
// Setup the Builder
|
||||
builderConfig, ok := t.Builders[name]
|
||||
if !ok {
|
||||
err = fmt.Errorf("No such build found in template: %s", name)
|
||||
return
|
||||
}
|
||||
|
||||
// We panic if there is no builder function because this is really
|
||||
// an internal bug that always needs to be fixed, not an error.
|
||||
if components.Builder == nil {
|
||||
panic("no builder function")
|
||||
}
|
||||
|
||||
// Panic if there are provisioners on the template but no provisioner
|
||||
// component finder. This is always an internal error, so we panic.
|
||||
if len(t.Provisioners) > 0 && components.Provisioner == nil {
|
||||
panic("no provisioner function")
|
||||
}
|
||||
|
||||
builder, err := components.Builder(builderConfig.Type)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if builder == nil {
|
||||
err = fmt.Errorf("Builder type not found: %s", builderConfig.Type)
|
||||
return
|
||||
}
|
||||
|
||||
// Process the name
|
||||
tpl, variables, err := t.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawName := name
|
||||
name, err = tpl.Process(name, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Gather the Hooks
|
||||
hooks := make(map[string][]Hook)
|
||||
for tplEvent, tplHooks := range t.Hooks {
|
||||
curHooks := make([]Hook, 0, len(tplHooks))
|
||||
|
||||
for _, hookName := range tplHooks {
|
||||
var hook Hook
|
||||
hook, err = components.Hook(hookName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if hook == nil {
|
||||
err = fmt.Errorf("Hook not found: %s", hookName)
|
||||
return
|
||||
}
|
||||
|
||||
curHooks = append(curHooks, hook)
|
||||
}
|
||||
|
||||
hooks[tplEvent] = curHooks
|
||||
}
|
||||
|
||||
// Prepare the post-processors
|
||||
postProcessors := make([][]coreBuildPostProcessor, 0, len(t.PostProcessors))
|
||||
for _, rawPPs := range t.PostProcessors {
|
||||
current := make([]coreBuildPostProcessor, 0, len(rawPPs))
|
||||
for _, rawPP := range rawPPs {
|
||||
if rawPP.TemplateOnlyExcept.Skip(rawName) {
|
||||
continue
|
||||
}
|
||||
|
||||
pp, err := components.PostProcessor(rawPP.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pp == nil {
|
||||
return nil, fmt.Errorf("PostProcessor type not found: %s", rawPP.Type)
|
||||
}
|
||||
|
||||
current = append(current, coreBuildPostProcessor{
|
||||
processor: pp,
|
||||
processorType: rawPP.Type,
|
||||
config: rawPP.RawConfig,
|
||||
keepInputArtifact: rawPP.KeepInputArtifact,
|
||||
})
|
||||
}
|
||||
|
||||
// If we have no post-processors in this chain, just continue.
|
||||
// This can happen if the post-processors skip certain builds.
|
||||
if len(current) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
postProcessors = append(postProcessors, current)
|
||||
}
|
||||
|
||||
// Prepare the provisioners
|
||||
provisioners := make([]coreBuildProvisioner, 0, len(t.Provisioners))
|
||||
for _, rawProvisioner := range t.Provisioners {
|
||||
if rawProvisioner.TemplateOnlyExcept.Skip(rawName) {
|
||||
continue
|
||||
}
|
||||
|
||||
var provisioner Provisioner
|
||||
provisioner, err = components.Provisioner(rawProvisioner.Type)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if provisioner == nil {
|
||||
err = fmt.Errorf("Provisioner type not found: %s", rawProvisioner.Type)
|
||||
return
|
||||
}
|
||||
|
||||
configs := make([]interface{}, 1, 2)
|
||||
configs[0] = rawProvisioner.RawConfig
|
||||
|
||||
if rawProvisioner.Override != nil {
|
||||
if override, ok := rawProvisioner.Override[name]; ok {
|
||||
configs = append(configs, override)
|
||||
}
|
||||
}
|
||||
|
||||
if rawProvisioner.pauseBefore > 0 {
|
||||
provisioner = &PausedProvisioner{
|
||||
PauseBefore: rawProvisioner.pauseBefore,
|
||||
Provisioner: provisioner,
|
||||
}
|
||||
}
|
||||
|
||||
coreProv := coreBuildProvisioner{provisioner, configs}
|
||||
provisioners = append(provisioners, coreProv)
|
||||
}
|
||||
|
||||
b = &coreBuild{
|
||||
name: name,
|
||||
builder: builder,
|
||||
builderConfig: builderConfig.RawConfig,
|
||||
builderType: builderConfig.Type,
|
||||
hooks: hooks,
|
||||
postProcessors: postProcessors,
|
||||
provisioners: provisioners,
|
||||
variables: variables,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//Build a ConfigTemplate object populated by the values within a
|
||||
//parsed template
|
||||
func (t *Template) NewConfigTemplate() (c *ConfigTemplate, variables map[string]string, err error) {
|
||||
|
||||
// Prepare the variable template processor, which is a bit unique
|
||||
// because we don't allow user variable usage and we add a function
|
||||
// to read from the environment.
|
||||
varTpl, err := NewConfigTemplate()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
varTpl.Funcs(template.FuncMap{
|
||||
"env": templateEnv,
|
||||
"user": templateDisableUser,
|
||||
})
|
||||
|
||||
// Prepare the variables
|
||||
var varErrors []error
|
||||
variables = make(map[string]string)
|
||||
for k, v := range t.Variables {
|
||||
if v.Required && !v.HasValue {
|
||||
varErrors = append(varErrors,
|
||||
fmt.Errorf("Required user variable '%s' not set", k))
|
||||
}
|
||||
|
||||
var val string
|
||||
if v.HasValue {
|
||||
val = v.Value
|
||||
} else {
|
||||
val, err = varTpl.Process(v.Default, nil)
|
||||
if err != nil {
|
||||
varErrors = append(varErrors,
|
||||
fmt.Errorf("Error processing user variable '%s': %s'", k, err))
|
||||
}
|
||||
}
|
||||
|
||||
variables[k] = val
|
||||
}
|
||||
|
||||
if len(varErrors) > 0 {
|
||||
return nil, variables, &MultiError{varErrors}
|
||||
}
|
||||
|
||||
// Process the name
|
||||
tpl, err := NewConfigTemplate()
|
||||
if err != nil {
|
||||
return nil, variables, err
|
||||
}
|
||||
tpl.UserVars = variables
|
||||
|
||||
return tpl, variables, nil
|
||||
}
|
||||
|
||||
// TemplateOnlyExcept contains the logic required for "only" and "except"
|
||||
// meta-parameters.
|
||||
type TemplateOnlyExcept struct {
|
||||
Only []string
|
||||
Except []string
|
||||
}
|
||||
|
||||
// Prune will prune out the used values from the raw map.
|
||||
func (t *TemplateOnlyExcept) Prune(raw map[string]interface{}) {
|
||||
delete(raw, "except")
|
||||
delete(raw, "only")
|
||||
}
|
||||
|
||||
// Skip tests if we should skip putting this item onto a build.
|
||||
func (t *TemplateOnlyExcept) Skip(name string) bool {
|
||||
if len(t.Only) > 0 {
|
||||
onlyFound := false
|
||||
for _, n := range t.Only {
|
||||
if n == name {
|
||||
onlyFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !onlyFound {
|
||||
// Skip this provisioner
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// If the name is in the except list, then skip that
|
||||
for _, n := range t.Except {
|
||||
if n == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Validates the only/except parameters.
|
||||
func (t *TemplateOnlyExcept) Validate(b map[string]RawBuilderConfig) (e []error) {
|
||||
if len(t.Only) > 0 && len(t.Except) > 0 {
|
||||
e = append(e,
|
||||
fmt.Errorf("Only one of 'only' or 'except' may be specified."))
|
||||
}
|
||||
|
||||
if len(t.Only) > 0 {
|
||||
for _, n := range t.Only {
|
||||
if _, ok := b[n]; !ok {
|
||||
e = append(e,
|
||||
fmt.Errorf("'only' specified builder '%s' not found", n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range t.Except {
|
||||
if _, ok := b[n]; !ok {
|
||||
e = append(e,
|
||||
fmt.Errorf("'except' specified builder '%s' not found", n))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"builders": [{
|
||||
"name": "{{upper `name`}}",
|
||||
"type": "test"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "test"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"builders": [
|
||||
{"type": "something"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"builders": [
|
||||
{"type": "{{upper `tubes`}}"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "test"
|
||||
}],
|
||||
|
||||
"post-processors": ["test"]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "test"
|
||||
}],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "test",
|
||||
"override": {
|
||||
"test": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "test"
|
||||
}],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "test",
|
||||
"only": ["test"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "test"
|
||||
}],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "test",
|
||||
"only": ["foo"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "test"
|
||||
}],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "test"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [
|
||||
{"type": "foo"}
|
||||
],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "foo",
|
||||
"only": ["bar"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"variables": {
|
||||
"foo": null
|
||||
},
|
||||
|
||||
"builders": [{
|
||||
"type": "foo"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package packer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCoreConfig(t *testing.T) *CoreConfig {
|
||||
// Create some test components
|
||||
components := ComponentFinder{
|
||||
Builder: func(n string) (Builder, error) {
|
||||
if n != "test" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &MockBuilder{}, nil
|
||||
},
|
||||
}
|
||||
|
||||
return &CoreConfig{
|
||||
Cache: &FileCache{CacheDir: os.TempDir()},
|
||||
Components: components,
|
||||
Ui: TestUi(t),
|
||||
}
|
||||
}
|
||||
|
||||
func TestCore(t *testing.T, c *CoreConfig) *Core {
|
||||
core, err := NewCore(c)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return core
|
||||
}
|
||||
|
||||
func TestUi(t *testing.T) Ui {
|
||||
var buf bytes.Buffer
|
||||
return &BasicUi{
|
||||
Reader: &buf,
|
||||
Writer: ioutil.Discard,
|
||||
ErrorWriter: ioutil.Discard,
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuilder sets the builder with the name n to the component finder
|
||||
// and returns the mock.
|
||||
func TestBuilder(t *testing.T, c *CoreConfig, n string) *MockBuilder {
|
||||
var b MockBuilder
|
||||
|
||||
c.Components.Builder = func(actual string) (Builder, error) {
|
||||
if actual != n {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
return &b
|
||||
}
|
||||
|
||||
// TestProvisioner sets the prov. with the name n to the component finder
|
||||
// and returns the mock.
|
||||
func TestProvisioner(t *testing.T, c *CoreConfig, n string) *MockProvisioner {
|
||||
var b MockProvisioner
|
||||
|
||||
c.Components.Provisioner = func(actual string) (Provisioner, error) {
|
||||
if actual != n {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
return &b
|
||||
}
|
||||
|
||||
// TestPostProcessor sets the prov. with the name n to the component finder
|
||||
// and returns the mock.
|
||||
func TestPostProcessor(t *testing.T, c *CoreConfig, n string) *MockPostProcessor {
|
||||
var b MockPostProcessor
|
||||
|
||||
c.Components.PostProcessor = func(actual string) (PostProcessor, error) {
|
||||
if actual != n {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
return &b
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/common/uuid"
|
||||
)
|
||||
|
||||
// InitTime is the UTC time when this package was initialized. It is
|
||||
// used as the timestamp for all configuration templates so that they
|
||||
// match for a single build.
|
||||
var InitTime time.Time
|
||||
|
||||
func init() {
|
||||
InitTime = time.Now().UTC()
|
||||
}
|
||||
|
||||
// Funcs are the interpolation funcs that are available within interpolations.
|
||||
var FuncGens = map[string]FuncGenerator{
|
||||
"env": funcGenEnv,
|
||||
"isotime": funcGenIsotime,
|
||||
"pwd": funcGenPwd,
|
||||
"timestamp": funcGenTimestamp,
|
||||
"uuid": funcGenUuid,
|
||||
"user": funcGenUser,
|
||||
|
||||
"upper": funcGenPrimitive(strings.ToUpper),
|
||||
"lower": funcGenPrimitive(strings.ToLower),
|
||||
}
|
||||
|
||||
// FuncGenerator is a function that given a context generates a template
|
||||
// function for the template.
|
||||
type FuncGenerator func(*Context) interface{}
|
||||
|
||||
// Funcs returns the functions that can be used for interpolation given
|
||||
// a context.
|
||||
func Funcs(ctx *Context) template.FuncMap {
|
||||
result := make(map[string]interface{})
|
||||
for k, v := range FuncGens {
|
||||
result[k] = v(ctx)
|
||||
}
|
||||
|
||||
return template.FuncMap(result)
|
||||
}
|
||||
|
||||
func funcGenEnv(ctx *Context) interface{} {
|
||||
return func(k string) (string, error) {
|
||||
if !ctx.EnableEnv {
|
||||
// The error message doesn't have to be that detailed since
|
||||
// semantic checks should catch this.
|
||||
return "", errors.New("env vars are not allowed here")
|
||||
}
|
||||
|
||||
return os.Getenv(k), nil
|
||||
}
|
||||
}
|
||||
|
||||
func funcGenIsotime(ctx *Context) interface{} {
|
||||
return func(format ...string) (string, error) {
|
||||
if len(format) == 0 {
|
||||
return time.Now().UTC().Format(time.RFC3339), nil
|
||||
}
|
||||
|
||||
if len(format) > 1 {
|
||||
return "", fmt.Errorf("too many values, 1 needed: %v", format)
|
||||
}
|
||||
|
||||
return time.Now().UTC().Format(format[0]), nil
|
||||
}
|
||||
}
|
||||
|
||||
func funcGenPrimitive(value interface{}) FuncGenerator {
|
||||
return func(ctx *Context) interface{} {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func funcGenPwd(ctx *Context) interface{} {
|
||||
return func() (string, error) {
|
||||
return os.Getwd()
|
||||
}
|
||||
}
|
||||
|
||||
func funcGenTimestamp(ctx *Context) interface{} {
|
||||
return func() string {
|
||||
return strconv.FormatInt(InitTime.Unix(), 10)
|
||||
}
|
||||
}
|
||||
|
||||
func funcGenUser(ctx *Context) interface{} {
|
||||
return func(k string) string {
|
||||
if ctx == nil || ctx.UserVariables == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ctx.UserVariables[k]
|
||||
}
|
||||
}
|
||||
|
||||
func funcGenUuid(ctx *Context) interface{} {
|
||||
return func() string {
|
||||
return uuid.TimeOrderedUUID()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFuncEnv(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Output string
|
||||
}{
|
||||
{
|
||||
`{{env "PACKER_TEST_ENV"}}`,
|
||||
`foo`,
|
||||
},
|
||||
|
||||
{
|
||||
`{{env "PACKER_TEST_ENV_NOPE"}}`,
|
||||
``,
|
||||
},
|
||||
}
|
||||
|
||||
os.Setenv("PACKER_TEST_ENV", "foo")
|
||||
defer os.Setenv("PACKER_TEST_ENV", "")
|
||||
|
||||
ctx := &Context{EnableEnv: true}
|
||||
for _, tc := range cases {
|
||||
i := &I{Value: tc.Input}
|
||||
result, err := i.Render(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err)
|
||||
}
|
||||
|
||||
if result != tc.Output {
|
||||
t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncEnv_disable(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Output string
|
||||
Error bool
|
||||
}{
|
||||
{
|
||||
`{{env "PACKER_TEST_ENV"}}`,
|
||||
"",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := &Context{EnableEnv: false}
|
||||
for _, tc := range cases {
|
||||
i := &I{Value: tc.Input}
|
||||
result, err := i.Render(ctx)
|
||||
if (err != nil) != tc.Error {
|
||||
t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err)
|
||||
}
|
||||
|
||||
if result != tc.Output {
|
||||
t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncIsotime(t *testing.T) {
|
||||
ctx := &Context{}
|
||||
i := &I{Value: "{{isotime}}"}
|
||||
result, err := i.Render(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
val, err := time.Parse(time.RFC3339, result)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
currentTime := time.Now().UTC()
|
||||
if currentTime.Sub(val) > 2*time.Second {
|
||||
t.Fatalf("val: %d (current: %d)", val, currentTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncPwd(t *testing.T) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
Input string
|
||||
Output string
|
||||
}{
|
||||
{
|
||||
`{{pwd}}`,
|
||||
wd,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := &Context{}
|
||||
for _, tc := range cases {
|
||||
i := &I{Value: tc.Input}
|
||||
result, err := i.Render(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err)
|
||||
}
|
||||
|
||||
if result != tc.Output {
|
||||
t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncTimestamp(t *testing.T) {
|
||||
expected := strconv.FormatInt(InitTime.Unix(), 10)
|
||||
|
||||
cases := []struct {
|
||||
Input string
|
||||
Output string
|
||||
}{
|
||||
{
|
||||
`{{timestamp}}`,
|
||||
expected,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := &Context{}
|
||||
for _, tc := range cases {
|
||||
i := &I{Value: tc.Input}
|
||||
result, err := i.Render(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err)
|
||||
}
|
||||
|
||||
if result != tc.Output {
|
||||
t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncUser(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Output string
|
||||
}{
|
||||
{
|
||||
`{{user "foo"}}`,
|
||||
`foo`,
|
||||
},
|
||||
|
||||
{
|
||||
`{{user "what"}}`,
|
||||
``,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := &Context{
|
||||
UserVariables: map[string]string{
|
||||
"foo": "foo",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
i := &I{Value: tc.Input}
|
||||
result, err := i.Render(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Input: %s\n\nerr: %s", tc.Input, err)
|
||||
}
|
||||
|
||||
if result != tc.Output {
|
||||
t.Fatalf("Input: %s\n\nGot: %s", tc.Input, result)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// Context is the context that an interpolation is done in. This defines
|
||||
// things such as available variables.
|
||||
type Context struct {
|
||||
// Data is the data for the template that is available
|
||||
Data interface{}
|
||||
|
||||
// UserVariables is the mapping of user variables that the
|
||||
// "user" function reads from.
|
||||
UserVariables map[string]string
|
||||
|
||||
// EnableEnv enables the env function
|
||||
EnableEnv bool
|
||||
}
|
||||
|
||||
// Render is shorthand for constructing an I and calling Render.
|
||||
func Render(v string, ctx *Context) (string, error) {
|
||||
return (&I{Value: v}).Render(ctx)
|
||||
}
|
||||
|
||||
// I stands for "interpolation" and is the main interpolation struct
|
||||
// in order to render values.
|
||||
type I struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
// Render renders the interpolation with the given context.
|
||||
func (i *I) Render(ctx *Context) (string, error) {
|
||||
tpl, err := i.template(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var result bytes.Buffer
|
||||
var data interface{}
|
||||
if ctx != nil {
|
||||
data = ctx.Data
|
||||
}
|
||||
if err := tpl.Execute(&result, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
func (i *I) template(ctx *Context) (*template.Template, error) {
|
||||
return template.New("root").Funcs(Funcs(ctx)).Parse(i.Value)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIRender(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Ctx *Context
|
||||
Value string
|
||||
Result string
|
||||
}{
|
||||
"basic": {
|
||||
nil,
|
||||
"foo",
|
||||
"foo",
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range cases {
|
||||
i := &I{Value: tc.Value}
|
||||
result, err := i.Render(tc.Ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("%s\n\ninput: %s\n\nerr: %s", k, tc.Value, err)
|
||||
}
|
||||
if result != tc.Result {
|
||||
t.Fatalf(
|
||||
"%s\n\ninput: %s\n\nexpected: %s\n\ngot: %s",
|
||||
k, tc.Value, tc.Result, result)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"text/template"
|
||||
"text/template/parse"
|
||||
)
|
||||
|
||||
// functionsCalled returns a map (to be used as a set) of the functions
|
||||
// that are called from the given text template.
|
||||
func functionsCalled(t *template.Template) map[string]struct{} {
|
||||
result := make(map[string]struct{})
|
||||
functionsCalledWalk(t.Tree.Root, result)
|
||||
return result
|
||||
}
|
||||
|
||||
func functionsCalledWalk(raw parse.Node, r map[string]struct{}) {
|
||||
switch node := raw.(type) {
|
||||
case *parse.ActionNode:
|
||||
functionsCalledWalk(node.Pipe, r)
|
||||
case *parse.CommandNode:
|
||||
if in, ok := node.Args[0].(*parse.IdentifierNode); ok {
|
||||
r[in.Ident] = struct{}{}
|
||||
}
|
||||
|
||||
for _, n := range node.Args[1:] {
|
||||
functionsCalledWalk(n, r)
|
||||
}
|
||||
case *parse.ListNode:
|
||||
for _, n := range node.Nodes {
|
||||
functionsCalledWalk(n, r)
|
||||
}
|
||||
case *parse.PipeNode:
|
||||
for _, n := range node.Cmds {
|
||||
functionsCalledWalk(n, r)
|
||||
}
|
||||
case *parse.StringNode, *parse.TextNode:
|
||||
// Ignore
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type: %T", node))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func TestFunctionsCalled(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Result map[string]struct{}
|
||||
}{
|
||||
{
|
||||
"foo",
|
||||
map[string]struct{}{},
|
||||
},
|
||||
|
||||
{
|
||||
"foo {{user `bar`}}",
|
||||
map[string]struct{}{
|
||||
"user": struct{}{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
funcs := Funcs(&Context{})
|
||||
for _, tc := range cases {
|
||||
tpl, err := template.New("root").Funcs(funcs).Parse(tc.Input)
|
||||
if err != nil {
|
||||
t.Fatalf("err parsing: %v\n\n%s", tc.Input, err)
|
||||
}
|
||||
|
||||
actual := functionsCalled(tpl)
|
||||
if !reflect.DeepEqual(actual, tc.Result) {
|
||||
t.Fatalf("bad: %v\n\ngot: %#v", tc.Input, actual)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// rawTemplate is the direct JSON document format of the template file.
|
||||
// This is what is decoded directly from the file, and then it is turned
|
||||
// into a Template object thereafter.
|
||||
type rawTemplate struct {
|
||||
MinVersion string `mapstructure:"min_packer_version"`
|
||||
Description string
|
||||
|
||||
Builders []map[string]interface{}
|
||||
Push map[string]interface{}
|
||||
PostProcessors []interface{} `mapstructure:"post-processors"`
|
||||
Provisioners []map[string]interface{}
|
||||
Variables map[string]interface{}
|
||||
|
||||
RawContents []byte
|
||||
}
|
||||
|
||||
// Template returns the actual Template object built from this raw
|
||||
// structure.
|
||||
func (r *rawTemplate) Template() (*Template, error) {
|
||||
var result Template
|
||||
var errs error
|
||||
|
||||
// Copy some literals
|
||||
result.Description = r.Description
|
||||
result.MinVersion = r.MinVersion
|
||||
result.RawContents = r.RawContents
|
||||
|
||||
// Gather the variables
|
||||
if len(r.Variables) > 0 {
|
||||
result.Variables = make(map[string]*Variable, len(r.Variables))
|
||||
}
|
||||
for k, rawV := range r.Variables {
|
||||
var v Variable
|
||||
|
||||
// Variable is required if the value is exactly nil
|
||||
v.Required = rawV == nil
|
||||
|
||||
// Weak decode the default if we have one
|
||||
if err := r.decoder(&v.Default, nil).Decode(rawV); err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"variable %s: %s", k, err))
|
||||
continue
|
||||
}
|
||||
|
||||
result.Variables[k] = &v
|
||||
}
|
||||
|
||||
// Let's start by gathering all the builders
|
||||
if len(r.Builders) > 0 {
|
||||
result.Builders = make(map[string]*Builder, len(r.Builders))
|
||||
}
|
||||
for i, rawB := range r.Builders {
|
||||
var b Builder
|
||||
if err := mapstructure.WeakDecode(rawB, &b); err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"builder %d: %s", i+1, err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Set the raw configuration and delete any special keys
|
||||
b.Config = rawB
|
||||
delete(b.Config, "name")
|
||||
delete(b.Config, "type")
|
||||
if len(b.Config) == 0 {
|
||||
b.Config = nil
|
||||
}
|
||||
|
||||
// If there is no type set, it is an error
|
||||
if b.Type == "" {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"builder %d: missing 'type'", i+1))
|
||||
continue
|
||||
}
|
||||
|
||||
// The name defaults to the type if it isn't set
|
||||
if b.Name == "" {
|
||||
b.Name = b.Type
|
||||
}
|
||||
|
||||
// If this builder already exists, it is an error
|
||||
if _, ok := result.Builders[b.Name]; ok {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"builder %d: builder with name '%s' already exists",
|
||||
i+1, b.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
// Append the builders
|
||||
result.Builders[b.Name] = &b
|
||||
}
|
||||
|
||||
// Gather all the post-processors
|
||||
if len(r.PostProcessors) > 0 {
|
||||
result.PostProcessors = make([][]*PostProcessor, 0, len(r.PostProcessors))
|
||||
}
|
||||
for i, v := range r.PostProcessors {
|
||||
// Parse the configurations. We need to do this because post-processors
|
||||
// can take three different formats.
|
||||
configs, err := r.parsePostProcessor(i, v)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse the PostProcessors out of the configs
|
||||
pps := make([]*PostProcessor, 0, len(configs))
|
||||
for j, c := range configs {
|
||||
var pp PostProcessor
|
||||
if err := r.decoder(&pp, nil).Decode(c); err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"post-processor %d.%d: %s", i+1, j+1, err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Type is required
|
||||
if pp.Type == "" {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"post-processor %d.%d: type is required", i+1, j+1))
|
||||
continue
|
||||
}
|
||||
|
||||
// Set the configuration
|
||||
delete(c, "keep_input_artifact")
|
||||
delete(c, "type")
|
||||
if len(c) > 0 {
|
||||
pp.Config = c
|
||||
}
|
||||
|
||||
pps = append(pps, &pp)
|
||||
}
|
||||
|
||||
result.PostProcessors = append(result.PostProcessors, pps)
|
||||
}
|
||||
|
||||
// Gather all the provisioners
|
||||
if len(r.Provisioners) > 0 {
|
||||
result.Provisioners = make([]*Provisioner, 0, len(r.Provisioners))
|
||||
}
|
||||
for i, v := range r.Provisioners {
|
||||
var p Provisioner
|
||||
if err := r.decoder(&p, nil).Decode(v); err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"provisioner %d: %s", i+1, err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Type is required before any richer validation
|
||||
if p.Type == "" {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"provisioner %d: missing 'type'", i+1))
|
||||
continue
|
||||
}
|
||||
|
||||
// Copy the configuration
|
||||
delete(v, "except")
|
||||
delete(v, "only")
|
||||
delete(v, "override")
|
||||
delete(v, "pause_before")
|
||||
delete(v, "type")
|
||||
if len(v) > 0 {
|
||||
p.Config = v
|
||||
}
|
||||
|
||||
// TODO: stuff
|
||||
result.Provisioners = append(result.Provisioners, &p)
|
||||
}
|
||||
|
||||
// Push
|
||||
if len(r.Push) > 0 {
|
||||
var p Push
|
||||
if err := r.decoder(&p, nil).Decode(r.Push); err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf(
|
||||
"push: %s", err))
|
||||
}
|
||||
|
||||
result.Push = &p
|
||||
}
|
||||
|
||||
// If we have errors, return those with a nil result
|
||||
if errs != nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (r *rawTemplate) decoder(
|
||||
result interface{},
|
||||
md *mapstructure.Metadata) *mapstructure.Decoder {
|
||||
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
||||
Metadata: md,
|
||||
Result: result,
|
||||
})
|
||||
if err != nil {
|
||||
// This really shouldn't happen since we have firm control over
|
||||
// all the arguments and they're all unit tested. So we use a
|
||||
// panic here to note this would definitely be a bug.
|
||||
panic(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (r *rawTemplate) parsePostProcessor(
|
||||
i int, raw interface{}) ([]map[string]interface{}, error) {
|
||||
switch v := raw.(type) {
|
||||
case string:
|
||||
return []map[string]interface{}{
|
||||
{"type": v},
|
||||
}, nil
|
||||
case map[string]interface{}:
|
||||
return []map[string]interface{}{v}, nil
|
||||
case []interface{}:
|
||||
var err error
|
||||
result := make([]map[string]interface{}, len(v))
|
||||
for j, innerRaw := range v {
|
||||
switch innerV := innerRaw.(type) {
|
||||
case string:
|
||||
result[j] = map[string]interface{}{"type": innerV}
|
||||
case map[string]interface{}:
|
||||
result[j] = innerV
|
||||
case []interface{}:
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"post-processor %d.%d: sequence not allowed to be nested in a sequence",
|
||||
i+1, j+1))
|
||||
default:
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"post-processor %d.%d: unknown format",
|
||||
i+1, j+1))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("post-processor %d: bad format", i+1)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse takes the given io.Reader and parses a Template object out of it.
|
||||
func Parse(r io.Reader) (*Template, error) {
|
||||
// Create a buffer to copy what we read
|
||||
var buf bytes.Buffer
|
||||
r = io.TeeReader(r, &buf)
|
||||
|
||||
// First, decode the object into an interface{}. We do this instead of
|
||||
// the rawTemplate directly because we'd rather use mapstructure to
|
||||
// decode since it has richer errors.
|
||||
var raw interface{}
|
||||
if err := json.NewDecoder(r).Decode(&raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create our decoder
|
||||
var md mapstructure.Metadata
|
||||
var rawTpl rawTemplate
|
||||
rawTpl.RawContents = buf.Bytes()
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: &rawTpl,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Do the actual decode into our structure
|
||||
if err := decoder.Decode(raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build an error if there are unused root level keys
|
||||
if len(md.Unused) > 0 {
|
||||
sort.Strings(md.Unused)
|
||||
for _, unused := range md.Unused {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"Unknown root level key in template: '%s'", unused))
|
||||
}
|
||||
|
||||
// Return early for these errors
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return the template parsed from the raw structure
|
||||
return rawTpl.Template()
|
||||
}
|
||||
|
||||
// ParseFile is the same as Parse but is a helper to automatically open
|
||||
// a file for parsing.
|
||||
func ParseFile(path string) (*Template, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return Parse(f)
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
cases := []struct {
|
||||
File string
|
||||
Result *Template
|
||||
Err bool
|
||||
}{
|
||||
/*
|
||||
* Builders
|
||||
*/
|
||||
{
|
||||
"parse-basic.json",
|
||||
&Template{
|
||||
Builders: map[string]*Builder{
|
||||
"something": &Builder{
|
||||
Name: "something",
|
||||
Type: "something",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"parse-builder-no-type.json",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"parse-builder-repeat.json",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
/*
|
||||
* Provisioners
|
||||
*/
|
||||
{
|
||||
"parse-provisioner-basic.json",
|
||||
&Template{
|
||||
Provisioners: []*Provisioner{
|
||||
&Provisioner{
|
||||
Type: "something",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-provisioner-pause-before.json",
|
||||
&Template{
|
||||
Provisioners: []*Provisioner{
|
||||
&Provisioner{
|
||||
Type: "something",
|
||||
PauseBefore: 1 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-provisioner-only.json",
|
||||
&Template{
|
||||
Provisioners: []*Provisioner{
|
||||
&Provisioner{
|
||||
Type: "something",
|
||||
OnlyExcept: OnlyExcept{
|
||||
Only: []string{"foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-provisioner-except.json",
|
||||
&Template{
|
||||
Provisioners: []*Provisioner{
|
||||
&Provisioner{
|
||||
Type: "something",
|
||||
OnlyExcept: OnlyExcept{
|
||||
Except: []string{"foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-provisioner-override.json",
|
||||
&Template{
|
||||
Provisioners: []*Provisioner{
|
||||
&Provisioner{
|
||||
Type: "something",
|
||||
Override: map[string]interface{}{
|
||||
"foo": map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-provisioner-no-type.json",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-variable-default.json",
|
||||
&Template{
|
||||
Variables: map[string]*Variable{
|
||||
"foo": &Variable{
|
||||
Default: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-variable-required.json",
|
||||
&Template{
|
||||
Variables: map[string]*Variable{
|
||||
"foo": &Variable{
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-pp-basic.json",
|
||||
&Template{
|
||||
PostProcessors: [][]*PostProcessor{
|
||||
[]*PostProcessor{
|
||||
&PostProcessor{
|
||||
Type: "foo",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-pp-keep.json",
|
||||
&Template{
|
||||
PostProcessors: [][]*PostProcessor{
|
||||
[]*PostProcessor{
|
||||
&PostProcessor{
|
||||
Type: "foo",
|
||||
KeepInputArtifact: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-pp-string.json",
|
||||
&Template{
|
||||
PostProcessors: [][]*PostProcessor{
|
||||
[]*PostProcessor{
|
||||
&PostProcessor{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-pp-map.json",
|
||||
&Template{
|
||||
PostProcessors: [][]*PostProcessor{
|
||||
[]*PostProcessor{
|
||||
&PostProcessor{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-pp-slice.json",
|
||||
&Template{
|
||||
PostProcessors: [][]*PostProcessor{
|
||||
[]*PostProcessor{
|
||||
&PostProcessor{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
[]*PostProcessor{
|
||||
&PostProcessor{
|
||||
Type: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-pp-multi.json",
|
||||
&Template{
|
||||
PostProcessors: [][]*PostProcessor{
|
||||
[]*PostProcessor{
|
||||
&PostProcessor{
|
||||
Type: "foo",
|
||||
},
|
||||
&PostProcessor{
|
||||
Type: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-pp-no-type.json",
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-description.json",
|
||||
&Template{
|
||||
Description: "foo",
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-min-version.json",
|
||||
&Template{
|
||||
MinVersion: "1.2",
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"parse-push.json",
|
||||
&Template{
|
||||
Push: &Push{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tpl, err := ParseFile(fixtureDir(tc.File))
|
||||
if (err != nil) != tc.Err {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if tpl != nil {
|
||||
tpl.RawContents = nil
|
||||
}
|
||||
if !reflect.DeepEqual(tpl, tc.Result) {
|
||||
t.Fatalf("bad: %s\n\n%#v\n\n%#v", tc.File, tpl, tc.Result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_contents(t *testing.T) {
|
||||
tpl, err := ParseFile(fixtureDir("parse-contents.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(string(tpl.RawContents))
|
||||
expected := `{"builders":[{"type":"test"}]}`
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: %s\n\n%s", actual, expected)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
)
|
||||
|
||||
// Template represents the parsed template that is used to configure
|
||||
// Packer builds.
|
||||
type Template struct {
|
||||
Description string
|
||||
MinVersion string
|
||||
|
||||
Variables map[string]*Variable
|
||||
Builders map[string]*Builder
|
||||
Provisioners []*Provisioner
|
||||
PostProcessors [][]*PostProcessor
|
||||
Push *Push
|
||||
|
||||
// RawContents is just the raw data for this template
|
||||
RawContents []byte
|
||||
}
|
||||
|
||||
// Builder represents a builder configured in the template
|
||||
type Builder struct {
|
||||
Name string
|
||||
Type string
|
||||
Config map[string]interface{}
|
||||
}
|
||||
|
||||
// PostProcessor represents a post-processor within the template.
|
||||
type PostProcessor struct {
|
||||
OnlyExcept `mapstructure:",squash"`
|
||||
|
||||
Type string
|
||||
KeepInputArtifact bool `mapstructure:"keep_input_artifact"`
|
||||
Config map[string]interface{}
|
||||
}
|
||||
|
||||
// Provisioner represents a provisioner within the template.
|
||||
type Provisioner struct {
|
||||
OnlyExcept `mapstructure:",squash"`
|
||||
|
||||
Type string
|
||||
Config map[string]interface{}
|
||||
Override map[string]interface{}
|
||||
PauseBefore time.Duration `mapstructure:"pause_before"`
|
||||
}
|
||||
|
||||
// Push represents the configuration for pushing the template to Atlas.
|
||||
type Push struct {
|
||||
Name string
|
||||
Address string
|
||||
BaseDir string `mapstructure:"base_dir"`
|
||||
Include []string
|
||||
Exclude []string
|
||||
Token string
|
||||
VCS bool
|
||||
}
|
||||
|
||||
// Variable represents a variable within the template
|
||||
type Variable struct {
|
||||
Default string
|
||||
Required bool
|
||||
}
|
||||
|
||||
// OnlyExcept is a struct that is meant to be embedded that contains the
|
||||
// logic required for "only" and "except" meta-parameters.
|
||||
type OnlyExcept struct {
|
||||
Only []string
|
||||
Except []string
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Functions
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// Validate does some basic validation of the template on top of the
|
||||
// validation that occurs while parsing. If possible, we try to defer
|
||||
// validation to here. The validation errors that occur during parsing
|
||||
// are the minimal necessary to make sure parsing builds a reasonable
|
||||
// Template structure.
|
||||
func (t *Template) Validate() error {
|
||||
var err error
|
||||
|
||||
// At least one builder must be defined
|
||||
if len(t.Builders) == 0 {
|
||||
err = multierror.Append(err, errors.New(
|
||||
"at least one builder must be defined"))
|
||||
}
|
||||
|
||||
// Verify that the provisioner overrides target builders that exist
|
||||
for i, p := range t.Provisioners {
|
||||
// Validate only/except
|
||||
if verr := p.OnlyExcept.Validate(t); verr != nil {
|
||||
for _, e := range multierror.Append(verr).Errors {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"provisioner %d: %s", i+1, e))
|
||||
}
|
||||
}
|
||||
|
||||
// Validate overrides
|
||||
for name, _ := range p.Override {
|
||||
if _, ok := t.Builders[name]; !ok {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"provisioner %d: override '%s' doesn't exist",
|
||||
i+1, name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify post-processors
|
||||
for i, chain := range t.PostProcessors {
|
||||
for j, p := range chain {
|
||||
// Validate only/except
|
||||
if verr := p.OnlyExcept.Validate(t); verr != nil {
|
||||
for _, e := range multierror.Append(verr).Errors {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"post-processor %d.%d: %s", i+1, j+1, e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip says whether or not to skip the build with the given name.
|
||||
func (o *OnlyExcept) Skip(n string) bool {
|
||||
if len(o.Only) > 0 {
|
||||
for _, v := range o.Only {
|
||||
if v == n {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if len(o.Except) > 0 {
|
||||
for _, v := range o.Except {
|
||||
if v == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate validates that the OnlyExcept settings are correct for a thing.
|
||||
func (o *OnlyExcept) Validate(t *Template) error {
|
||||
if len(o.Only) > 0 && len(o.Except) > 0 {
|
||||
return errors.New("only one of 'only' or 'except' may be specified")
|
||||
}
|
||||
|
||||
var err error
|
||||
for _, n := range o.Only {
|
||||
if _, ok := t.Builders[n]; !ok {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"'only' specified builder '%s' not found", n))
|
||||
}
|
||||
}
|
||||
for _, n := range o.Except {
|
||||
if _, ok := t.Builders[n]; !ok {
|
||||
err = multierror.Append(err, fmt.Errorf(
|
||||
"'except' specified builder '%s' not found", n))
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// GoStringer
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
func (b *Builder) GoString() string {
|
||||
return fmt.Sprintf("*%#v", *b)
|
||||
}
|
||||
|
||||
func (p *Provisioner) GoString() string {
|
||||
return fmt.Sprintf("*%#v", *p)
|
||||
}
|
||||
|
||||
func (p *PostProcessor) GoString() string {
|
||||
return fmt.Sprintf("*%#v", *p)
|
||||
}
|
||||
|
||||
func (v *Variable) GoString() string {
|
||||
return fmt.Sprintf("*%#v", *v)
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package template
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const FixturesDir = "./test-fixtures"
|
||||
|
||||
// fixtureDir returns the path to a test fixtures directory
|
||||
func fixtureDir(n string) string {
|
||||
return filepath.Join(FixturesDir, n)
|
||||
}
|
||||
|
||||
func TestTemplateValidate(t *testing.T) {
|
||||
cases := []struct {
|
||||
File string
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
"validate-no-builders.json",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"validate-bad-override.json",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"validate-good-override.json",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"validate-bad-prov-only.json",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"validate-good-prov-only.json",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"validate-bad-prov-except.json",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"validate-good-prov-except.json",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"validate-bad-pp-only.json",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"validate-good-pp-only.json",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"validate-bad-pp-except.json",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"validate-good-pp-except.json",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
f, err := os.Open(fixtureDir(tc.File))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
tpl, err := Parse(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s\n\n%s", tc.File, err)
|
||||
}
|
||||
|
||||
err = tpl.Validate()
|
||||
if (err != nil) != tc.Err {
|
||||
t.Fatalf("err: %s\n\n%s", tc.File, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnlyExceptSkip(t *testing.T) {
|
||||
cases := []struct {
|
||||
Only, Except []string
|
||||
Input string
|
||||
Result bool
|
||||
}{
|
||||
{
|
||||
[]string{"foo"},
|
||||
nil,
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
nil,
|
||||
[]string{"foo"},
|
||||
"foo",
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
nil,
|
||||
nil,
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
oe := &OnlyExcept{
|
||||
Only: tc.Only,
|
||||
Except: tc.Except,
|
||||
}
|
||||
|
||||
actual := oe.Skip(tc.Input)
|
||||
if actual != tc.Result {
|
||||
t.Fatalf(
|
||||
"bad: %#v\n\n%#v\n\n%#v\n\n%#v",
|
||||
actual, tc.Only, tc.Except, tc.Input)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"builders": [{"type": "something"}]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"builders": [{"foo": "something"}]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"builders": [
|
||||
{"type": "something"},
|
||||
{"type": "something"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{"builders":[{"type":"test"}]}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"description": "foo"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"min_packer_version": "1.2"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"post-processors": [{
|
||||
"type": "foo",
|
||||
"foo": "bar"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"post-processors": [{
|
||||
"type": "foo",
|
||||
"keep_input_artifact": true
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"post-processors": [{
|
||||
"type": "foo"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"post-processors": [[{
|
||||
"type": "foo"
|
||||
}, "bar"]]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"post-processors": [{
|
||||
"keep_input_artifact": true
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"post-processors": [{
|
||||
"type": "foo"
|
||||
}, "bar"]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"post-processors": ["foo"]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"provisioners": [
|
||||
{"type": "something"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "something",
|
||||
"except": ["foo"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"provisioners": [
|
||||
{"foo": "something"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "something",
|
||||
"only": ["foo"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "something",
|
||||
"override": {
|
||||
"foo": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "something",
|
||||
"pause_before": "1s"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"push": {
|
||||
"name": "foo"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"variables": {
|
||||
"foo": "foo"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"variables": {
|
||||
"foo": null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "foo"
|
||||
}],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "bar",
|
||||
"override": {
|
||||
"bar": {}
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "foo"
|
||||
}],
|
||||
|
||||
"post-processors": [{
|
||||
"type": "bar",
|
||||
"except": ["bar"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "foo"
|
||||
}],
|
||||
|
||||
"post-processors": [{
|
||||
"type": "bar",
|
||||
"only": ["bar"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "foo"
|
||||
}],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "bar",
|
||||
"except": ["bar"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "foo"
|
||||
}],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "bar",
|
||||
"only": ["bar"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "foo"
|
||||
}],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "bar",
|
||||
"override": {
|
||||
"foo": {}
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "foo"
|
||||
}],
|
||||
|
||||
"post-processors": [{
|
||||
"type": "bar",
|
||||
"except": ["foo"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "foo"
|
||||
}],
|
||||
|
||||
"post-processors": [{
|
||||
"type": "bar",
|
||||
"only": ["foo"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "foo"
|
||||
}],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "bar",
|
||||
"except": ["foo"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"builders": [{
|
||||
"type": "foo"
|
||||
}],
|
||||
|
||||
"provisioners": [{
|
||||
"type": "bar",
|
||||
"only": ["foo"]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{}
|
Loading…
Reference in New Issue