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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
cmdcommon "github.com/mitchellh/packer/common/command"
|
|
||||||
"github.com/mitchellh/packer/packer"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"github.com/mitchellh/packer/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BuildCommand struct {
|
type BuildCommand struct {
|
||||||
@ -20,71 +20,52 @@ type BuildCommand struct {
|
|||||||
|
|
||||||
func (c BuildCommand) Run(args []string) int {
|
func (c BuildCommand) Run(args []string) int {
|
||||||
var cfgColor, cfgDebug, cfgForce, cfgParallel bool
|
var cfgColor, cfgDebug, cfgForce, cfgParallel bool
|
||||||
buildOptions := new(cmdcommon.BuildOptions)
|
flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars)
|
||||||
|
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
||||||
env, err := c.Meta.Environment()
|
flags.BoolVar(&cfgColor, "color", true, "")
|
||||||
if err != nil {
|
flags.BoolVar(&cfgDebug, "debug", false, "")
|
||||||
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
|
flags.BoolVar(&cfgForce, "force", false, "")
|
||||||
|
flags.BoolVar(&cfgParallel, "parallel", true, "")
|
||||||
|
if err := flags.Parse(args); err != nil {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError)
|
args = flags.Args()
|
||||||
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()
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
cmdFlags.Usage()
|
flags.Usage()
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := buildOptions.Validate(); err != nil {
|
// Parse the template
|
||||||
env.Ui().Error(err.Error())
|
tpl, err := template.ParseFile(args[0])
|
||||||
env.Ui().Error("")
|
|
||||||
env.Ui().Error(c.Help())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
userVars, err := buildOptions.AllUserVars()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
|
||||||
env.Ui().Error("")
|
|
||||||
env.Ui().Error(c.Help())
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the file into a byte array so that we can parse the template
|
// Get the core
|
||||||
log.Printf("Reading template: %s", args[0])
|
core, err := c.Meta.Core(tpl)
|
||||||
tpl, err := packer.ParseTemplateFile(args[0], userVars)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err))
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// The component finder for our builds
|
// Get the builds we care about
|
||||||
components := &packer.ComponentFinder{
|
buildNames := c.Meta.BuildNames(core)
|
||||||
Builder: env.Builder,
|
builds := make([]packer.Build, 0, len(buildNames))
|
||||||
Hook: env.Hook,
|
for _, n := range buildNames {
|
||||||
PostProcessor: env.PostProcessor,
|
b, err := core.Build(n)
|
||||||
Provisioner: env.Provisioner,
|
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 = append(builds, b)
|
||||||
builds, err := buildOptions.Builds(tpl, components)
|
|
||||||
if err != nil {
|
|
||||||
env.Ui().Error(err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfgDebug {
|
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
|
// Compile all the UIs for the builds
|
||||||
@ -95,24 +76,23 @@ func (c BuildCommand) Run(args []string) int {
|
|||||||
packer.UiColorYellow,
|
packer.UiColorYellow,
|
||||||
packer.UiColorBlue,
|
packer.UiColorBlue,
|
||||||
}
|
}
|
||||||
|
|
||||||
buildUis := make(map[string]packer.Ui)
|
buildUis := make(map[string]packer.Ui)
|
||||||
for i, b := range builds {
|
for i, b := range buildNames {
|
||||||
var ui packer.Ui
|
var ui packer.Ui
|
||||||
ui = env.Ui()
|
ui = c.Ui
|
||||||
if cfgColor {
|
if cfgColor {
|
||||||
ui = &packer.ColoredUi{
|
ui = &packer.ColoredUi{
|
||||||
Color: colors[i%len(colors)],
|
Color: colors[i%len(colors)],
|
||||||
Ui: env.Ui(),
|
Ui: ui,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildUis[b.Name()] = ui
|
buildUis[b] = ui
|
||||||
ui.Say(fmt.Sprintf("%s output will be in this color.", b.Name()))
|
ui.Say(fmt.Sprintf("%s output will be in this color.", b))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a newline between the color output and the actual output
|
// 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("Build debug mode: %v", cfgDebug)
|
||||||
log.Printf("Force build: %v", cfgForce)
|
log.Printf("Force build: %v", cfgForce)
|
||||||
@ -125,7 +105,7 @@ func (c BuildCommand) Run(args []string) int {
|
|||||||
|
|
||||||
warnings, err := b.Prepare()
|
warnings, err := b.Prepare()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Ui().Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if len(warnings) > 0 {
|
if len(warnings) > 0 {
|
||||||
@ -169,7 +149,7 @@ func (c BuildCommand) Run(args []string) int {
|
|||||||
name := b.Name()
|
name := b.Name()
|
||||||
log.Printf("Starting build run: %s", name)
|
log.Printf("Starting build run: %s", name)
|
||||||
ui := buildUis[name]
|
ui := buildUis[name]
|
||||||
runArtifacts, err := b.Run(ui, env.Cache())
|
runArtifacts, err := b.Run(ui, c.CoreConfig.Cache)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err))
|
ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err))
|
||||||
@ -205,34 +185,34 @@ func (c BuildCommand) Run(args []string) int {
|
|||||||
interruptWg.Wait()
|
interruptWg.Wait()
|
||||||
|
|
||||||
if interrupted {
|
if interrupted {
|
||||||
env.Ui().Say("Cleanly cancelled builds after being interrupted.")
|
c.Ui.Say("Cleanly cancelled builds after being interrupted.")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errors) > 0 {
|
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 {
|
for name, err := range errors {
|
||||||
// Create a UI for the machine readable stuff to be targetted
|
// Create a UI for the machine readable stuff to be targetted
|
||||||
ui := &packer.TargettedUi{
|
ui := &packer.TargettedUi{
|
||||||
Target: name,
|
Target: name,
|
||||||
Ui: env.Ui(),
|
Ui: c.Ui,
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.Machine("error", err.Error())
|
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 {
|
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 {
|
for name, buildArtifacts := range artifacts {
|
||||||
// Create a UI for the machine readable stuff to be targetted
|
// Create a UI for the machine readable stuff to be targetted
|
||||||
ui := &packer.TargettedUi{
|
ui := &packer.TargettedUi{
|
||||||
Target: name,
|
Target: name,
|
||||||
Ui: env.Ui(),
|
Ui: c.Ui,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Machine-readable helpful
|
// Machine-readable helpful
|
||||||
@ -267,11 +247,11 @@ func (c BuildCommand) Run(args []string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ui.Machine("artifact", iStr, "end")
|
ui.Machine("artifact", iStr, "end")
|
||||||
env.Ui().Say(message.String())
|
c.Ui.Say(message.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
if len(errors) > 0 {
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
const fixturesDir = "./test-fixtures"
|
const fixturesDir = "./test-fixtures"
|
||||||
|
|
||||||
func fatalCommand(t *testing.T, m Meta) {
|
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(
|
t.Fatalf(
|
||||||
"Bad exit code.\n\nStdout:\n\n%s\n\nStderr:\n\n%s",
|
"Bad exit code.\n\nStdout:\n\n%s\n\nStderr:\n\n%s",
|
||||||
ui.OutputWriter.String(),
|
out.String(),
|
||||||
ui.ErrorWriter.String())
|
err.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFixture(n string) string {
|
func testFixture(n string) string {
|
||||||
@ -22,7 +25,12 @@ func testFixture(n string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testMeta(t *testing.T) Meta {
|
func testMeta(t *testing.T) Meta {
|
||||||
|
var out, err bytes.Buffer
|
||||||
|
|
||||||
return Meta{
|
return Meta{
|
||||||
Ui: new(cli.MockUi),
|
Ui: &packer.BasicUi{
|
||||||
|
Writer: &out,
|
||||||
|
ErrorWriter: &err,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package command
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@ -17,28 +16,22 @@ type FixCommand struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *FixCommand) Run(args []string) int {
|
func (c *FixCommand) Run(args []string) int {
|
||||||
env, err := c.Meta.Environment()
|
flags := c.Meta.FlagSet("fix", FlagSetNone)
|
||||||
if err != nil {
|
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
||||||
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
|
if err := flags.Parse(args); err != nil {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdFlags := flag.NewFlagSet("fix", flag.ContinueOnError)
|
args = flags.Args()
|
||||||
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
|
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
args = cmdFlags.Args()
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
cmdFlags.Usage()
|
flags.Usage()
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the file for decoding
|
// Read the file for decoding
|
||||||
tplF, err := os.Open(args[0])
|
tplF, err := os.Open(args[0])
|
||||||
if err != nil {
|
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
|
return 1
|
||||||
}
|
}
|
||||||
defer tplF.Close()
|
defer tplF.Close()
|
||||||
@ -47,7 +40,7 @@ func (c *FixCommand) Run(args []string) int {
|
|||||||
var templateData map[string]interface{}
|
var templateData map[string]interface{}
|
||||||
decoder := json.NewDecoder(tplF)
|
decoder := json.NewDecoder(tplF)
|
||||||
if err := decoder.Decode(&templateData); err != nil {
|
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
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +58,7 @@ func (c *FixCommand) Run(args []string) int {
|
|||||||
log.Printf("Running fixer: %s", name)
|
log.Printf("Running fixer: %s", name)
|
||||||
input, err = fixer.Fix(input)
|
input, err = fixer.Fix(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Ui().Error(fmt.Sprintf("Error fixing: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error fixing: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,20 +66,20 @@ func (c *FixCommand) Run(args []string) int {
|
|||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
encoder := json.NewEncoder(&output)
|
encoder := json.NewEncoder(&output)
|
||||||
if err := encoder.Encode(input); err != nil {
|
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
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
var indented bytes.Buffer
|
var indented bytes.Buffer
|
||||||
if err := json.Indent(&indented, output.Bytes(), "", " "); err != nil {
|
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
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
result := indented.String()
|
result := indented.String()
|
||||||
result = strings.Replace(result, `\u003c`, "<", -1)
|
result = strings.Replace(result, `\u003c`, "<", -1)
|
||||||
result = strings.Replace(result, `\u003e`, ">", -1)
|
result = strings.Replace(result, `\u003e`, ">", -1)
|
||||||
env.Ui().Say(result)
|
c.Ui.Say(result)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,27 +1,20 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/packer/packer"
|
|
||||||
"log"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InspectCommand struct{
|
type InspectCommand struct {
|
||||||
Meta
|
Meta
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *InspectCommand) Run(args []string) int {
|
func (c *InspectCommand) Run(args []string) int {
|
||||||
env, err := c.Meta.Environment()
|
flags := c.Meta.FlagSet("inspect", FlagSetNone)
|
||||||
if err != nil {
|
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
||||||
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()) }
|
|
||||||
if err := flags.Parse(args); err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@ -32,16 +25,15 @@ func (c *InspectCommand) Run(args []string) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the file into a byte array so that we can parse the template
|
// Parse the template
|
||||||
log.Printf("Reading template: %#v", args[0])
|
tpl, err := template.ParseFile(args[0])
|
||||||
tpl, err := packer.ParseTemplateFile(args[0], nil)
|
|
||||||
if err != nil {
|
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
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience...
|
// Convenience...
|
||||||
ui := env.Ui()
|
ui := c.Ui
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
if tpl.Description != "" {
|
if tpl.Description != "" {
|
||||||
|
143
command/meta.go
143
command/meta.go
@ -1,15 +1,148 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
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/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 {
|
type Meta struct {
|
||||||
EnvConfig *packer.EnvironmentConfig
|
CoreConfig *packer.CoreConfig
|
||||||
Ui cli.Ui
|
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) {
|
// Core returns the core for the given template given the configured
|
||||||
return packer.NewEnvironment(m.EnvConfig)
|
// 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/archive"
|
||||||
"github.com/hashicorp/atlas-go/v1"
|
"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.
|
// 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."))
|
"longer used. It will be removed in the next version."))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the template
|
// Parse the template
|
||||||
tpl, err := packer.ParseTemplateFile(args[0], nil)
|
tpl, err := template.ParseFile(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate some things
|
// Validate some things
|
||||||
if tpl.Push.Name == "" {
|
if tpl.Push == nil || tpl.Push.Name == "" {
|
||||||
c.Ui.Error(fmt.Sprintf(
|
c.Ui.Error(fmt.Sprintf(
|
||||||
"The 'push' section must be specified in the template with\n" +
|
"The 'push' section must be specified in the template with\n" +
|
||||||
"at least the 'name' option set."))
|
"at least the 'name' option set."))
|
||||||
@ -131,7 +131,7 @@ func (c *PushCommand) Run(args []string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find the Atlas post-processors, if possible
|
// Find the Atlas post-processors, if possible
|
||||||
var atlasPPs []packer.RawPostProcessorConfig
|
var atlasPPs []*template.PostProcessor
|
||||||
for _, list := range tpl.PostProcessors {
|
for _, list := range tpl.PostProcessors {
|
||||||
for _, pp := range list {
|
for _, pp := range list {
|
||||||
if pp.Type == "atlas" {
|
if pp.Type == "atlas" {
|
||||||
@ -221,7 +221,7 @@ func (c *PushCommand) Run(args []string) int {
|
|||||||
return 1
|
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
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
cmdcommon "github.com/mitchellh/packer/common/command"
|
|
||||||
"github.com/mitchellh/packer/packer"
|
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"github.com/mitchellh/packer/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ValidateCommand struct {
|
type ValidateCommand struct {
|
||||||
@ -15,72 +15,54 @@ type ValidateCommand struct {
|
|||||||
|
|
||||||
func (c *ValidateCommand) Run(args []string) int {
|
func (c *ValidateCommand) Run(args []string) int {
|
||||||
var cfgSyntaxOnly bool
|
var cfgSyntaxOnly bool
|
||||||
buildOptions := new(cmdcommon.BuildOptions)
|
flags := c.Meta.FlagSet("validate", FlagSetBuildFilter|FlagSetVars)
|
||||||
|
flags.Usage = func() { c.Ui.Say(c.Help()) }
|
||||||
env, err := c.Meta.Environment()
|
flags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only")
|
||||||
if err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdFlags := flag.NewFlagSet("validate", flag.ContinueOnError)
|
args = flags.Args()
|
||||||
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()
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
cmdFlags.Usage()
|
flags.Usage()
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := buildOptions.Validate(); err != nil {
|
// Parse the template
|
||||||
env.Ui().Error(err.Error())
|
tpl, err := template.ParseFile(args[0])
|
||||||
env.Ui().Error("")
|
|
||||||
env.Ui().Error(c.Help())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
userVars, err := buildOptions.AllUserVars()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to parse template: %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))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're only checking syntax, then we're done already
|
||||||
if cfgSyntaxOnly {
|
if cfgSyntaxOnly {
|
||||||
env.Ui().Say("Syntax-only check passed. Everything looks okay.")
|
c.Ui.Say("Syntax-only check passed. Everything looks okay.")
|
||||||
return 0
|
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)
|
errs := make([]error, 0)
|
||||||
warnings := make(map[string][]string)
|
warnings := make(map[string][]string)
|
||||||
|
|
||||||
// The component finder for our builds
|
// Get the builds we care about
|
||||||
components := &packer.ComponentFinder{
|
buildNames := c.Meta.BuildNames(core)
|
||||||
Builder: env.Builder,
|
builds := make([]packer.Build, 0, len(buildNames))
|
||||||
Hook: env.Hook,
|
for _, n := range buildNames {
|
||||||
PostProcessor: env.PostProcessor,
|
b, err := core.Build(n)
|
||||||
Provisioner: env.Provisioner,
|
if err != nil {
|
||||||
}
|
c.Ui.Error(fmt.Sprintf(
|
||||||
|
"Failed to initialize build '%s': %s",
|
||||||
|
n, err))
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, get all the builds
|
builds = append(builds, b)
|
||||||
builds, err := buildOptions.Builds(tpl, components)
|
|
||||||
if err != nil {
|
|
||||||
env.Ui().Error(err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the configuration of all builds
|
// Check the configuration of all builds
|
||||||
@ -96,12 +78,12 @@ func (c *ValidateCommand) Run(args []string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) > 0 {
|
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 {
|
for i, err := range errs {
|
||||||
env.Ui().Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
|
|
||||||
if (i + 1) < len(errs) {
|
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 {
|
if len(warnings) > 0 {
|
||||||
env.Ui().Say("Template validation succeeded, but there were some warnings.")
|
c.Ui.Say("Template validation succeeded, but there were some warnings.")
|
||||||
env.Ui().Say("These are ONLY WARNINGS, and Packer will attempt to build the")
|
c.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 despite them, but they should be paid attention to.\n")
|
||||||
|
|
||||||
for build, warns := range warnings {
|
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 {
|
for _, warning := range warns {
|
||||||
env.Ui().Say(fmt.Sprintf("* %s", warning))
|
c.Ui.Say(fmt.Sprintf("* %s", warning))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
env.Ui().Say("Template validated successfully.")
|
c.Ui.Say("Template validated successfully.")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,15 +33,9 @@ func (c *VersionCommand) Help() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *VersionCommand) Run(args []string) int {
|
func (c *VersionCommand) Run(args []string) int {
|
||||||
env, err := c.Meta.Environment()
|
c.Ui.Machine("version", c.Version)
|
||||||
if err != nil {
|
c.Ui.Machine("version-prelease", c.VersionPrerelease)
|
||||||
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
|
c.Ui.Machine("version-commit", c.Revision)
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
env.Ui().Machine("version", c.Version)
|
|
||||||
env.Ui().Machine("version-prelease", c.VersionPrerelease)
|
|
||||||
env.Ui().Machine("version-commit", c.Revision)
|
|
||||||
|
|
||||||
var versionString bytes.Buffer
|
var versionString bytes.Buffer
|
||||||
fmt.Fprintf(&versionString, "Packer v%s", c.Version)
|
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
|
// If we have a version check function, then let's check for
|
||||||
// the latest version as well.
|
// the latest version as well.
|
||||||
if c.CheckFunc != nil {
|
if c.CheckFunc != nil {
|
||||||
// Separate the prior output with a newline
|
// Separate the prior output with a newline
|
||||||
c.Ui.Output("")
|
c.Ui.Say("")
|
||||||
|
|
||||||
// Check the latest version
|
// Check the latest version
|
||||||
info, err := c.CheckFunc()
|
info, err := c.CheckFunc()
|
||||||
@ -68,7 +62,7 @@ func (c *VersionCommand) Run(args []string) int {
|
|||||||
"Error checking latest version: %s", err))
|
"Error checking latest version: %s", err))
|
||||||
}
|
}
|
||||||
if info.Outdated {
|
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"+
|
"Your version of Packer is out of date! The latest version\n"+
|
||||||
"is %s. You can update by downloading from www.packer.io",
|
"is %s. You can update by downloading from www.packer.io",
|
||||||
info.Latest))
|
info.Latest))
|
||||||
|
17
commands.go
17
commands.go
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/mitchellh/packer/command"
|
"github.com/mitchellh/packer/command"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commands is the mapping of all the available Terraform commands.
|
// Commands is the mapping of all the available Terraform commands.
|
||||||
@ -18,17 +19,13 @@ const ErrorPrefix = "e:"
|
|||||||
const OutputPrefix = "o:"
|
const OutputPrefix = "o:"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Ui = &cli.PrefixedUi{
|
|
||||||
AskPrefix: OutputPrefix,
|
|
||||||
OutputPrefix: OutputPrefix,
|
|
||||||
InfoPrefix: OutputPrefix,
|
|
||||||
ErrorPrefix: ErrorPrefix,
|
|
||||||
Ui: &cli.BasicUi{Writer: os.Stdout},
|
|
||||||
}
|
|
||||||
|
|
||||||
meta := command.Meta{
|
meta := command.Meta{
|
||||||
EnvConfig: &EnvConfig,
|
CoreConfig: &CoreConfig,
|
||||||
Ui: Ui,
|
Ui: &packer.BasicUi{
|
||||||
|
Reader: os.Stdin,
|
||||||
|
Writer: os.Stdout,
|
||||||
|
ErrorWriter: os.Stdout,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands = map[string]cli.CommandFactory{
|
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"
|
"github.com/mitchellh/packer/packer/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnvConfig is the global EnvironmentConfig we use to initialize the CLI.
|
// CoreConfig is the global CoreConfig we use to initialize the CLI.
|
||||||
var EnvConfig packer.EnvironmentConfig
|
var CoreConfig packer.CoreConfig
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
DisableCheckpoint bool `json:"disable_checkpoint"`
|
DisableCheckpoint bool `json:"disable_checkpoint"`
|
||||||
|
29
helper/flag-kv/flag.go
Normal file
29
helper/flag-kv/flag.go
Normal file
@ -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
|
||||||
|
}
|
34
helper/flag-kv/flag_json.go
Normal file
34
helper/flag-kv/flag_json.go
Normal file
@ -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
|
||||||
|
}
|
59
helper/flag-kv/flag_json_test.go
Normal file
59
helper/flag-kv/flag_json_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
helper/flag-kv/flag_test.go
Normal file
56
helper/flag-kv/flag_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
helper/flag-kv/test-fixtures/basic.json
Normal file
3
helper/flag-kv/test-fixtures/basic.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"key": "value"
|
||||||
|
}
|
16
helper/flag-slice/flag.go
Normal file
16
helper/flag-slice/flag.go
Normal file
@ -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
|
||||||
|
}
|
33
helper/flag-slice/flag_test.go
Normal file
33
helper/flag-slice/flag_test.go
Normal file
@ -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()
|
defer plugin.CleanupClients()
|
||||||
|
|
||||||
// Create the environment configuration
|
// Create the environment configuration
|
||||||
EnvConfig = *packer.DefaultEnvironmentConfig()
|
CoreConfig.Cache = cache
|
||||||
EnvConfig.Cache = cache
|
CoreConfig.Components.Builder = config.LoadBuilder
|
||||||
EnvConfig.Components.Builder = config.LoadBuilder
|
CoreConfig.Components.Hook = config.LoadHook
|
||||||
EnvConfig.Components.Hook = config.LoadHook
|
CoreConfig.Components.PostProcessor = config.LoadPostProcessor
|
||||||
EnvConfig.Components.PostProcessor = config.LoadPostProcessor
|
CoreConfig.Components.Provisioner = config.LoadProvisioner
|
||||||
EnvConfig.Components.Provisioner = config.LoadProvisioner
|
|
||||||
if machineReadable {
|
if machineReadable {
|
||||||
EnvConfig.Ui = &packer.MachineReadableUi{
|
CoreConfig.Ui = &packer.MachineReadableUi{
|
||||||
Writer: os.Stdout,
|
Writer: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ func testBuild() *coreBuild {
|
|||||||
},
|
},
|
||||||
postProcessors: [][]coreBuildPostProcessor{
|
postProcessors: [][]coreBuildPostProcessor{
|
||||||
[]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),
|
variables: make(map[string]string),
|
||||||
@ -66,12 +66,12 @@ func TestBuild_Prepare(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
corePP := build.postProcessors[0][0]
|
corePP := build.postProcessors[0][0]
|
||||||
pp := corePP.processor.(*TestPostProcessor)
|
pp := corePP.processor.(*MockPostProcessor)
|
||||||
if !pp.configCalled {
|
if !pp.ConfigureCalled {
|
||||||
t.Fatal("should be called")
|
t.Fatal("should be called")
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(pp.configVal, []interface{}{make(map[string]interface{}), packerConfig}) {
|
if !reflect.DeepEqual(pp.ConfigureConfigs, []interface{}{make(map[string]interface{}), packerConfig}) {
|
||||||
t.Fatalf("bad: %#v", pp.configVal)
|
t.Fatalf("bad: %#v", pp.ConfigureConfigs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,8 +208,8 @@ func TestBuild_Run(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify post-processor was run
|
// Verify post-processor was run
|
||||||
pp := build.postProcessors[0][0].processor.(*TestPostProcessor)
|
pp := build.postProcessors[0][0].processor.(*MockPostProcessor)
|
||||||
if !pp.ppCalled {
|
if !pp.PostProcessCalled {
|
||||||
t.Fatal("should be called")
|
t.Fatal("should be called")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -244,7 +244,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
|
|||||||
build = testBuild()
|
build = testBuild()
|
||||||
build.postProcessors = [][]coreBuildPostProcessor{
|
build.postProcessors = [][]coreBuildPostProcessor{
|
||||||
[]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 = testBuild()
|
||||||
build.postProcessors = [][]coreBuildPostProcessor{
|
build.postProcessors = [][]coreBuildPostProcessor{
|
||||||
[]coreBuildPostProcessor{
|
[]coreBuildPostProcessor{
|
||||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1"}, "pp", make(map[string]interface{}), false},
|
coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp1"}, "pp", make(map[string]interface{}), false},
|
||||||
},
|
},
|
||||||
[]coreBuildPostProcessor{
|
[]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 = testBuild()
|
||||||
build.postProcessors = [][]coreBuildPostProcessor{
|
build.postProcessors = [][]coreBuildPostProcessor{
|
||||||
[]coreBuildPostProcessor{
|
[]coreBuildPostProcessor{
|
||||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1a"}, "pp", make(map[string]interface{}), false},
|
coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp1a"}, "pp", make(map[string]interface{}), false},
|
||||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1b"}, "pp", make(map[string]interface{}), true},
|
coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp1b"}, "pp", make(map[string]interface{}), true},
|
||||||
},
|
},
|
||||||
[]coreBuildPostProcessor{
|
[]coreBuildPostProcessor{
|
||||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2a"}, "pp", make(map[string]interface{}), false},
|
coreBuildPostProcessor{&MockPostProcessor{ArtifactId: "pp2a"}, "pp", make(map[string]interface{}), false},
|
||||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2b"}, "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{
|
build.postProcessors = [][]coreBuildPostProcessor{
|
||||||
[]coreBuildPostProcessor{
|
[]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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h != nil {
|
||||||
|
if err := h.Run(HookProvision, ui, nil, nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &MockArtifact{
|
return &MockArtifact{
|
||||||
IdValue: tb.ArtifactId,
|
IdValue: tb.ArtifactId,
|
||||||
}, nil
|
}, nil
|
||||||
|
245
packer/core.go
Normal file
245
packer/core.go
Normal file
@ -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
|
||||||
|
}
|
378
packer/core_test.go
Normal file
378
packer/core_test.go
Normal file
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
11
packer/packer_test.go
Normal file
11
packer/packer_test.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package packer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const FixtureDir = "./test-fixtures"
|
||||||
|
|
||||||
|
func fixtureDir(n string) string {
|
||||||
|
return filepath.Join(FixtureDir, n)
|
||||||
|
}
|
33
packer/post_processor_mock.go
Normal file
33
packer/post_processor_mock.go
Normal file
@ -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 {
|
func (c *Client) Hook() packer.Hook {
|
||||||
return &hook{
|
return &hook{
|
||||||
client: c.client,
|
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"
|
DefaultCacheEndpoint = "Cache"
|
||||||
DefaultCommandEndpoint = "Command"
|
DefaultCommandEndpoint = "Command"
|
||||||
DefaultCommunicatorEndpoint = "Communicator"
|
DefaultCommunicatorEndpoint = "Communicator"
|
||||||
DefaultEnvironmentEndpoint = "Environment"
|
|
||||||
DefaultHookEndpoint = "Hook"
|
DefaultHookEndpoint = "Hook"
|
||||||
DefaultPostProcessorEndpoint = "PostProcessor"
|
DefaultPostProcessorEndpoint = "PostProcessor"
|
||||||
DefaultProvisionerEndpoint = "Provisioner"
|
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) {
|
func (s *Server) RegisterHook(h packer.Hook) {
|
||||||
s.server.RegisterName(DefaultHookEndpoint, &HookServer{
|
s.server.RegisterName(DefaultHookEndpoint, &HookServer{
|
||||||
hook: h,
|
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
6
packer/test-fixtures/build-basic-interpolated.json
Normal file
6
packer/test-fixtures/build-basic-interpolated.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"name": "{{upper `name`}}",
|
||||||
|
"type": "test"
|
||||||
|
}]
|
||||||
|
}
|
5
packer/test-fixtures/build-basic.json
Normal file
5
packer/test-fixtures/build-basic.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "test"
|
||||||
|
}]
|
||||||
|
}
|
5
packer/test-fixtures/build-names-basic.json
Normal file
5
packer/test-fixtures/build-names-basic.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"builders": [
|
||||||
|
{"type": "something"}
|
||||||
|
]
|
||||||
|
}
|
5
packer/test-fixtures/build-names-func.json
Normal file
5
packer/test-fixtures/build-names-func.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"builders": [
|
||||||
|
{"type": "{{upper `tubes`}}"}
|
||||||
|
]
|
||||||
|
}
|
7
packer/test-fixtures/build-pp.json
Normal file
7
packer/test-fixtures/build-pp.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "test"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"post-processors": ["test"]
|
||||||
|
}
|
14
packer/test-fixtures/build-prov-override.json
Normal file
14
packer/test-fixtures/build-prov-override.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "test"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "test",
|
||||||
|
"override": {
|
||||||
|
"test": {
|
||||||
|
"foo": "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
10
packer/test-fixtures/build-prov-skip-include.json
Normal file
10
packer/test-fixtures/build-prov-skip-include.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "test"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "test",
|
||||||
|
"only": ["test"]
|
||||||
|
}]
|
||||||
|
}
|
10
packer/test-fixtures/build-prov-skip.json
Normal file
10
packer/test-fixtures/build-prov-skip.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "test"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "test",
|
||||||
|
"only": ["foo"]
|
||||||
|
}]
|
||||||
|
}
|
9
packer/test-fixtures/build-prov.json
Normal file
9
packer/test-fixtures/build-prov.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "test"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "test"
|
||||||
|
}]
|
||||||
|
}
|
10
packer/test-fixtures/validate-dup-builder.json
Normal file
10
packer/test-fixtures/validate-dup-builder.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"builders": [
|
||||||
|
{"type": "foo"}
|
||||||
|
],
|
||||||
|
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "foo",
|
||||||
|
"only": ["bar"]
|
||||||
|
}]
|
||||||
|
}
|
9
packer/test-fixtures/validate-req-variable.json
Normal file
9
packer/test-fixtures/validate-req-variable.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"foo": null
|
||||||
|
},
|
||||||
|
|
||||||
|
"builders": [{
|
||||||
|
"type": "foo"
|
||||||
|
}]
|
||||||
|
}
|
93
packer/testing.go
Normal file
93
packer/testing.go
Normal file
@ -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.
|
// Prepares the signal handlers so that we handle interrupts properly.
|
||||||
// The signal handler exists in a goroutine.
|
// The signal handler exists in a goroutine.
|
||||||
func setupSignalHandlers(env packer.Environment) {
|
func setupSignalHandlers(ui packer.Ui) {
|
||||||
ch := make(chan os.Signal, 1)
|
ch := make(chan os.Signal, 1)
|
||||||
signal.Notify(ch, os.Interrupt)
|
signal.Notify(ch, os.Interrupt)
|
||||||
|
|
||||||
@ -20,13 +20,13 @@ func setupSignalHandlers(env packer.Environment) {
|
|||||||
<-ch
|
<-ch
|
||||||
log.Println("First interrupt. Ignoring to allow plugins to clean up.")
|
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.
|
// Second interrupt. Go down hard.
|
||||||
<-ch
|
<-ch
|
||||||
log.Println("Second interrupt. Exiting now.")
|
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
|
// Force kill all the plugins, but mark that we're killing them
|
||||||
// first so that we don't get panics everywhere.
|
// first so that we don't get panics everywhere.
|
||||||
|
110
template/interpolate/funcs.go
Normal file
110
template/interpolate/funcs.go
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
178
template/interpolate/funcs_test.go
Normal file
178
template/interpolate/funcs_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
template/interpolate/i.go
Normal file
54
template/interpolate/i.go
Normal file
@ -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)
|
||||||
|
}
|
32
template/interpolate/i_test.go
Normal file
32
template/interpolate/i_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
template/interpolate/parse.go
Normal file
42
template/interpolate/parse.go
Normal file
@ -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))
|
||||||
|
}
|
||||||
|
}
|
39
template/interpolate/parse_test.go
Normal file
39
template/interpolate/parse_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
314
template/parse.go
Normal file
314
template/parse.go
Normal file
@ -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)
|
||||||
|
}
|
300
template/parse_test.go
Normal file
300
template/parse_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
197
template/template.go
Normal file
197
template/template.go
Normal file
@ -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)
|
||||||
|
}
|
137
template/template_test.go
Normal file
137
template/template_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
template/test-fixtures/parse-basic.json
Normal file
3
template/test-fixtures/parse-basic.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"builders": [{"type": "something"}]
|
||||||
|
}
|
3
template/test-fixtures/parse-builder-no-type.json
Normal file
3
template/test-fixtures/parse-builder-no-type.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"builders": [{"foo": "something"}]
|
||||||
|
}
|
6
template/test-fixtures/parse-builder-repeat.json
Normal file
6
template/test-fixtures/parse-builder-repeat.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"builders": [
|
||||||
|
{"type": "something"},
|
||||||
|
{"type": "something"}
|
||||||
|
]
|
||||||
|
}
|
1
template/test-fixtures/parse-contents.json
Normal file
1
template/test-fixtures/parse-contents.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"builders":[{"type":"test"}]}
|
3
template/test-fixtures/parse-description.json
Normal file
3
template/test-fixtures/parse-description.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"description": "foo"
|
||||||
|
}
|
3
template/test-fixtures/parse-min-version.json
Normal file
3
template/test-fixtures/parse-min-version.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"min_packer_version": "1.2"
|
||||||
|
}
|
6
template/test-fixtures/parse-pp-basic.json
Normal file
6
template/test-fixtures/parse-pp-basic.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"post-processors": [{
|
||||||
|
"type": "foo",
|
||||||
|
"foo": "bar"
|
||||||
|
}]
|
||||||
|
}
|
6
template/test-fixtures/parse-pp-keep.json
Normal file
6
template/test-fixtures/parse-pp-keep.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"post-processors": [{
|
||||||
|
"type": "foo",
|
||||||
|
"keep_input_artifact": true
|
||||||
|
}]
|
||||||
|
}
|
5
template/test-fixtures/parse-pp-map.json
Normal file
5
template/test-fixtures/parse-pp-map.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"post-processors": [{
|
||||||
|
"type": "foo"
|
||||||
|
}]
|
||||||
|
}
|
5
template/test-fixtures/parse-pp-multi.json
Normal file
5
template/test-fixtures/parse-pp-multi.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"post-processors": [[{
|
||||||
|
"type": "foo"
|
||||||
|
}, "bar"]]
|
||||||
|
}
|
5
template/test-fixtures/parse-pp-no-type.json
Normal file
5
template/test-fixtures/parse-pp-no-type.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"post-processors": [{
|
||||||
|
"keep_input_artifact": true
|
||||||
|
}]
|
||||||
|
}
|
5
template/test-fixtures/parse-pp-slice.json
Normal file
5
template/test-fixtures/parse-pp-slice.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"post-processors": [{
|
||||||
|
"type": "foo"
|
||||||
|
}, "bar"]
|
||||||
|
}
|
3
template/test-fixtures/parse-pp-string.json
Normal file
3
template/test-fixtures/parse-pp-string.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"post-processors": ["foo"]
|
||||||
|
}
|
5
template/test-fixtures/parse-provisioner-basic.json
Normal file
5
template/test-fixtures/parse-provisioner-basic.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"provisioners": [
|
||||||
|
{"type": "something"}
|
||||||
|
]
|
||||||
|
}
|
8
template/test-fixtures/parse-provisioner-except.json
Normal file
8
template/test-fixtures/parse-provisioner-except.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"provisioners": [
|
||||||
|
{
|
||||||
|
"type": "something",
|
||||||
|
"except": ["foo"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
template/test-fixtures/parse-provisioner-no-type.json
Normal file
5
template/test-fixtures/parse-provisioner-no-type.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"provisioners": [
|
||||||
|
{"foo": "something"}
|
||||||
|
]
|
||||||
|
}
|
8
template/test-fixtures/parse-provisioner-only.json
Normal file
8
template/test-fixtures/parse-provisioner-only.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"provisioners": [
|
||||||
|
{
|
||||||
|
"type": "something",
|
||||||
|
"only": ["foo"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
10
template/test-fixtures/parse-provisioner-override.json
Normal file
10
template/test-fixtures/parse-provisioner-override.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"provisioners": [
|
||||||
|
{
|
||||||
|
"type": "something",
|
||||||
|
"override": {
|
||||||
|
"foo": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"provisioners": [
|
||||||
|
{
|
||||||
|
"type": "something",
|
||||||
|
"pause_before": "1s"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
template/test-fixtures/parse-push.json
Normal file
5
template/test-fixtures/parse-push.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"push": {
|
||||||
|
"name": "foo"
|
||||||
|
}
|
||||||
|
}
|
5
template/test-fixtures/parse-variable-default.json
Normal file
5
template/test-fixtures/parse-variable-default.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"foo": "foo"
|
||||||
|
}
|
||||||
|
}
|
5
template/test-fixtures/parse-variable-required.json
Normal file
5
template/test-fixtures/parse-variable-required.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"foo": null
|
||||||
|
}
|
||||||
|
}
|
12
template/test-fixtures/validate-bad-override.json
Normal file
12
template/test-fixtures/validate-bad-override.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "foo"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "bar",
|
||||||
|
"override": {
|
||||||
|
"bar": {}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
10
template/test-fixtures/validate-bad-pp-except.json
Normal file
10
template/test-fixtures/validate-bad-pp-except.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "foo"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"post-processors": [{
|
||||||
|
"type": "bar",
|
||||||
|
"except": ["bar"]
|
||||||
|
}]
|
||||||
|
}
|
10
template/test-fixtures/validate-bad-pp-only.json
Normal file
10
template/test-fixtures/validate-bad-pp-only.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "foo"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"post-processors": [{
|
||||||
|
"type": "bar",
|
||||||
|
"only": ["bar"]
|
||||||
|
}]
|
||||||
|
}
|
10
template/test-fixtures/validate-bad-prov-except.json
Normal file
10
template/test-fixtures/validate-bad-prov-except.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "foo"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "bar",
|
||||||
|
"except": ["bar"]
|
||||||
|
}]
|
||||||
|
}
|
10
template/test-fixtures/validate-bad-prov-only.json
Normal file
10
template/test-fixtures/validate-bad-prov-only.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "foo"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "bar",
|
||||||
|
"only": ["bar"]
|
||||||
|
}]
|
||||||
|
}
|
12
template/test-fixtures/validate-good-override.json
Normal file
12
template/test-fixtures/validate-good-override.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "foo"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "bar",
|
||||||
|
"override": {
|
||||||
|
"foo": {}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
10
template/test-fixtures/validate-good-pp-except.json
Normal file
10
template/test-fixtures/validate-good-pp-except.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "foo"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"post-processors": [{
|
||||||
|
"type": "bar",
|
||||||
|
"except": ["foo"]
|
||||||
|
}]
|
||||||
|
}
|
10
template/test-fixtures/validate-good-pp-only.json
Normal file
10
template/test-fixtures/validate-good-pp-only.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "foo"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"post-processors": [{
|
||||||
|
"type": "bar",
|
||||||
|
"only": ["foo"]
|
||||||
|
}]
|
||||||
|
}
|
10
template/test-fixtures/validate-good-prov-except.json
Normal file
10
template/test-fixtures/validate-good-prov-except.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "foo"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "bar",
|
||||||
|
"except": ["foo"]
|
||||||
|
}]
|
||||||
|
}
|
10
template/test-fixtures/validate-good-prov-only.json
Normal file
10
template/test-fixtures/validate-good-prov-only.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"builders": [{
|
||||||
|
"type": "foo"
|
||||||
|
}],
|
||||||
|
|
||||||
|
"provisioners": [{
|
||||||
|
"type": "bar",
|
||||||
|
"only": ["foo"]
|
||||||
|
}]
|
||||||
|
}
|
1
template/test-fixtures/validate-no-builders.json
Normal file
1
template/test-fixtures/validate-no-builders.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
Loading…
x
Reference in New Issue
Block a user