Compare commits

..

2 Commits

Author SHA1 Message Date
Mitchell Hashimoto 0b23c8addf v0.3.7 2013-09-09 14:48:42 -07:00
Mitchell Hashimoto aa658e982b provisioner/puppet-masterless: detailed exit codes 2013-09-09 14:46:25 -07:00
71 changed files with 392 additions and 1838 deletions
-61
View File
@@ -1,64 +1,3 @@
## 0.3.9 (October 2, 2013)
FEATURES:
* The Amazon chroot builder is now able to run without any `sudo` privileges
by using the "command_wrapper" configuration. [GH-430]
* Chef provisioner supports environments. [GH-483]
BUG FIXES:
* core: default user variable values don't need to be strings. [GH-456]
* builder/amazon-chroot: Fix errors with waitin for state change. [GH-459]
* builder/digitalocean: Use proper error message JSON key (DO API change).
* communicator/ssh: SCP uploads now work properly when directories
contain symlinks. [GH-449]
* provisioner/chef-solo: Data bags and roles path are now properly
populated when set. [GH-470]
* provisioner/shell: Windows line endings are actually properly changed
to Unix line endings. [GH-477]
## 0.3.8 (September 22, 2013)
FEATURES:
* core: You can now specify `only` and `except` configurations on any
provisioner or post-processor to specify a list of builds that they
are valid for. [GH-438]
* builders/virtualbox: Guest additions can be attached rather than uploaded,
easier to handle for Windows guests. [GH-405]
* provisioner/chef-solo: Ability to specify a custom Chef configuration
template.
* provisioner/chef-solo: Roles and data bags support. [GH-348]
IMPROVEMENTS:
* core: User variables can now be used for integer, boolean, etc.
values. [GH-418]
* core: Plugins made with incompatible versions will no longer load.
* builder/amazon/all: Interrupts work while waiting for AMI to be ready.
* provisioner/shell: Script line-endings are automatically converted to
Unix-style line-endings. Can be disabled by setting "binary" to "true".
[GH-277]
BUG FIXES:
* core: Set TCP KeepAlives on internally created RPC connections so that
they don't die. [GH-416]
* builder/amazon/all: While waiting for AMI, will detect "failed" state.
* builder/amazon/all: Waiting for state will detect if the resource (AMI,
instance, etc.) disappears from under it.
* builder/amazon/instance: Exclude only contents of /tmp, not /tmp
itself. [GH-437]
* builder/amazon/instance: Make AccessKey/SecretKey available to bundle
command even when they come from the environment. [GH-434]
* builder/virtualbox: F1-F12 and delete scancodes now work. [GH-425]
* post-processor/vagrant: Override configurations properly work. [GH-426]
* provisioner/puppet-masterless: Fix failure case when both facter vars
are used and prevent_sudo. [GH-415]
* provisioner/puppet-masterless: User variables now work properly in
manifest file and hiera path. [GH-448]
## 0.3.7 (September 9, 2013)
BACKWARDS INCOMPATIBILITIES:
+1 -3
View File
@@ -47,9 +47,7 @@ it raises the chances we can quickly merge or address your contributions.
If you have never worked with Go before, you will have to complete the
following steps in order to be able to compile and test Packer.
1. Install Go. On a Mac, you can `brew install go`. Make sure the Go
version is at least Go 1.1. Packer will not work with anything less than
Go 1.1.
1. Install Go. On a Mac, you can `brew install go`.
2. Set and export the `GOPATH` environment variable. For example, you can
add `export GOPATH=$HOME/Documents/golang` to your `.bash_profile`.
+3 -9
View File
@@ -2,22 +2,16 @@ NO_COLOR=\033[0m
OK_COLOR=\033[32;01m
ERROR_COLOR=\033[31;01m
WARN_COLOR=\033[33;01m
DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
all: deps
@mkdir -p bin/
@echo "$(OK_COLOR)==> Building$(NO_COLOR)"
@bash --norc -i ./scripts/build.sh
@./scripts/build.sh
deps:
@echo "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)"
@go get -d -v ./...
@echo $(DEPS) | xargs -n1 go get -d
updatedeps:
@echo "$(OK_COLOR)==> Updating all dependencies$(NO_COLOR)"
@go get -d -v -u ./...
@echo $(DEPS) | xargs -n1 go get -d -u
@go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs -n1 go get -d
clean:
@rm -rf bin/ local/ pkg/ src/ website/.sass-cache website/build
@@ -29,4 +23,4 @@ test: deps
@echo "$(OK_COLOR)==> Testing Packer...$(NO_COLOR)"
go test ./...
.PHONY: all clean deps format test updatedeps
.PHONY: all deps format test
+12 -17
View File
@@ -27,19 +27,16 @@ type Config struct {
awscommon.AMIConfig `mapstructure:",squash"`
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
CommandWrapper string `mapstructure:"command_wrapper"`
CopyFiles []string `mapstructure:"copy_files"`
DevicePath string `mapstructure:"device_path"`
MountCommand string `mapstructure:"mount_command"`
MountPath string `mapstructure:"mount_path"`
SourceAmi string `mapstructure:"source_ami"`
UnmountCommand string `mapstructure:"unmount_command"`
tpl *packer.ConfigTemplate
}
type wrappedCommandTemplate struct {
Command string
}
type Builder struct {
config Config
runner multistep.Runner
@@ -81,14 +78,18 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.CopyFiles = []string{"/etc/resolv.conf"}
}
if b.config.CommandWrapper == "" {
b.config.CommandWrapper = "{{.Command}}"
if b.config.MountCommand == "" {
b.config.MountCommand = "mount"
}
if b.config.MountPath == "" {
b.config.MountPath = "packer-amazon-chroot-volumes/{{.Device}}"
}
if b.config.UnmountCommand == "" {
b.config.UnmountCommand = "umount"
}
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
@@ -126,8 +127,10 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
templates := map[string]*string{
"device_path": &b.config.DevicePath,
"source_ami": &b.config.SourceAmi,
"device_path": &b.config.DevicePath,
"mount_command": &b.config.MountCommand,
"source_ami": &b.config.SourceAmi,
"unmount_command": &b.config.UnmountCommand,
}
for n, ptr := range templates {
@@ -164,20 +167,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn := ec2.New(auth, region)
wrappedCommand := func(command string) (string, error) {
return b.config.tpl.Process(
b.config.CommandWrapper, &wrappedCommandTemplate{
Command: command,
})
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
// Build the steps
steps := []multistep.Step{
-11
View File
@@ -82,14 +82,3 @@ func TestBuilderPrepare_SourceAmi(t *testing.T) {
t.Errorf("err: %s", err)
}
}
func TestBuilderPrepare_CommandWrapper(t *testing.T) {
b := &Builder{}
config := testConfig()
config["command_wrapper"] = "echo hi; {{.Command}}"
err := b.Prepare(config)
if err != nil {
t.Errorf("err: %s", err)
}
}
-15
View File
@@ -1,15 +0,0 @@
package chroot
import (
"os/exec"
)
// CommandWrapper is a type that given a command, will possibly modify that
// command in-flight. This might return an error.
type CommandWrapper func(string) (string, error)
// ShellCommand takes a command string and returns an *exec.Cmd to execute
// it within the context of a shell (/bin/sh).
func ShellCommand(command string) *exec.Cmd {
return exec.Command("/bin/sh", "-c", command)
}
+38 -24
View File
@@ -1,10 +1,8 @@
package chroot
import (
"fmt"
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
@@ -15,18 +13,16 @@ import (
// Communicator is a special communicator that works by executing
// commands locally but within a chroot.
type Communicator struct {
Chroot string
CmdWrapper CommandWrapper
Chroot string
}
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
command, err := c.CmdWrapper(
fmt.Sprintf("chroot %s %s", c.Chroot, cmd.Command))
chrootCmdPath, err := exec.LookPath("chroot")
if err != nil {
return err
}
localCmd := ShellCommand(command)
localCmd := exec.Command(chrootCmdPath, c.Chroot, "/bin/sh", "-c", cmd.Command)
localCmd.Stdin = cmd.Stdin
localCmd.Stdout = cmd.Stdout
localCmd.Stderr = cmd.Stderr
@@ -50,7 +46,7 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
}
log.Printf(
"Chroot execution exited with '%d': '%s'",
"Chroot executation ended with '%d': '%s'",
exitStatus, cmd.Command)
cmd.SetExited(exitStatus)
}()
@@ -61,31 +57,49 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
func (c *Communicator) Upload(dst string, r io.Reader) error {
dst = filepath.Join(c.Chroot, dst)
log.Printf("Uploading to chroot dir: %s", dst)
tf, err := ioutil.TempFile("", "packer-amazon-chroot")
if err != nil {
return fmt.Errorf("Error preparing shell script: %s", err)
}
defer os.Remove(tf.Name())
io.Copy(tf, r)
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp %s %s", tf.Name(), dst))
f, err := os.Create(dst)
if err != nil {
return err
}
defer f.Close()
return ShellCommand(cpCmd).Run()
if _, err := io.Copy(f, r); err != nil {
return err
}
return nil
}
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
// TODO: remove any file copied if it appears in `exclude`
chrootDest := filepath.Join(c.Chroot, dst)
log.Printf("Uploading directory '%s' to '%s'", src, chrootDest)
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp -R %s* %s", src, chrootDest))
if err != nil {
return err
walkFn := func(fullPath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
path, err := filepath.Rel(src, fullPath)
if err != nil {
return err
}
for _, e := range exclude {
if e == path {
log.Printf("Skipping excluded file: %s", path)
return nil
}
}
dstPath := filepath.Join(dst, path)
f, err := os.Open(fullPath)
if err != nil {
return err
}
defer f.Close()
return c.Upload(dstPath, f)
}
return ShellCommand(cpCmd).Run()
log.Printf("Uploading directory '%s' to '%s'", src, dst)
return filepath.Walk(src, walkFn)
}
func (c *Communicator) Download(src string, w io.Writer) error {
-1
View File
@@ -1 +0,0 @@
package chroot
-44
View File
@@ -1,44 +0,0 @@
package chroot
import (
"fmt"
"io/ioutil"
"os"
"testing"
)
func TestCopyFile(t *testing.T) {
first, err := ioutil.TempFile("", "copy_files_test")
if err != nil {
t.Fatalf("couldn't create temp file.")
}
defer os.Remove(first.Name())
newName := first.Name() + "-new"
payload := "copy_files_test.go payload"
if _, err = first.WriteString(payload); err != nil {
t.Fatalf("Couldn't write payload to first file.")
}
first.Sync()
cmd := ShellCommand(fmt.Sprintf("cp %s %s", first.Name(), newName))
if err := cmd.Run(); err != nil {
t.Fatalf("Couldn't copy file")
}
defer os.Remove(newName)
second, err := os.Open(newName)
if err != nil {
t.Fatalf("Couldn't open copied file.")
}
defer second.Close()
var copiedPayload = make([]byte, len(payload))
if _, err := second.Read(copiedPayload); err != nil {
t.Fatalf("Couldn't open copied file for reading.")
}
if string(copiedPayload) != payload {
t.Fatalf("payload not copied.")
}
}
+6 -7
View File
@@ -60,8 +60,7 @@ func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", errors.New("No attachments on volume.")
}
a := resp.Volumes[0].Attachments[0]
return a, a.Status, nil
return nil, resp.Volumes[0].Attachments[0].Status, nil
},
}
@@ -112,12 +111,12 @@ func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
return nil, "", err
}
v := resp.Volumes[0]
if len(v.Attachments) > 0 {
return v, v.Attachments[0].Status, nil
} else {
return v, "detached", nil
state := "detached"
if len(resp.Volumes[0].Attachments) > 0 {
state = resp.Volumes[0].Attachments[0].Status
}
return nil, state, nil
},
}
@@ -15,12 +15,10 @@ func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction
hook := state.Get("hook").(packer.Hook)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
// Create our communicator
comm := &Communicator{
Chroot: mountPath,
CmdWrapper: wrappedCommand,
Chroot: mountPath,
}
// Provision
+44 -26
View File
@@ -1,11 +1,12 @@
package chroot
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io"
"log"
"os"
"path/filepath"
)
@@ -22,8 +23,6 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
stderr := new(bytes.Buffer)
s.files = make([]string, 0, len(config.CopyFiles))
if len(config.CopyFiles) > 0 {
@@ -33,20 +32,8 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
chrootPath := filepath.Join(mountPath, path)
log.Printf("Copying '%s' to '%s'", path, chrootPath)
cmdText, err := wrappedCommand(fmt.Sprintf("cp %s %s", path, chrootPath))
if err != nil {
err := fmt.Errorf("Error building copy command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
stderr.Reset()
cmd := ShellCommand(cmdText)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"Error copying file: %s\nnStderr: %s", err, stderr.String())
if err := s.copySingle(chrootPath, path); err != nil {
err := fmt.Errorf("Error copying file: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
@@ -67,18 +54,11 @@ func (s *StepCopyFiles) Cleanup(state multistep.StateBag) {
}
}
func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error {
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
func (s *StepCopyFiles) CleanupFunc(multistep.StateBag) error {
if s.files != nil {
for _, file := range s.files {
log.Printf("Removing: %s", file)
localCmdText, err := wrappedCommand(fmt.Sprintf("rm -f %s", file))
if err != nil {
return err
}
localCmd := ShellCommand(localCmdText)
if err := localCmd.Run(); err != nil {
if err := os.Remove(file); err != nil {
return err
}
}
@@ -87,3 +67,41 @@ func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error {
s.files = nil
return nil
}
func (s *StepCopyFiles) copySingle(dst, src string) error {
// Stat the src file so we can copy the mode later
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
// Remove any existing destination file
if err := os.Remove(dst); err != nil {
return err
}
// Copy the files
srcF, err := os.Open(src)
if err != nil {
return err
}
defer srcF.Close()
dstF, err := os.Create(dst)
if err != nil {
return err
}
defer dstF.Close()
if _, err := io.Copy(dstF, srcF); err != nil {
return err
}
dstF.Close()
// Match the mode
if err := os.Chmod(dst, srcInfo.Mode()); err != nil {
return err
}
return nil
}
+1 -2
View File
@@ -75,8 +75,7 @@ func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", err
}
v := resp.Volumes[0]
return v, v.Status, nil
return nil, resp.Volumes[0].Status, nil
},
}
+6 -18
View File
@@ -7,6 +7,7 @@ import (
"github.com/mitchellh/packer/packer"
"log"
"os"
"os/exec"
"path/filepath"
)
@@ -27,7 +28,6 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
mountPath, err := config.tpl.Process(config.MountPath, &mountPathData{
Device: filepath.Base(device),
@@ -59,16 +59,8 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
ui.Say("Mounting the root device...")
stderr := new(bytes.Buffer)
mountCommand, err := wrappedCommand(
fmt.Sprintf("mount %s %s", device, mountPath))
if err != nil {
err := fmt.Errorf("Error creating mount command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
cmd := ShellCommand(mountCommand)
mountCommand := fmt.Sprintf("%s %s %s", config.MountCommand, device, mountPath)
cmd := exec.Command("/bin/sh", "-c", mountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
@@ -98,16 +90,12 @@ func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
return nil
}
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
ui.Say("Unmounting the root device...")
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", s.mountPath))
if err != nil {
return fmt.Errorf("Error creating unmount command: %s", err)
}
cmd := ShellCommand(unmountCommand)
unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, s.mountPath)
cmd := exec.Command("/bin/sh", "-c", unmountCommand)
if err := cmd.Run(); err != nil {
return fmt.Errorf("Error unmounting root device: %s", err)
}
+9 -18
View File
@@ -6,6 +6,7 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"os/exec"
)
// StepMountExtra mounts the attached device.
@@ -20,7 +21,6 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
s.mounts = make([]string, 0, len(config.ChrootMounts))
@@ -42,19 +42,13 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2]))
stderr := new(bytes.Buffer)
mountCommand, err := wrappedCommand(fmt.Sprintf(
"mount %s %s %s",
mountCommand := fmt.Sprintf(
"%s %s %s %s",
config.MountCommand,
flags,
mountInfo[1],
innerPath))
if err != nil {
err := fmt.Errorf("Error creating mount command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
cmd := ShellCommand(mountCommand)
innerPath)
cmd := exec.Command("/bin/sh", "-c", mountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
@@ -85,18 +79,15 @@ func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error {
return nil
}
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
config := state.Get("config").(*Config)
for len(s.mounts) > 0 {
var path string
lastIndex := len(s.mounts) - 1
path, s.mounts = s.mounts[lastIndex], s.mounts[:lastIndex]
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", path))
if err != nil {
return fmt.Errorf("Error creating unmount command: %s", err)
}
unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, path)
stderr := new(bytes.Buffer)
cmd := ShellCommand(unmountCommand)
cmd := exec.Command("/bin/sh", "-c", unmountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf(
+1 -9
View File
@@ -52,16 +52,8 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
state.Put("amis", amis)
// Wait for the image to become ready
stateChange := awscommon.StateChangeConf{
Conn: ec2conn,
Pending: []string{"pending"},
Target: "available",
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, registerResp.ImageId),
StepState: state,
}
ui.Say("Waiting for AMI to become ready...")
if _, err := awscommon.WaitForState(&stateChange); err != nil {
if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
+1 -2
View File
@@ -51,8 +51,7 @@ func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", errors.New("No snapshots found.")
}
s := resp.Snapshots[0]
return s, s.Status, nil
return nil, resp.Snapshots[0].Status, nil
},
}
+1 -8
View File
@@ -18,14 +18,7 @@ type AccessConfig struct {
// Auth returns a valid aws.Auth object for access to AWS services, or
// an error if the authentication couldn't be resolved.
func (c *AccessConfig) Auth() (aws.Auth, error) {
auth, err := aws.GetAuth(c.AccessKey, c.SecretKey)
if err == nil {
// Store the accesskey and secret that we got...
c.AccessKey = auth.AccessKey
c.SecretKey = auth.SecretKey
}
return auth, err
return aws.GetAuth(c.AccessKey, c.SecretKey)
}
// Region returns the aws.Region object for access to AWS services, requesting
+30
View File
@@ -0,0 +1,30 @@
package common
import (
"github.com/mitchellh/goamz/ec2"
"log"
"time"
)
// WaitForAMI waits for the given AMI ID to become ready.
func WaitForAMI(c *ec2.EC2, imageId string) error {
for {
imageResp, err := c.Images([]string{imageId}, ec2.NewFilter())
if err != nil {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
log.Println("AMI not found, probably state issues on AWS side. Trying again.")
continue
}
return err
}
if imageResp.Images[0].State == "available" {
return nil
}
log.Printf("Image in state %s, sleeping 2s before checking again",
imageResp.Images[0].State)
time.Sleep(2 * time.Second)
}
}
@@ -30,32 +30,6 @@ type StateChangeConf struct {
Target string
}
// AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an AMI for state changes.
func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.Images([]string{imageId}, ec2.NewFilter())
if err != nil {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
// Set this to nil as if we didn't find anything.
resp = nil
} else {
log.Printf("Error on AMIStateRefresh: %s", err)
return nil, "", err
}
}
if resp == nil || len(resp.Images) == 0 {
// Sometimes AWS has consistency issues and doesn't see the
// AMI. Return an empty state.
return nil, "", nil
}
i := resp.Images[0]
return i, i.State, nil
}
}
// InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an EC2 instance.
func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
@@ -82,8 +56,6 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
log.Printf("Waiting for state to become: %s", conf.Target)
notfoundTick := 0
for {
var currentState string
i, currentState, err = conf.Refresh()
@@ -91,17 +63,9 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
return
}
if i == nil {
// If we didn't find the resource, check if we have been
// not finding it for awhile, and if so, report an error.
notfoundTick += 1
if notfoundTick > 20 {
return nil, errors.New("couldn't find resource")
}
} else {
// Reset the counter for when a resource isn't found
notfoundTick = 0
// Check states only if we were able to refresh to an instance
// that exists.
if i != nil {
if currentState == conf.Target {
return
}
+2 -11
View File
@@ -40,17 +40,8 @@ func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt
}
stateChange := StateChangeConf{
Conn: regionconn,
Pending: []string{"pending"},
Target: "available",
Refresh: AMIStateRefreshFunc(regionconn, resp.ImageId),
StepState: state,
}
ui.Say(fmt.Sprintf("Waiting for AMI (%s) in region (%s) to become ready...",
resp.ImageId, region))
if _, err := WaitForState(&stateChange); err != nil {
ui.Say(fmt.Sprintf("Waiting for AMI (%s) in region (%s) to become ready...", resp.ImageId, region))
if err := WaitForAMI(regionconn, resp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s", resp.ImageId, region, err)
state.Put("error", err)
ui.Error(err.Error())
+1 -9
View File
@@ -39,16 +39,8 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
state.Put("amis", amis)
// Wait for the image to become ready
stateChange := awscommon.StateChangeConf{
Conn: ec2conn,
Pending: []string{"pending"},
Target: "available",
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, createResp.ImageId),
StepState: state,
}
ui.Say("Waiting for AMI to become ready...")
if _, err := awscommon.WaitForState(&stateChange); err != nil {
if err := awscommon.WaitForAMI(ec2conn, createResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
+1 -1
View File
@@ -83,7 +83,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
"-u {{.AccountId}} " +
"-c {{.CertPath}} " +
"-r {{.Architecture}} " +
"-e {{.PrivatePath}}/* " +
"-e {{.PrivatePath}} " +
"-d {{.Destination}} " +
"-p {{.Prefix}} " +
"--batch"
+1 -9
View File
@@ -37,16 +37,8 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
state.Put("amis", amis)
// Wait for the image to become ready
stateChange := awscommon.StateChangeConf{
Conn: ec2conn,
Pending: []string{"pending"},
Target: "available",
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, registerResp.ImageId),
StepState: state,
}
ui.Say("Waiting for AMI to become ready...")
if _, err := awscommon.WaitForState(&stateChange); err != nil {
if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
+1 -1
View File
@@ -227,7 +227,7 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin
}
if status == "ERROR" {
status = decodedResponse["message"].(string)
status = decodedResponse["error_message"].(string)
}
lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s",
+2 -3
View File
@@ -38,9 +38,8 @@ func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
state.Put("privateKey", string(pem.EncodeToMemory(&priv_blk)))
// Marshal the public key into SSH compatible format
// TODO properly handle the public key error
pub, _ := ssh.NewPublicKey(&priv.PublicKey)
pub_sshformat := string(ssh.MarshalAuthorizedKey(pub))
pub := priv.PublicKey
pub_sshformat := string(ssh.MarshalAuthorizedKey(&pub))
// The name of the public key on DO
name := fmt.Sprintf("packer-%s", hex.EncodeToString(identifier.NewUUID().Raw()))
-2
View File
@@ -28,7 +28,6 @@ type config struct {
DiskSize uint `mapstructure:"disk_size"`
FloppyFiles []string `mapstructure:"floppy_files"`
Format string `mapstructure:"format"`
GuestAdditionsAttach bool `mapstructure:"guest_additions_attach"`
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
@@ -362,7 +361,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
new(stepCreateVM),
new(stepCreateDisk),
new(stepAttachISO),
new(stepAttachGuestAdditions),
new(stepAttachFloppy),
new(stepForwardSSH),
new(stepVBoxManage),
@@ -1,81 +0,0 @@
package virtualbox
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
// This step attaches the VirtualBox guest additions as a inserted CD onto
// the virtual machine.
//
// Uses:
// config *config
// driver Driver
// guest_additions_path string
// ui packer.Ui
// vmName string
//
// Produces:
type stepAttachGuestAdditions struct {
attachedPath string
}
func (s *stepAttachGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
guestAdditionsPath := state.Get("guest_additions_path").(string)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
// If we're not attaching the guest additions then just return
if !config.GuestAdditionsAttach {
log.Println("Not attaching guest additions since we're uploading.")
return multistep.ActionContinue
}
// Attach the guest additions to the computer
log.Println("Attaching guest additions ISO onto IDE controller...")
command := []string{
"storageattach", vmName,
"--storagectl", "IDE Controller",
"--port", "1",
"--device", "0",
"--type", "dvddrive",
"--medium", guestAdditionsPath,
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error attaching guest additions: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Track the path so that we can unregister it from VirtualBox later
s.attachedPath = guestAdditionsPath
return multistep.ActionContinue
}
func (s *stepAttachGuestAdditions) Cleanup(state multistep.StateBag) {
if s.attachedPath == "" {
return
}
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
command := []string{
"storageattach", vmName,
"--storagectl", "IDE Controller",
"--port", "1",
"--device", "0",
"--medium", "none",
}
if err := driver.VBoxManage(command...); err != nil {
ui.Error(fmt.Sprintf("Error unregistering guest additions: %s", err))
}
}
+15 -20
View File
@@ -92,34 +92,29 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
func scancodes(message string) []string {
// Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
//
// Scancodes represent raw keyboard output and are fed to the VM by the
// VBoxManage controlvm keyboardputscancode program.
//
// Scancodes are recorded here in pairs. The first entry represents
// the key press and the second entry represents the key release and is
// derived from the first by the addition of 0x80.
special := make(map[string][]string)
special["<bs>"] = []string{"0e", "8e"}
special["<del>"] = []string{"53", "d3"}
special["<bs>"] = []string{"ff", "08"}
special["<del>"] = []string{"ff", "ff"}
special["<enter>"] = []string{"1c", "9c"}
special["<esc>"] = []string{"01", "81"}
special["<f1>"] = []string{"3b", "bb"}
special["<f2>"] = []string{"3c", "bc"}
special["<f3>"] = []string{"3d", "bd"}
special["<f4>"] = []string{"3e", "be"}
special["<f5>"] = []string{"3f", "bf"}
special["<f6>"] = []string{"40", "c0"}
special["<f7>"] = []string{"41", "c1"}
special["<f8>"] = []string{"42", "c2"}
special["<f9>"] = []string{"43", "c3"}
special["<f10>"] = []string{"44", "c4"}
special["<f1>"] = []string{"ff", "be"}
special["<f2>"] = []string{"ff", "bf"}
special["<f3>"] = []string{"ff", "c0"}
special["<f4>"] = []string{"ff", "c1"}
special["<f5>"] = []string{"ff", "c2"}
special["<f6>"] = []string{"ff", "c3"}
special["<f7>"] = []string{"ff", "c4"}
special["<f8>"] = []string{"ff", "c5"}
special["<f9>"] = []string{"ff", "c6"}
special["<f10>"] = []string{"ff", "c7"}
special["<f11>"] = []string{"ff", "c8"}
special["<f12>"] = []string{"ff", "c9"}
special["<return>"] = []string{"1c", "9c"}
special["<tab>"] = []string{"0f", "8f"}
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
// Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
scancodeIndex := make(map[string]uint)
scancodeIndex["1234567890-="] = 0x02
scancodeIndex["!@#$%^&*()_+"] = 0x02
@@ -4,7 +4,6 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"os"
)
@@ -22,12 +21,6 @@ func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepA
guestAdditionsPath := state.Get("guest_additions_path").(string)
ui := state.Get("ui").(packer.Ui)
// If we're attaching then don't do this, since we attached.
if config.GuestAdditionsAttach {
log.Println("Not uploading guest additions since we're attaching.")
return multistep.ActionContinue
}
version, err := driver.Version()
if err != nil {
state.Put("error", fmt.Errorf("Error reading version for guest additions upload: %s", err))
+2 -3
View File
@@ -41,9 +41,8 @@ func CheckUnusedConfig(md *mapstructure.Metadata) *packer.MultiError {
func DecodeConfig(target interface{}, raws ...interface{}) (*mapstructure.Metadata, error) {
var md mapstructure.Metadata
decoderConfig := &mapstructure.DecoderConfig{
Metadata: &md,
Result: target,
WeaklyTypedInput: true,
Metadata: &md,
Result: target,
}
decoder, err := mapstructure.NewDecoder(decoderConfig)
+2 -21
View File
@@ -408,27 +408,8 @@ func scpUploadDir(root string, fs []os.FileInfo, w io.Writer, r *bufio.Reader) e
for _, fi := range fs {
realPath := filepath.Join(root, fi.Name())
// Track if this is actually a symlink to a directory. If it is
// a symlink to a file we don't do any special behavior because uploading
// a file just works. If it is a directory, we need to know so we
// treat it as such.
isSymlinkToDir := false
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
symPath, err := filepath.EvalSymlinks(realPath)
if err != nil {
return err
}
symFi, err := os.Lstat(symPath)
if err != nil {
return err
}
isSymlinkToDir = symFi.IsDir()
}
if !fi.IsDir() && !isSymlinkToDir {
// It is a regular file (or symlink to a file), just upload it
if !fi.IsDir() {
// It is a regular file, just upload it
f, err := os.Open(realPath)
if err != nil {
return err
+3 -4
View File
@@ -1,7 +1,6 @@
package ssh
import (
"code.google.com/p/go.crypto/ssh"
"crypto"
"crypto/dsa"
"crypto/rsa"
@@ -54,15 +53,15 @@ func (k *SimpleKeychain) AddPEMKeyPassword(key string, password string) (err err
}
// Key method for ssh.ClientKeyring interface
func (k *SimpleKeychain) Key(i int) (ssh.PublicKey, error) {
func (k *SimpleKeychain) Key(i int) (interface{}, error) {
if i < 0 || i >= len(k.keys) {
return nil, nil
}
switch key := k.keys[i].(type) {
case *rsa.PrivateKey:
return ssh.NewPublicKey(&key.PublicKey)
return &key.PublicKey, nil
case *dsa.PrivateKey:
return ssh.NewPublicKey(&key.PublicKey)
return &key.PublicKey, nil
}
panic("unknown key type")
}
+4 -18
View File
@@ -317,24 +317,10 @@ func (c *Client) Start() (address string, err error) {
err = errors.New("timeout while waiting for plugin to start")
case <-exitCh:
err = errors.New("plugin exited before we could connect")
case lineBytes := <-linesCh:
// Trim the line and split by "|" in order to get the parts of
// the output.
line := strings.TrimSpace(string(lineBytes))
parts := strings.SplitN(line, "|", 2)
if len(parts) < 2 {
err = fmt.Errorf("Unrecognized remote plugin message: %s", line)
return
}
// Test the API version
if parts[0] != APIVersion {
err = fmt.Errorf("Incompatible API version with plugin. "+
"Plugin version: %s, Ours: %s", parts[0], APIVersion)
return
}
c.address = parts[1]
case line := <-linesCh:
// Trim the address and reset the err since we were able
// to read some sort of address.
c.address = strings.TrimSpace(string(line))
address = c.address
}
-15
View File
@@ -37,21 +37,6 @@ func TestClient(t *testing.T) {
}
}
func TestClientStart_badVersion(t *testing.T) {
config := &ClientConfig{
Cmd: helperProcess("bad-version"),
StartTimeout: 50 * time.Millisecond,
}
c := NewClient(config)
defer c.Kill()
_, err := c.Start()
if err == nil {
t.Fatal("err should not be nil")
}
}
func TestClient_Start_Timeout(t *testing.T) {
config := &ClientConfig{
Cmd: helperProcess("start-timeout"),
+1 -8
View File
@@ -30,16 +30,9 @@ var Interrupts int32 = 0
const MagicCookieKey = "PACKER_PLUGIN_MAGIC_COOKIE"
const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d6991ca9872b2"
// The APIVersion is outputted along with the RPC address. The plugin
// client validates this API version and will show an error if it doesn't
// know how to speak it.
const APIVersion = "1"
// This serves a single RPC connection on the given RPC server on
// a random port.
func serve(server *rpc.Server) (err error) {
log.Printf("Plugin build against Packer '%s'", packer.GitCommit)
if os.Getenv(MagicCookieKey) != MagicCookieValue {
return errors.New("Please do not execute plugins directly. Packer will execute these for you.")
}
@@ -82,7 +75,7 @@ func serve(server *rpc.Server) (err error) {
// Output the address to stdout
log.Printf("Plugin address: %s\n", address)
fmt.Printf("%s|%s\n", APIVersion, address)
fmt.Println(address)
os.Stdout.Sync()
// Accept a connection
+3 -6
View File
@@ -50,9 +50,6 @@ func TestHelperProcess(*testing.T) {
cmd, args := args[0], args[1:]
switch cmd {
case "bad-version":
fmt.Printf("%s1|:1234\n", APIVersion)
<-make(chan int)
case "builder":
ServeBuilder(new(helperBuilder))
case "command":
@@ -62,7 +59,7 @@ func TestHelperProcess(*testing.T) {
case "invalid-rpc-address":
fmt.Println("lolinvalid")
case "mock":
fmt.Printf("%s|:1234\n", APIVersion)
fmt.Println(":1234")
<-make(chan int)
case "post-processor":
ServePostProcessor(new(helperPostProcessor))
@@ -72,11 +69,11 @@ func TestHelperProcess(*testing.T) {
time.Sleep(1 * time.Minute)
os.Exit(1)
case "stderr":
fmt.Printf("%s|:1234\n", APIVersion)
fmt.Println(":1234")
log.Println("HELLO")
log.Println("WORLD")
case "stdin":
fmt.Printf("%s|:1234\n", APIVersion)
fmt.Println(":1234")
data := make([]byte, 5)
if _, err := os.Stdin.Read(data); err != nil {
log.Printf("stdin read error: %s", err)
+2 -2
View File
@@ -52,7 +52,7 @@ func (b *build) Run(ui packer.Ui, cache packer.Cache) ([]packer.Artifact, error)
artifacts := make([]packer.Artifact, len(result))
for i, addr := range result {
client, err := rpcDial(addr)
client, err := rpc.Dial("tcp", addr)
if err != nil {
return nil, err
}
@@ -92,7 +92,7 @@ func (b *BuildServer) Prepare(v map[string]string, reply *error) error {
}
func (b *BuildServer) Run(args *BuildRunArgs, reply *[]string) error {
client, err := rpcDial(args.UiRPCAddress)
client, err := rpc.Dial("tcp", args.UiRPCAddress)
if err != nil {
return err
}
+4 -3
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/mitchellh/packer/packer"
"log"
"net"
"net/rpc"
)
@@ -94,7 +95,7 @@ func (b *builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return nil, nil
}
client, err := rpcDial(response.RPCAddress)
client, err := rpc.Dial("tcp", response.RPCAddress)
if err != nil {
return nil, err
}
@@ -118,12 +119,12 @@ func (b *BuilderServer) Prepare(args *BuilderPrepareArgs, reply *error) error {
}
func (b *BuilderServer) Run(args *BuilderRunArgs, reply *interface{}) error {
client, err := rpcDial(args.RPCAddress)
client, err := rpc.Dial("tcp", args.RPCAddress)
if err != nil {
return err
}
responseC, err := tcpDial(args.ResponseAddress)
responseC, err := net.Dial("tcp", args.ResponseAddress)
if err != nil {
return err
}
+1 -1
View File
@@ -66,7 +66,7 @@ func (c *CommandServer) Help(args *interface{}, reply *string) error {
}
func (c *CommandServer) Run(args *CommandRunArgs, reply *int) error {
client, err := rpcDial(args.RPCAddress)
client, err := rpc.Dial("tcp", args.RPCAddress)
if err != nil {
return err
}
+6 -6
View File
@@ -177,7 +177,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
toClose := make([]net.Conn, 0)
if args.StdinAddress != "" {
stdinC, err := tcpDial(args.StdinAddress)
stdinC, err := net.Dial("tcp", args.StdinAddress)
if err != nil {
return err
}
@@ -187,7 +187,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
}
if args.StdoutAddress != "" {
stdoutC, err := tcpDial(args.StdoutAddress)
stdoutC, err := net.Dial("tcp", args.StdoutAddress)
if err != nil {
return err
}
@@ -197,7 +197,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
}
if args.StderrAddress != "" {
stderrC, err := tcpDial(args.StderrAddress)
stderrC, err := net.Dial("tcp", args.StderrAddress)
if err != nil {
return err
}
@@ -208,7 +208,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
// Connect to the response address so we can write our result to it
// when ready.
responseC, err := tcpDial(args.ResponseAddress)
responseC, err := net.Dial("tcp", args.ResponseAddress)
if err != nil {
return err
}
@@ -234,7 +234,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
}
func (c *CommunicatorServer) Upload(args *CommunicatorUploadArgs, reply *interface{}) (err error) {
readerC, err := tcpDial(args.ReaderAddress)
readerC, err := net.Dial("tcp", args.ReaderAddress)
if err != nil {
return
}
@@ -250,7 +250,7 @@ func (c *CommunicatorServer) UploadDir(args *CommunicatorUploadDirArgs, reply *e
}
func (c *CommunicatorServer) Download(args *CommunicatorDownloadArgs, reply *interface{}) (err error) {
writerC, err := tcpDial(args.WriterAddress)
writerC, err := net.Dial("tcp", args.WriterAddress)
if err != nil {
return
}
-33
View File
@@ -1,33 +0,0 @@
package rpc
import (
"net"
"net/rpc"
)
// rpcDial makes a TCP connection to a remote RPC server and returns
// the client. This will set the connection up properly so that keep-alives
// are set and so on and should be used to make all RPC connections within
// this package.
func rpcDial(address string) (*rpc.Client, error) {
tcpConn, err := tcpDial(address)
if err != nil {
return nil, err
}
// Create an RPC client around our connection
return rpc.NewClient(tcpConn), nil
}
// tcpDial connects via TCP to the designated address.
func tcpDial(address string) (*net.TCPConn, error) {
conn, err := net.Dial("tcp", address)
if err != nil {
return nil, err
}
// Set a keep-alive so that the connection stays alive even when idle
tcpConn := conn.(*net.TCPConn)
tcpConn.SetKeepAlive(true)
return tcpConn, nil
}
+6 -6
View File
@@ -28,7 +28,7 @@ func (e *Environment) Builder(name string) (b packer.Builder, err error) {
return
}
client, err := rpcDial(reply)
client, err := rpc.Dial("tcp", reply)
if err != nil {
return
}
@@ -43,7 +43,7 @@ func (e *Environment) Cache() packer.Cache {
panic(err)
}
client, err := rpcDial(reply)
client, err := rpc.Dial("tcp", reply)
if err != nil {
panic(err)
}
@@ -64,7 +64,7 @@ func (e *Environment) Hook(name string) (h packer.Hook, err error) {
return
}
client, err := rpcDial(reply)
client, err := rpc.Dial("tcp", reply)
if err != nil {
return
}
@@ -80,7 +80,7 @@ func (e *Environment) PostProcessor(name string) (p packer.PostProcessor, err er
return
}
client, err := rpcDial(reply)
client, err := rpc.Dial("tcp", reply)
if err != nil {
return
}
@@ -96,7 +96,7 @@ func (e *Environment) Provisioner(name string) (p packer.Provisioner, err error)
return
}
client, err := rpcDial(reply)
client, err := rpc.Dial("tcp", reply)
if err != nil {
return
}
@@ -109,7 +109,7 @@ func (e *Environment) Ui() packer.Ui {
var reply string
e.client.Call("Environment.Ui", new(interface{}), &reply)
client, err := rpcDial(reply)
client, err := rpc.Dial("tcp", reply)
if err != nil {
panic(err)
}
+1 -1
View File
@@ -46,7 +46,7 @@ func (h *hook) Cancel() {
}
func (h *HookServer) Run(args *HookRunArgs, reply *interface{}) error {
client, err := rpcDial(args.RPCAddress)
client, err := rpc.Dial("tcp", args.RPCAddress)
if err != nil {
return err
}
+2 -2
View File
@@ -57,7 +57,7 @@ func (p *postProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Art
return nil, false, nil
}
client, err := rpcDial(response.RPCAddress)
client, err := rpc.Dial("tcp", response.RPCAddress)
if err != nil {
return nil, false, err
}
@@ -75,7 +75,7 @@ func (p *PostProcessorServer) Configure(args *PostProcessorConfigureArgs, reply
}
func (p *PostProcessorServer) PostProcess(address string, reply *PostProcessorProcessResponse) error {
client, err := rpcDial(address)
client, err := rpc.Dial("tcp", address)
if err != nil {
return err
}
+1 -1
View File
@@ -65,7 +65,7 @@ func (p *ProvisionerServer) Prepare(args *ProvisionerPrepareArgs, reply *error)
}
func (p *ProvisionerServer) Provision(args *ProvisionerProvisionArgs, reply *interface{}) error {
client, err := rpcDial(args.RPCAddress)
client, err := rpc.Dial("tcp", args.RPCAddress)
if err != nil {
return err
}
+23 -139
View File
@@ -48,8 +48,6 @@ type RawBuilderConfig struct {
// 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{}
@@ -59,8 +57,6 @@ type RawPostProcessorConfig struct {
// 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{}
@@ -124,25 +120,18 @@ func ParseTemplate(data []byte) (t *Template, err error) {
// Gather all the variables
for k, v := range rawTpl.Variables {
var variable RawVariable
variable.Default = ""
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)
}
if v != nil {
def, ok := v.(string)
if !ok {
errors = append(errors,
fmt.Errorf("variable '%s': default value must be string or null", k))
continue
}
err = decoder.Decode(v)
if err != nil {
errors = append(errors,
fmt.Errorf("Error decoding default value for user var '%s': %s", k, err))
continue
variable.Default = def
}
t.Variables[k] = variable
@@ -200,50 +189,32 @@ func ParseTemplate(data []byte) (t *Template, err error) {
continue
}
configs := make([]RawPostProcessorConfig, 0, len(rawPP))
t.PostProcessors[i] = make([]RawPostProcessorConfig, len(rawPP))
configs := t.PostProcessors[i]
for j, pp := range rawPP {
var config RawPostProcessorConfig
if err := mapstructure.Decode(pp, &config); err != nil {
config := &configs[j]
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))
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))
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))
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
@@ -266,8 +237,9 @@ func ParseTemplate(data []byte) (t *Template, err error) {
continue
}
// Delete the keys that we used
raw.TemplateOnlyExcept.Prune(v)
// The provisioners not only don't need or want the override settings
// (as they are processed as part of the preparation below), but will
// actively reject them as invalid configuration.
delete(v, "override")
// Verify that the override keys exist...
@@ -278,14 +250,6 @@ func ParseTemplate(data []byte) (t *Template, err error) {
}
}
// 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))
}
}
raw.RawConfig = v
}
@@ -436,12 +400,8 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
// 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(name) {
continue
}
current := make([]coreBuildPostProcessor, len(rawPPs))
for i, rawPP := range rawPPs {
pp, err := components.PostProcessor(rawPP.Type)
if err != nil {
return nil, err
@@ -451,18 +411,12 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
return nil, fmt.Errorf("PostProcessor type not found: %s", rawPP.Type)
}
current = append(current, coreBuildPostProcessor{
current[i] = 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)
@@ -471,10 +425,6 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
// Prepare the provisioners
provisioners := make([]coreBuildProvisioner, 0, len(t.Provisioners))
for _, rawProvisioner := range t.Provisioners {
if rawProvisioner.TemplateOnlyExcept.Skip(name) {
continue
}
var provisioner Provisioner
provisioner, err = components.Provisioner(rawProvisioner.Type)
if err != nil {
@@ -521,69 +471,3 @@ func (t *Template) Build(name string, components *ComponentFinder) (b Build, err
return
}
// 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
}
+2 -418
View File
@@ -9,33 +9,6 @@ import (
"testing"
)
func testTemplateComponentFinder() *ComponentFinder {
builder := testBuilder()
pp := new(TestPostProcessor)
provisioner := &MockProvisioner{}
builderMap := map[string]Builder{
"test-builder": builder,
}
ppMap := map[string]PostProcessor{
"test-pp": pp,
}
provisionerMap := map[string]Provisioner{
"test-prov": provisioner,
}
builderFactory := func(n string) (Builder, error) { return builderMap[n], nil }
ppFactory := func(n string) (PostProcessor, error) { return ppMap[n], nil }
provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil }
return &ComponentFinder{
Builder: builderFactory,
PostProcessor: ppFactory,
Provisioner: provFactory,
}
}
func TestParseTemplateFile_basic(t *testing.T) {
data := `
{
@@ -391,8 +364,7 @@ func TestParseTemplate_Variables(t *testing.T) {
{
"variables": {
"foo": "bar",
"bar": null,
"baz": 27
"bar": null
},
"builders": [{"type": "something"}]
@@ -404,7 +376,7 @@ func TestParseTemplate_Variables(t *testing.T) {
t.Fatalf("err: %s", err)
}
if result.Variables == nil || len(result.Variables) != 3 {
if result.Variables == nil || len(result.Variables) != 2 {
t.Fatalf("bad vars: %#v", result.Variables)
}
@@ -423,14 +395,6 @@ func TestParseTemplate_Variables(t *testing.T) {
if !result.Variables["bar"].Required {
t.Fatal("bar should be required")
}
if result.Variables["baz"].Default != "27" {
t.Fatal("default should be empty")
}
if result.Variables["baz"].Required {
t.Fatal("baz should not be required")
}
}
func TestParseTemplate_variablesBadDefault(t *testing.T) {
@@ -699,386 +663,6 @@ func TestTemplate_Build(t *testing.T) {
}
}
func TestTemplateBuild_exceptOnlyPP(t *testing.T) {
data := `
{
"builders": [
{
"name": "test1",
"type": "test-builder"
},
{
"name": "test2",
"type": "test-builder"
}
],
"post-processors": [
{
"type": "test-pp",
"except": ["test1"],
"only": ["test1"]
}
]
}
`
_, err := ParseTemplate([]byte(data))
if err == nil {
t.Fatal("should have error")
}
}
func TestTemplateBuild_exceptOnlyProv(t *testing.T) {
data := `
{
"builders": [
{
"name": "test1",
"type": "test-builder"
},
{
"name": "test2",
"type": "test-builder"
}
],
"provisioners": [
{
"type": "test-prov",
"except": ["test1"],
"only": ["test1"]
}
]
}
`
_, err := ParseTemplate([]byte(data))
if err == nil {
t.Fatal("should have error")
}
}
func TestTemplateBuild_exceptPPInvalid(t *testing.T) {
data := `
{
"builders": [
{
"name": "test1",
"type": "test-builder"
},
{
"name": "test2",
"type": "test-builder"
}
],
"post-processors": [
{
"type": "test-pp",
"except": ["test5"]
}
]
}
`
_, err := ParseTemplate([]byte(data))
if err == nil {
t.Fatal("should have error")
}
}
func TestTemplateBuild_exceptPP(t *testing.T) {
data := `
{
"builders": [
{
"name": "test1",
"type": "test-builder"
},
{
"name": "test2",
"type": "test-builder"
}
],
"post-processors": [
{
"type": "test-pp",
"except": ["test1"]
}
]
}
`
template, err := ParseTemplate([]byte(data))
if err != nil {
t.Fatalf("err: %s", err)
}
// Verify test1 has no post-processors
build, err := template.Build("test1", testTemplateComponentFinder())
if err != nil {
t.Fatalf("err: %s", err)
}
cbuild := build.(*coreBuild)
if len(cbuild.postProcessors) > 0 {
t.Fatal("should have no postProcessors")
}
// Verify test2 has no post-processors
build, err = template.Build("test2", testTemplateComponentFinder())
if err != nil {
t.Fatalf("err: %s", err)
}
cbuild = build.(*coreBuild)
if len(cbuild.postProcessors) != 1 {
t.Fatalf("invalid: %d", len(cbuild.postProcessors))
}
}
func TestTemplateBuild_exceptProvInvalid(t *testing.T) {
data := `
{
"builders": [
{
"name": "test1",
"type": "test-builder"
},
{
"name": "test2",
"type": "test-builder"
}
],
"provisioners": [
{
"type": "test-prov",
"except": ["test5"]
}
]
}
`
_, err := ParseTemplate([]byte(data))
if err == nil {
t.Fatal("should have error")
}
}
func TestTemplateBuild_exceptProv(t *testing.T) {
data := `
{
"builders": [
{
"name": "test1",
"type": "test-builder"
},
{
"name": "test2",
"type": "test-builder"
}
],
"provisioners": [
{
"type": "test-prov",
"except": ["test1"]
}
]
}
`
template, err := ParseTemplate([]byte(data))
if err != nil {
t.Fatalf("err: %s", err)
}
// Verify test1 has no provisioners
build, err := template.Build("test1", testTemplateComponentFinder())
if err != nil {
t.Fatalf("err: %s", err)
}
cbuild := build.(*coreBuild)
if len(cbuild.provisioners) > 0 {
t.Fatal("should have no provisioners")
}
// Verify test2 has no provisioners
build, err = template.Build("test2", testTemplateComponentFinder())
if err != nil {
t.Fatalf("err: %s", err)
}
cbuild = build.(*coreBuild)
if len(cbuild.provisioners) != 1 {
t.Fatalf("invalid: %d", len(cbuild.provisioners))
}
}
func TestTemplateBuild_onlyPPInvalid(t *testing.T) {
data := `
{
"builders": [
{
"name": "test1",
"type": "test-builder"
},
{
"name": "test2",
"type": "test-builder"
}
],
"post-processors": [
{
"type": "test-pp",
"only": ["test5"]
}
]
}
`
_, err := ParseTemplate([]byte(data))
if err == nil {
t.Fatal("should have error")
}
}
func TestTemplateBuild_onlyPP(t *testing.T) {
data := `
{
"builders": [
{
"name": "test1",
"type": "test-builder"
},
{
"name": "test2",
"type": "test-builder"
}
],
"post-processors": [
{
"type": "test-pp",
"only": ["test2"]
}
]
}
`
template, err := ParseTemplate([]byte(data))
if err != nil {
t.Fatalf("err: %s", err)
}
// Verify test1 has no post-processors
build, err := template.Build("test1", testTemplateComponentFinder())
if err != nil {
t.Fatalf("err: %s", err)
}
cbuild := build.(*coreBuild)
if len(cbuild.postProcessors) > 0 {
t.Fatal("should have no postProcessors")
}
// Verify test2 has no post-processors
build, err = template.Build("test2", testTemplateComponentFinder())
if err != nil {
t.Fatalf("err: %s", err)
}
cbuild = build.(*coreBuild)
if len(cbuild.postProcessors) != 1 {
t.Fatalf("invalid: %d", len(cbuild.postProcessors))
}
}
func TestTemplateBuild_onlyProvInvalid(t *testing.T) {
data := `
{
"builders": [
{
"name": "test1",
"type": "test-builder"
},
{
"name": "test2",
"type": "test-builder"
}
],
"provisioners": [
{
"type": "test-prov",
"only": ["test5"]
}
]
}
`
_, err := ParseTemplate([]byte(data))
if err == nil {
t.Fatal("should have error")
}
}
func TestTemplateBuild_onlyProv(t *testing.T) {
data := `
{
"builders": [
{
"name": "test1",
"type": "test-builder"
},
{
"name": "test2",
"type": "test-builder"
}
],
"provisioners": [
{
"type": "test-prov",
"only": ["test2"]
}
]
}
`
template, err := ParseTemplate([]byte(data))
if err != nil {
t.Fatalf("err: %s", err)
}
// Verify test1 has no provisioners
build, err := template.Build("test1", testTemplateComponentFinder())
if err != nil {
t.Fatalf("err: %s", err)
}
cbuild := build.(*coreBuild)
if len(cbuild.provisioners) > 0 {
t.Fatal("should have no provisioners")
}
// Verify test2 has no provisioners
build, err = template.Build("test2", testTemplateComponentFinder())
if err != nil {
t.Fatalf("err: %s", err)
}
cbuild = build.(*coreBuild)
if len(cbuild.provisioners) != 1 {
t.Fatalf("invalid: %d", len(cbuild.provisioners))
}
}
func TestTemplate_Build_ProvisionerOverride(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
+1 -1
View File
@@ -10,7 +10,7 @@ import (
var GitCommit string
// The version of packer.
const Version = "0.3.9"
const Version = "0.3.7"
// Any pre-release marker for the version. If this is "" (empty string),
// then it means that it is a final release. Otherwise, this is the
+25 -41
View File
@@ -24,12 +24,15 @@ type Config struct {
}
type PostProcessor struct {
config Config
premade map[string]packer.PostProcessor
extraConfig map[string]interface{}
config Config
premade map[string]packer.PostProcessor
rawConfigs []interface{}
}
func (p *PostProcessor) Configure(raws ...interface{}) error {
// Store the raw configs for usage later
p.rawConfigs = raws
_, err := common.DecodeConfig(&p.config, raws...)
if err != nil {
return err
@@ -42,8 +45,10 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
tpl.UserVars = p.config.PackerUserVars
// Defaults
ppExtraConfig := make(map[string]interface{})
if p.config.OutputPath == "" {
p.config.OutputPath = "packer_{{ .BuildName }}_{{.Provider}}.box"
ppExtraConfig["output"] = p.config.OutputPath
}
// Accumulate any errors
@@ -53,18 +58,10 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
errs, fmt.Errorf("Error parsing output template: %s", err))
}
// Store extra configuration we'll send to each post-processor type
p.extraConfig = make(map[string]interface{})
p.extraConfig["output"] = p.config.OutputPath
p.extraConfig["packer_build_name"] = p.config.PackerBuildName
p.extraConfig["packer_builder_type"] = p.config.PackerBuilderType
p.extraConfig["packer_debug"] = p.config.PackerDebug
p.extraConfig["packer_force"] = p.config.PackerForce
p.extraConfig["packer_user_variables"] = p.config.PackerUserVars
// Store the extra configuration for post-processors
p.rawConfigs = append(p.rawConfigs, ppExtraConfig)
// TODO(mitchellh): Properly handle multiple raw configs. This isn't
// very pressing at the moment because at the time of this comment
// only the first member of raws can contain the actual type-overrides.
// TODO(mitchellh): Properly handle multiple raw configs
var mapConfig map[string]interface{}
if err := mapstructure.Decode(raws[0], &mapConfig); err != nil {
errs = packer.MultiErrorAppend(errs,
@@ -74,14 +71,18 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
p.premade = make(map[string]packer.PostProcessor)
for k, raw := range mapConfig {
pp, err := p.subPostProcessor(k, raw, p.extraConfig)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
pp := keyToPostProcessor(k)
if pp == nil {
continue
}
if pp == nil {
continue
// Create the proper list of configurations
ppConfigs := make([]interface{}, 0, len(p.rawConfigs)+1)
copy(ppConfigs, p.rawConfigs)
ppConfigs = append(ppConfigs, raw)
if err := pp.Configure(ppConfigs...); err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
p.premade[k] = pp
@@ -105,37 +106,20 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
pp, ok := p.premade[ppName]
if !ok {
log.Printf("Premade post-processor for '%s' not found. Creating.", ppName)
var err error
pp, err = p.subPostProcessor(ppName, nil, p.extraConfig)
if err != nil {
return nil, false, err
}
pp = keyToPostProcessor(ppName)
if pp == nil {
return nil, false, fmt.Errorf("Vagrant box post-processor not found: %s", ppName)
}
if err := pp.Configure(p.rawConfigs...); err != nil {
return nil, false, err
}
}
ui.Say(fmt.Sprintf("Creating Vagrant box for '%s' provider", ppName))
return pp.PostProcess(ui, artifact)
}
func (p *PostProcessor) subPostProcessor(key string, specific interface{}, extra map[string]interface{}) (packer.PostProcessor, error) {
pp := keyToPostProcessor(key)
if pp == nil {
return nil, nil
}
if err := pp.Configure(extra, specific); err != nil {
return nil, err
}
return pp, nil
}
// keyToPostProcessor maps a configuration key to the actual post-processor
// it will be configuring. This returns a new instance of that post-processor.
func keyToPostProcessor(key string) packer.PostProcessor {
switch key {
case "aws":
+7 -125
View File
@@ -9,7 +9,6 @@ import (
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -18,12 +17,7 @@ import (
type Config struct {
common.PackerConfig `mapstructure:",squash"`
ChefEnvironment string `mapstructure:"chef_environment"`
ConfigTemplate string `mapstructure:"config_template"`
CookbookPaths []string `mapstructure:"cookbook_paths"`
RolesPath string `mapstructure:"roles_path"`
DataBagsPath string `mapstructure:"data_bags_path"`
EnvironmentsPath string `mapstructure:"environments_path"`
ExecuteCommand string `mapstructure:"execute_command"`
InstallCommand string `mapstructure:"install_command"`
RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths"`
@@ -41,18 +35,7 @@ type Provisioner struct {
}
type ConfigTemplate struct {
CookbookPaths string
DataBagsPath string
RolesPath string
EnvironmentsPath string
ChefEnvironment string
// Templates don't support boolean statements until Go 1.2. In the
// mean time, we do this.
// TODO(mitchellh): Remove when Go 1.2 is released
HasDataBagsPath bool
HasRolesPath bool
HasEnvironmentsPath bool
CookbookPaths string
}
type ExecuteTemplate struct {
@@ -97,12 +80,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
errs := common.CheckUnusedConfig(md)
templates := map[string]*string{
"config_template": &p.config.ConfigTemplate,
"data_bags_path": &p.config.DataBagsPath,
"roles_path": &p.config.RolesPath,
"staging_dir": &p.config.StagingDir,
"environments_path": &p.config.EnvironmentsPath,
"chef_environment": &p.config.ChefEnvironment,
"staging_dir": &p.config.StagingDir,
}
for n, ptr := range templates {
@@ -143,17 +121,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
}
if p.config.ConfigTemplate != "" {
fi, err := os.Stat(p.config.ConfigTemplate)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Bad config template path: %s", err))
} else if fi.IsDir() {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Config template path must be a file: %s", err))
}
}
for _, path := range p.config.CookbookPaths {
pFileInfo, err := os.Stat(path)
@@ -163,33 +130,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
}
if p.config.RolesPath != "" {
pFileInfo, err := os.Stat(p.config.RolesPath)
if err != nil || !pFileInfo.IsDir() {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Bad roles path '%s': %s", p.config.RolesPath, err))
}
}
if p.config.DataBagsPath != "" {
pFileInfo, err := os.Stat(p.config.DataBagsPath)
if err != nil || !pFileInfo.IsDir() {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Bad data bags path '%s': %s", p.config.DataBagsPath, err))
}
}
if p.config.EnvironmentsPath != "" {
pFileInfo, err := os.Stat(p.config.EnvironmentsPath)
if err != nil || !pFileInfo.IsDir() {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Bad environments path '%s': %s", p.config.EnvironmentsPath, err))
}
}
// Process the user variables within the JSON and set the JSON.
// Do this early so that we can validate and show errors.
p.config.Json, err = p.processJsonUserVars()
@@ -226,31 +166,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
cookbookPaths = append(cookbookPaths, targetPath)
}
rolesPath := ""
if p.config.RolesPath != "" {
rolesPath = fmt.Sprintf("%s/roles", p.config.StagingDir)
if err := p.uploadDirectory(ui, comm, rolesPath, p.config.RolesPath); err != nil {
return fmt.Errorf("Error uploading roles: %s", err)
}
}
dataBagsPath := ""
if p.config.DataBagsPath != "" {
dataBagsPath = fmt.Sprintf("%s/data_bags", p.config.StagingDir)
if err := p.uploadDirectory(ui, comm, dataBagsPath, p.config.DataBagsPath); err != nil {
return fmt.Errorf("Error uploading data bags: %s", err)
}
}
environmentsPath := ""
if p.config.EnvironmentsPath != "" {
environmentsPath = fmt.Sprintf("%s/environments", p.config.StagingDir)
if err := p.uploadDirectory(ui, comm, environmentsPath, p.config.EnvironmentsPath); err != nil {
return fmt.Errorf("Error uploading environments: %s", err)
}
}
configPath, err := p.createConfig(ui, comm, cookbookPaths, rolesPath, dataBagsPath, environmentsPath, p.config.ChefEnvironment)
configPath, err := p.createConfig(ui, comm, cookbookPaths)
if err != nil {
return fmt.Errorf("Error creating Chef config file: %s", err)
}
@@ -287,7 +203,7 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
return comm.UploadDir(dst, src, nil)
}
func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string, rolesPath string, dataBagsPath string, environmentsPath string, chefEnvironment string) (string, error) {
func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string) (string, error) {
ui.Message("Creating configuration file 'solo.rb'")
cookbook_paths := make([]string, len(p.config.RemoteCookbookPaths)+len(localCookbooks))
@@ -300,32 +216,8 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local
cookbook_paths[i] = fmt.Sprintf(`"%s"`, path)
}
// Read the template
tpl := DefaultConfigTemplate
if p.config.ConfigTemplate != "" {
f, err := os.Open(p.config.ConfigTemplate)
if err != nil {
return "", err
}
defer f.Close()
tplBytes, err := ioutil.ReadAll(f)
if err != nil {
return "", err
}
tpl = string(tplBytes)
}
configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{
CookbookPaths: strings.Join(cookbook_paths, ","),
RolesPath: rolesPath,
DataBagsPath: dataBagsPath,
EnvironmentsPath: environmentsPath,
HasRolesPath: rolesPath != "",
HasDataBagsPath: dataBagsPath != "",
HasEnvironmentsPath: environmentsPath != "",
ChefEnvironment: chefEnvironment,
configString, err := p.config.tpl.Process(DefaultConfigTemplate, &ConfigTemplate{
CookbookPaths: strings.Join(cookbook_paths, ","),
})
if err != nil {
return "", err
@@ -476,15 +368,5 @@ func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) {
}
var DefaultConfigTemplate = `
cookbook_path [{{.CookbookPaths}}]
{{if .HasRolesPath}}
role_path "{{.RolesPath}}"
{{end}}
{{if .HasDataBagsPath}}
data_bag_path "{{.DataBagsPath}}"
{{end}}
{{if .HasEnvironmentsPath}}
environments_path "{{.EnvironmentsPath}}"
chef_environment "{{.ChefEnvironment}}"
{{end}}
cookbook_path [{{.CookbookPaths}}]
`
-147
View File
@@ -19,65 +19,6 @@ func TestProvisioner_Impl(t *testing.T) {
}
}
func TestProvisionerPrepare_chefEnvironment(t *testing.T) {
var p Provisioner
config := testConfig()
config["chef_environment"] = "some-env"
err := p.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
if p.config.ChefEnvironment != "some-env" {
t.Fatalf("unexpected: %#v", p.config.ChefEnvironment)
}
}
func TestProvisionerPrepare_configTemplate(t *testing.T) {
var err error
var p Provisioner
// Test no config template
config := testConfig()
delete(config, "config_template")
err = p.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
// Test with a file
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
config = testConfig()
config["config_template"] = tf.Name()
p = Provisioner{}
err = p.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
// Test with a directory
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config = testConfig()
config["config_template"] = td
p = Provisioner{}
err = p.Prepare(config)
if err == nil {
t.Fatal("should have err")
}
}
func TestProvisionerPrepare_cookbookPaths(t *testing.T) {
var p Provisioner
@@ -91,25 +32,11 @@ func TestProvisionerPrepare_cookbookPaths(t *testing.T) {
t.Fatalf("err: %s", err)
}
rolesPath, err := ioutil.TempDir("", "roles")
if err != nil {
t.Fatalf("err: %s", err)
}
dataBagsPath, err := ioutil.TempDir("", "data_bags")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(path1)
defer os.Remove(path2)
defer os.Remove(rolesPath)
defer os.Remove(dataBagsPath)
config := testConfig()
config["cookbook_paths"] = []string{path1, path2}
config["roles_path"] = rolesPath
config["data_bags_path"] = dataBagsPath
err = p.Prepare(config)
if err != nil {
@@ -123,80 +50,6 @@ func TestProvisionerPrepare_cookbookPaths(t *testing.T) {
if p.config.CookbookPaths[0] != path1 || p.config.CookbookPaths[1] != path2 {
t.Fatalf("unexpected: %#v", p.config.CookbookPaths)
}
if p.config.RolesPath != rolesPath {
t.Fatalf("unexpected: %#v", p.config.RolesPath)
}
if p.config.DataBagsPath != dataBagsPath {
t.Fatalf("unexpected: %#v", p.config.DataBagsPath)
}
}
func TestProvisionerPrepare_dataBagsPath(t *testing.T) {
var p Provisioner
dataBagsPath, err := ioutil.TempDir("", "data_bags")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(dataBagsPath)
config := testConfig()
config["data_bags_path"] = dataBagsPath
err = p.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
if p.config.DataBagsPath != dataBagsPath {
t.Fatalf("unexpected: %#v", p.config.DataBagsPath)
}
}
func TestProvisionerPrepare_environmentsPath(t *testing.T) {
var p Provisioner
environmentsPath, err := ioutil.TempDir("", "environments")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(environmentsPath)
config := testConfig()
config["environments_path"] = environmentsPath
err = p.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
if p.config.EnvironmentsPath != environmentsPath {
t.Fatalf("unexpected: %#v", p.config.EnvironmentsPath)
}
}
func TestProvisionerPrepare_rolesPath(t *testing.T) {
var p Provisioner
rolesPath, err := ioutil.TempDir("", "roles")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(rolesPath)
config := testConfig()
config["roles_path"] = rolesPath
err = p.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
if p.config.RolesPath != rolesPath {
t.Fatalf("unexpected: %#v", p.config.RolesPath)
}
}
func TestProvisionerPrepare_json(t *testing.T) {
+4 -5
View File
@@ -69,7 +69,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
// Set some defaults
if p.config.ExecuteCommand == "" {
p.config.ExecuteCommand = "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
p.config.ExecuteCommand = "{{.FacterVars}}{{if .Sudo}} sudo -E {{end}}" +
"puppet apply --verbose --modulepath='{{.ModulePath}}' " +
"{{if .HasHieraConfigPath}}--hiera_config='{{.HieraConfigPath}}' {{end}}" +
"--detailed-exitcodes " +
@@ -82,9 +82,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
// Templates
templates := map[string]*string{
"hiera_config_path": &p.config.HieraConfigPath,
"manifest_file": &p.config.ManifestFile,
"staging_dir": &p.config.StagingDir,
"staging_dir": &p.config.StagingDir,
}
for n, ptr := range templates {
@@ -217,6 +215,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
}
// Upload manifests
ui.Message("Uploading manifests...")
remoteManifestFile, err := p.uploadManifests(ui, comm)
if err != nil {
return fmt.Errorf("Error uploading manifests: %s", err)
@@ -250,7 +249,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
return err
}
if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 {
if cmd.ExitStatus != 0 {
return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus)
}
+1 -11
View File
@@ -8,7 +8,6 @@ import (
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"log"
"os"
@@ -21,10 +20,6 @@ const DefaultRemotePath = "/tmp/script.sh"
type config struct {
common.PackerConfig `mapstructure:",squash"`
// If true, the script contains binary and line endings will not be
// converted from Windows to Unix-style.
Binary bool
// An inline script to execute. Multiple strings are all executed
// in the context of a single shell.
Inline []string
@@ -264,12 +259,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
return err
}
var r io.Reader = f
if !p.config.Binary {
r = &UnixReader{Reader: r}
}
if err := comm.Upload(p.config.RemotePath, r); err != nil {
if err := comm.Upload(p.config.RemotePath, f); err != nil {
return fmt.Errorf("Error uploading script: %s", err)
}
-58
View File
@@ -1,58 +0,0 @@
package shell
import (
"bufio"
"io"
"sync"
)
// UnixReader is a Reader implementation that automatically converts
// Windows line endings to Unix line endings.
type UnixReader struct {
Reader io.Reader
buf []byte
once sync.Once
scanner *bufio.Scanner
}
func (r *UnixReader) Read(p []byte) (n int, err error) {
// Create the buffered reader once
r.once.Do(func() {
r.scanner = bufio.NewScanner(r.Reader)
r.scanner.Split(scanUnixLine)
})
// If we have no data in our buffer, scan to the next token
if len(r.buf) == 0 {
if !r.scanner.Scan() {
err = r.scanner.Err()
if err == nil {
err = io.EOF
}
return 0, err
}
r.buf = r.scanner.Bytes()
}
// Write out as much data as we can to the buffer, storing the rest
// for the next read.
n = len(p)
if n > len(r.buf) {
n = len(r.buf)
}
copy(p, r.buf)
r.buf = r.buf[n:]
return
}
// scanUnixLine is a bufio.Scanner SplitFunc. It tokenizes on lines, but
// only returns unix-style lines. So even if the line is "one\r\n", the
// token returned will be "one\n".
func scanUnixLine(data []byte, atEOF bool) (advance int, token []byte, err error) {
advance, token, err = bufio.ScanLines(data, atEOF)
return advance, append(token, "\n"...), err
}
-51
View File
@@ -1,51 +0,0 @@
package shell
import (
"bytes"
"io"
"testing"
)
func TestUnixReader_impl(t *testing.T) {
var raw interface{}
raw = new(UnixReader)
if _, ok := raw.(io.Reader); !ok {
t.Fatal("should be reader")
}
}
func TestUnixReader(t *testing.T) {
input := "one\r\ntwo\nthree\r\n"
expected := "one\ntwo\nthree\n"
r := &UnixReader{
Reader: bytes.NewReader([]byte(input)),
}
result := new(bytes.Buffer)
if _, err := io.Copy(result, r); err != nil {
t.Fatalf("err: %s", err)
}
if result.String() != expected {
t.Fatalf("bad: %#v", result.String())
}
}
func TestUnixReader_unixOnly(t *testing.T) {
input := "one\n\ntwo\nthree\n"
expected := "one\n\ntwo\nthree\n"
r := &UnixReader{
Reader: bytes.NewReader([]byte(input)),
}
result := new(bytes.Buffer)
if _, err := io.Copy(result, r); err != nil {
t.Fatalf("err: %s", err)
}
if result.String() != expected {
t.Fatalf("bad: %#v", result.String())
}
}
-42
View File
@@ -26,9 +26,6 @@ if [ "$(go env GOOS)" = "windows" ]; then
EXTENSION=".exe"
fi
# Make sure that if we're killed, we kill all our subprocseses
trap "kill 0" SIGINT SIGTERM EXIT
# If we're building a race-enabled build, then set that up.
if [ ! -z $PACKER_RACE ]; then
echo -e "${OK_COLOR}--> Building with race detection enabled${NO_COLOR}"
@@ -38,60 +35,21 @@ fi
echo -e "${OK_COLOR}--> Installing dependencies to speed up builds...${NO_COLOR}"
go get ./...
# This function waits for all background tasks to complete
waitAll() {
RESULT=0
for job in `jobs -p`; do
wait $job
if [ $? -ne 0 ]; then
RESULT=1
fi
done
if [ $RESULT -ne 0 ]; then
exit $RESULT
fi
}
waitSingle() {
if [ ! -z $PACKER_NO_BUILD_PARALLEL ]; then
waitAll
fi
}
if [ -z $PACKER_NO_BUILD_PARALLEL ]; then
echo -e "${OK_COLOR}--> NOTE: Compilation of components " \
"will be done in parallel.${NO_COLOR}"
fi
# Compile the main Packer app
echo -e "${OK_COLOR}--> Compiling Packer${NO_COLOR}"
(
go build \
${PACKER_RACE} \
-ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
-v \
-o bin/packer${EXTENSION} .
) &
waitSingle
# Go over each plugin and build it
for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do
PLUGIN_NAME=$(basename ${PLUGIN})
echo -e "${OK_COLOR}--> Compiling Plugin: ${PLUGIN_NAME}${NO_COLOR}"
(
go build \
${PACKER_RACE} \
-ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
-v \
-o bin/packer-${PLUGIN_NAME}${EXTENSION} ${PLUGIN}
) &
waitSingle
done
waitAll
# Reset signal trapping to avoid "Terminated: 15" at the end
trap - SIGINT SIGTERM EXIT
+4 -4
View File
@@ -2,11 +2,11 @@ source 'https://rubygems.org'
ruby '1.9.3'
gem "middleman", "~> 3.1.5"
gem "middleman-minify-html", "~> 3.1.1"
gem "middleman", "~> 3.0.6"
gem "middleman-minify-html", "~> 3.0.0"
gem "rack-contrib", "~> 1.1.0"
gem "redcarpet", "~> 3.0.0"
gem "therubyracer", "~> 0.12.0"
gem "redcarpet", "~> 2.2.2"
gem "therubyracer", "~> 0.10.2"
gem "thin", "~> 1.5.0"
group :development do
+92 -67
View File
@@ -1,109 +1,134 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (3.2.14)
i18n (~> 0.6, >= 0.6.4)
POpen4 (0.1.4)
Platform (>= 0.4.0)
open4
Platform (0.4.0)
activesupport (3.2.9)
i18n (~> 0.6)
multi_json (~> 1.0)
chunky_png (1.2.8)
chunky_png (1.2.6)
coffee-script (2.2.0)
coffee-script-source
execjs
coffee-script-source (1.6.3)
coffee-script-source (1.3.3)
compass (0.12.2)
chunky_png (~> 1.2)
fssm (>= 0.2.7)
sass (~> 3.1)
daemons (1.1.9)
eventmachine (1.0.3)
eventmachine (1.0.0)
execjs (1.4.0)
multi_json (~> 1.0)
ffi (1.9.0)
fssm (0.2.10)
haml (4.0.3)
tilt
highline (1.6.19)
hike (1.2.3)
i18n (0.6.5)
kramdown (1.1.0)
libv8 (3.16.14.3)
listen (1.2.3)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
rb-kqueue (>= 0.2)
middleman (3.1.5)
ffi (1.2.0)
fssm (0.2.9)
haml (3.1.7)
highline (1.6.15)
hike (1.2.1)
htmlcompressor (0.0.3)
yui-compressor (~> 0.9.6)
http_router (0.10.2)
rack (>= 1.0.0)
url_mount (~> 0.2.1)
i18n (0.6.1)
libv8 (3.3.10.4)
listen (0.5.3)
maruku (0.6.1)
syntax (>= 1.0.0)
middleman (3.0.6)
middleman-core (= 3.0.6)
middleman-more (= 3.0.6)
middleman-sprockets (~> 3.0.2)
middleman-core (3.0.6)
activesupport (~> 3.2.6)
bundler (~> 1.1)
listen (~> 0.5.2)
rack (~> 1.4.1)
rack-test (~> 0.6.1)
rb-fsevent (~> 0.9.1)
rb-inotify (~> 0.8.8)
thor (~> 0.15.4)
tilt (~> 1.3.1)
middleman-minify-html (3.0.0)
htmlcompressor
middleman-core (~> 3.0.0)
middleman-more (3.0.6)
coffee-script (~> 2.2.0)
coffee-script-source (~> 1.3.3)
compass (>= 0.12.2)
execjs (~> 1.4.0)
haml (>= 3.1.6)
kramdown (~> 1.1.0)
middleman-core (= 3.1.5)
middleman-more (= 3.1.5)
middleman-sprockets (>= 3.1.2)
i18n (~> 0.6.0)
maruku (~> 0.6.0)
middleman-core (= 3.0.6)
padrino-helpers (= 0.10.7)
sass (>= 3.1.20)
uglifier (~> 2.1.0)
middleman-core (3.1.5)
activesupport (~> 3.2.6)
bundler (~> 1.1)
i18n (~> 0.6.1)
listen (~> 1.2.2)
rack (>= 1.4.5)
rack-test (~> 0.6.1)
thor (>= 0.15.2, < 2.0)
tilt (~> 1.3.6)
middleman-minify-html (3.1.1)
middleman-core (~> 3.0)
middleman-more (3.1.5)
middleman-sprockets (3.1.4)
middleman-core (>= 3.0.14)
middleman-more (>= 3.0.14)
sprockets (~> 2.1)
sprockets-helpers (~> 1.0.0)
sprockets-sass (~> 1.0.0)
multi_json (1.8.0)
rack (1.5.2)
uglifier (~> 1.2.6)
middleman-sprockets (3.0.4)
middleman-more (~> 3.0.1)
sprockets (~> 2.1, < 2.5)
sprockets-sass (~> 0.8.0)
multi_json (1.4.0)
open4 (1.3.0)
padrino-core (0.10.7)
activesupport (~> 3.2.0)
http_router (~> 0.10.2)
sinatra (~> 1.3.1)
thor (~> 0.15.2)
tilt (~> 1.3.0)
padrino-helpers (0.10.7)
i18n (~> 0.6)
padrino-core (= 0.10.7)
rack (1.4.1)
rack-contrib (1.1.0)
rack (>= 0.9.1)
rack-protection (1.2.0)
rack
rack-test (0.6.2)
rack (>= 1.0)
rb-fsevent (0.9.3)
rb-inotify (0.9.2)
rb-fsevent (0.9.2)
rb-inotify (0.8.8)
ffi (>= 0.5.0)
rb-kqueue (0.2.0)
ffi (>= 0.5.0)
redcarpet (3.0.0)
ref (1.0.5)
sass (3.2.10)
sprockets (2.10.0)
redcarpet (2.2.2)
sass (3.2.3)
sinatra (1.3.3)
rack (~> 1.3, >= 1.3.6)
rack-protection (~> 1.2)
tilt (~> 1.3, >= 1.3.3)
sprockets (2.4.5)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sprockets-helpers (1.0.1)
sprockets (~> 2.0)
sprockets-sass (1.0.1)
sprockets-sass (0.8.0)
sprockets (~> 2.0)
tilt (~> 1.1)
therubyracer (0.12.0)
libv8 (~> 3.16.14.0)
ref
thin (1.5.1)
syntax (1.0.0)
therubyracer (0.10.2)
libv8 (~> 3.3.10)
thin (1.5.0)
daemons (>= 1.0.9)
eventmachine (>= 0.12.6)
rack (>= 1.0.0)
thor (0.18.1)
tilt (1.3.7)
uglifier (2.1.2)
thor (0.15.4)
tilt (1.3.3)
uglifier (1.2.7)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
multi_json (~> 1.3)
url_mount (0.2.1)
rack
yui-compressor (0.9.6)
POpen4 (>= 0.1.4)
PLATFORMS
ruby
DEPENDENCIES
highline (~> 1.6.15)
middleman (~> 3.1.5)
middleman-minify-html (~> 3.1.1)
middleman (~> 3.0.6)
middleman-minify-html (~> 3.0.0)
rack-contrib (~> 1.1.0)
redcarpet (~> 3.0.0)
therubyracer (~> 0.12.0)
redcarpet (~> 2.2.2)
therubyracer (~> 0.10.2)
thin (~> 1.5.0)
@@ -111,11 +111,9 @@ Optional:
of the source AMI will be attached. This defaults to "" (empty string),
which forces Packer to find an open device automatically.
* `command_wrapper` (string) - How to run shell commands. This
defaults to "{{.Command}}". This may be useful to set if you want to set
environmental variables or perhaps run it with `sudo` or so on. This is a
configuration template where the `.Command` variable is replaced with the
command to be run..
* `mount_command` (string) - The command to use to mount devices. This
defaults to "mount". This may be useful to set if you want to set
environmental variables or perhaps run it with `sudo` or so on.
* `mount_path` (string) - The path where the volume will be mounted. This is
where the chroot environment will be. This defaults to
@@ -125,6 +123,9 @@ Optional:
* `tags` (object of key/value strings) - Tags applied to the AMI.
* `unmount_command` (string) - Just like `mount_command`, except this is
the command to unmount devices.
## Basic Example
Here is a basic example. It is completely valid except for the access keys:
@@ -183,37 +184,3 @@ out of your AMI builds.
Packer properly obtains a process lock for the parallelism-sensitive parts
of its internals such as finding an available device.
## Using an IAM Instance Profile
If AWS keys are not specified in the template or through environment variables
Packer will use credentials provided by the instance's IAM profile, if it has one.
The following policy document provides the minimal set permissions necessary for Packer to work:
<pre class="prettyprint">
{
"Statement": [{
"Effect": "Allow",
"Action" : [
"ec2:AttachVolume",
"ec2:CreateVolume",
"ec2:DeleteVolume",
"ec2:DescribeVolumes",
"ec2:DetachVolume",
"ec2:DescribeInstances",
"ec2:CreateSnapshot",
"ec2:DeleteSnapshot",
"ec2:DescribeSnapshots",
"ec2:DescribeImages",
"ec2:RegisterImage",
"ec2:CreateTags"
],
"Resource" : "*"
}]
}
</pre>
@@ -35,6 +35,11 @@ Required:
Optional:
* `event_delay` (string) - The delay, as a duration string, before checking
the status of an event. DigitalOcean's current API has consistency issues
where events take time to appear after being created. This defaults to "5s"
and generally shouldn't have to be changed.
* `image_id` (int) - The ID of the base image to use. This is the image that
will be used to launch a new droplet and provision it. Defaults to "284203",
which happens to be "Ubuntu 12.04 x64 Server."
@@ -85,10 +85,6 @@ Optional:
* `format` (string) - Either "ovf" or "ova", this specifies the output
format of the exported virtual machine. This defaults to "ovf".
* `guest_additions_attach` (bool) - If this is true (defaults to "false"),
the guest additions ISO will be attached to the virtual machine as a CD
rather than uploaded as a raw ISO.
* `guest_additions_path` (string) - The path on the guest virtual machine
where the VirtualBox guest additions ISO will be uploaded. By default this
is "VBoxGuestAdditions.iso" which should upload into the login directory
@@ -27,7 +27,7 @@ Ubuntu to self-install. Still, the example serves to show the basic configuratio
<pre class="prettyprint">
{
"type": "vmware",
"iso_url": "http://old-releases.ubuntu.com/releases/precise/ubuntu-12.04.2-server-amd64.iso",
"iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.2-server-amd64.iso",
"iso_checksum": "af5f788aee1b32c4b2634734309cc9e9",
"iso_checksum_type": "md5",
"ssh_username": "packer",
@@ -42,8 +42,6 @@ usage: packer [--version] [--help] <command> [<args>]
Available commands are:
build build image(s) from template
fix fixes templates from old versions of packer
inspect see components of a template
validate check that a template is valid
```
@@ -32,26 +32,11 @@ The example below is fully functional and expects cookbooks in the
The reference of available configuration options is listed below. No
configuration is actually required, but at least `run_list` is recommended.
* `config_template` (string) - Path to a template that will be used for
the Chef configuration file. By default Packer only sets configuration
it needs to match the settings set in the provisioner configuration. If
you need to set configurations that the Packer provisioner doesn't support,
then you should use a custom configuration template. See the dedicated
"Chef Configuration" section below for more details.
* `cookbook_paths` (array of strings) - This is an array of paths to
"cookbooks" directories on your local filesystem. These will be uploaded
to the remote machine in the directory specified by the `staging_directory`.
By default, this is empty.
* `roles_path` (string) - The path to the "roles" directory on your local filesystem.
These will be uploaded to the remote machine in the directory specified by the
`staging_directory`. By default, this is empty.
* `data_bags_path` (string) - The path to the "data_bags" directory on your local filesystem.
These will be uploaded to the remote machine in the directory specified by the
`staging_directory`. By default, this is empty.
* `execute_command` (string) - The command used to execute Chef. This has
various [configuration template variables](/docs/templates/configuration-templates.html)
available. See below for more information.
@@ -85,25 +70,6 @@ configuration is actually required, but at least `run_list` is recommended.
this folder. If the permissions are not correct, use a shell provisioner
prior to this to configure it properly.
## Chef Configuration
By default, Packer uses a simple Chef configuration file in order to set
the options specified for the provisioner. But Chef is a complex tool that
supports many configuration options. Packer allows you to specify a custom
configuration template if you'd like to set custom configurations.
The default value for the configuration template is:
```
cookbook_path [{{.CookbookPaths}}]
```
This template is a [configuration template](/docs/templates/configuration-templates.html)
and has a set of variables available to use:
* `CookbookPaths` is the set of cookbook paths ready to embedded directly
into a Ruby array to configure Chef.
## Execute Command
By default, Packer uses the following command (broken across multiple lines
@@ -79,7 +79,7 @@ By default, Packer uses the following command (broken across multiple lines
for readability) to execute Puppet:
```
{{.FacterVars}}{{if .Sudo}} sudo -E {{end}}puppet apply \
{{.FacterVars}}{{if .Sudo} sudo -E {{end}}puppet apply \
--verbose \
--modulepath='{{.ModulePath}}' \
{{if .HasHieraConfigPath}}--hiera_config='{{.HieraConfigPath}}' {{end}} \
@@ -47,10 +47,6 @@ Exactly _one_ of the following is required:
Optional parameters:
* `binary` (boolean) - If true, specifies that the script(s) are binary
files, and Packer should therefore not convert Windows line endings to
Unix line endings (if there are any). By default this is false.
* `environment_vars` (array of strings) - An array of key/value pairs
to inject prior to the execute_command. The format should be
`key=value`. Packer injects some environmental variables by default
@@ -102,7 +98,7 @@ root privileges without worrying about password prompts.
## Default Environmental Variables
In addition to being able to specify custom environmental variables using
the `environment_vars` configuration, the provisioner automatically
the `environmental_vars` configuration, the provisioner automatically
defines certain commonly useful environmental variables:
* `PACKER_BUILD_NAME` is set to the name of the build that Packer is running.
@@ -125,28 +125,3 @@ The answer is no, of course not. Packer is smart enough to figure out
that at least one post-processor requested that the input be kept, so it will keep
it around.
</div>
## Run on Specific Builds
You can use the `only` or `except` configurations to run a post-processor
only with specific builds. These two configurations do what you expect:
`only` will only run the post-processor on the specified builds and
`except` will run the post-processor on anything other than the specified
builds.
An example of `only` being used is shown below, but the usage of `except`
is effectively the same. `only` and `except` can only be specified on "detailed"
configurations. If you have a sequence of post-processors to run, `only`
and `except` will only affect that single post-processor in the sequence.
<pre class="prettyprint">
{
"type": "vagrant",
"only": ["virtualbox"]
}
</pre>
The values within `only` or `except` are _build names_, not builder
types. If you recall, build names by default are just their builder type,
but if you specify a custom `name` parameter, then you should use that
as the value instead of the type.
@@ -53,30 +53,6 @@ provisioner to run a local script within the machines:
}
</pre>
## Run on Specific Builds
You can use the `only` or `except` configurations to run a provisioner
only with specific builds. These two configurations do what you expect:
`only` will only run the provisioner on the specified builds and
`except` will run the provisioner on anything other than the specified
builds.
An example of `only` being used is shown below, but the usage of `except`
is effectively the same:
<pre class="prettyprint">
{
"type": "shell",
"script": "script.sh",
"only": ["virtualbox"]
}
</pre>
The values within `only` or `except` are _build names_, not builder
types. If you recall, build names by default are just their builder type,
but if you specify a custom `name` parameter, then you should use that
as the value instead of the type.
## Build-Specific Overrides
While the goal of Packer is to produce identical machine images, it
+1 -1
View File
@@ -54,7 +54,7 @@ validation will fail.
Using the variables is extremely easy. Variables are used by calling
the user function in the form of <code>{{user &#96;variable&#96;}}</code>.
This function can be used in _any value_ within the template, in
This function can be used in _any string_ within the template, in
builders, provisioners, _anything_. The user variable is available globally
within the template.
@@ -45,8 +45,6 @@ usage: packer [--version] [--help] <command> [<args>]
Available commands are:
build build image(s) from template
fix fixes templates from old versions of packer
inspect see components of a template
validate check that a template is valid
```