Compare commits

..

67 Commits

Author SHA1 Message Date
Mitchell Hashimoto 8597b5c5a3 v0.3.9 2013-10-02 08:34:29 -07:00
Mitchell Hashimoto 5b8c6582b6 provisioner/chef-solo: unused var 2013-10-02 08:29:12 -07:00
Mitchell Hashimoto fbd5a3c534 provisioner/chef-solo: alphabetize tests and such
/cc @netshade
2013-10-02 08:28:51 -07:00
Mitchell Hashimoto a4309ff33a Update CHANGELOG 2013-10-02 08:27:38 -07:00
Mitchell Hashimoto 96a0ae50b7 Merge pull request #483 from netshade/chef-environment-support
provisioner/chef-solo: chef environment support
2013-10-02 08:26:47 -07:00
Mitchell Hashimoto bb99b79733 Merge pull request #486 from tchaudhri/update_site_avail_commands_list
website: Updated web site doc. for avail commands list
2013-10-01 19:08:51 -07:00
Tehmasp Chaudhri 0d5cd549a4 Updated web site doc. for avail commands list 2013-10-01 19:45:11 -06:00
Chris 8ba2346fe3 formatting 2013-10-01 11:21:16 -04:00
Chris cf5abfc0f1 add environments_path and chef_environment support to chef provisioner 2013-10-01 11:20:36 -04:00
Matthew Hooker 2af19060ce Fix the way we run commands in chroot.
* Single quotes around chroot command cause it to fail.
* The chroot command also gets passed through the CommandWrapper template, so having sudo would clash with however the user wants to run it.
* Fix spelling mistake.
2013-10-01 09:35:55 +00:00
Matthew Hooker 43e76cc7d0 Fix UnixReader panic on empty lines.
Added a test for the failure.

Most of the scanner code looks like it's a modified version of
bufio.ScanLines, so I changed it to use that but always add a line feed.
2013-10-01 09:06:01 +00:00
Mitchell Hashimoto b230453b0a Update CHANGELOG 2013-09-30 22:03:14 -07:00
Jason A. Beranek b2a00e5772 provisioner/shell: fix provisioner to use UnixReader [GH-477]
Packer was using the original file reader when uploading, instead of the UnixReader /cc @mitchellh
2013-09-30 23:54:54 -05:00
Mitchell Hashimoto 7bad5923d3 go fmt 2013-09-30 11:39:52 -07:00
Mitchell Hashimoto ff163e4459 provisioner/shell: add another UnixReader test for sanity 2013-09-30 11:39:24 -07:00
Mitchell Hashimoto a793e03fb6 builder/amazon/chroot: remove unused comment 2013-09-30 09:35:58 -07:00
Mitchell Hashimoto c84dd13539 builder/amazon/chroot: CommandWrapper
/cc @mwhooker - I changed the interface up a bit to return an error,
since things should return errors in Go (the ui.Error bit was kind of
ghetto because it had no way to bubble that error up except through the
UI).

Using this, I made it so that the communicator uses both a
CommandWrapper and ShellCommand with chroot so that the chroot commannd
is also wrapped (it wasn't before).

I think the functionality of all this is the same but I'd love if you
could look it over and make sure.
2013-09-30 09:33:57 -07:00
Mitchell Hashimoto 5b7c745c3c builder/amazon/chroot: alphebatize the configs
/cc @mwhooker
2013-09-30 09:32:20 -07:00
Mitchell Hashimoto d916604c24 Update CHANGELOG 2013-09-30 09:02:18 -07:00
Mitchell Hashimoto 68fb788c97 Merge pull request #432 from mwhooker/chroot_cmd
build/amazon/chroot: command_wrapper to support sudo-less
2013-09-30 09:00:22 -07:00
Matthew Hooker c92b73899c Merge remote-tracking branch 'origin/master' 2013-09-30 04:46:15 +00:00
Mitchell Hashimoto d10a2596b8 Update CHANGELOG 2013-09-29 15:11:32 -04:00
Mitchell Hashimoto 604fe9ffaa Merge pull request #473 from mitchellh/do-error-message
builder/digitalocean: error message key is "message" not "error_message"
2013-09-29 12:10:00 -07:00
Jack Pearkes 22a3cdb6bc builder/digitalocean: error message key is "message" not "error_message" 2013-09-29 11:01:30 +02:00
Matthew Hooker 6f7aad23f0 comment, formatting. 2013-09-29 08:04:57 +00:00
Mitchell Hashimoto 581a33ab7d Update CHANGELOG 2013-09-28 09:39:34 +02:00
Mitchell Hashimoto 2fa53edb2d Merge pull request #470 from kirikaza/fix_chef_config
provisioner/chef-solo: fix .RolesPath and .DataBagsPath in the config template
2013-09-28 00:38:10 -07:00
Mitchell Hashimoto dc5fd1cc17 Merge pull request #469 from kirikaza/fix_final_sigterm
scripts: build.sh exits without being killed by SIGTERM
2013-09-28 00:37:44 -07:00
Matthew Hooker a84c74318c WIP copying files. 2013-09-28 01:10:33 +00:00
Matthew Hooker 837e641802 move wrapper definitions around. 2013-09-27 22:08:15 +00:00
Matthew Hooker 8857358830 building but there's an exec error. 2013-09-27 20:47:44 +00:00
Kirill Kazakov 7761c9329d provisioner/chef-solo: fix .RolesPath and .DataBagsPath in the config template 2013-09-27 19:13:55 +04:00
Matthew Hooker 40e15c84ef fixing up types. 2013-09-27 11:55:19 +00:00
Matthew Hooker 299ee6efd2 replace command config with wrapper command. 2013-09-27 10:54:53 +00:00
Kirill Kazakov ace8109867 scripts: build.sh exits without being killed by SIGTERM 2013-09-27 14:27:47 +04:00
Mitchell Hashimoto 2da011f46c Merge pull request #466 from podollb/master
website: removing the docs for digital ocean's optional param 'event_delay'
2013-09-26 23:55:25 -07:00
Matthew Hooker 93c2de0af3 fix chroot Upload command. 2013-09-27 02:11:28 +00:00
Matthew Hooker ccd9b56f9c wip 2013-09-26 18:34:01 -07:00
Matthew Hooker 6d158eaa29 bugfixes, wip 2013-09-27 01:28:06 +00:00
Ben Podoll 34cafbe68e removing the docs for digital ocean's optional param 'event_delay' as it is no longer used 2013-09-26 20:24:18 -05:00
Matthew Hooker 308b148bb4 get chroot command from proper place. 2013-09-26 16:39:37 -07:00
Matthew Hooker 6d4b834f61 Merge remote-tracking branch 'origin/master' into 430 2013-09-26 16:24:28 -07:00
Matthew Hooker 75d2d769a9 We're not using this log package. 2013-09-26 02:31:10 -07:00
Matthew Hooker 7e2b9de778 Unit tests. 2013-09-26 02:25:57 -07:00
Matthew Hooker 74adaa534e bugfix. 2013-09-26 01:35:29 -07:00
Matthew Hooker da467b7d67 using blocking cp method. 2013-09-26 01:32:53 -07:00
Matthew Hooker 0659ccdea0 Revert "notes/reorg."
This reverts commit 73c5aec24d.

Conflicts:
	builder/amazon/chroot/step_copy_files.go
2013-09-26 01:16:51 -07:00
Matthew Hooker 95f5cea285 wip 2013-09-26 01:08:06 -07:00
Matthew Hooker 03994f052d Merge branch 'master' into 430 2013-09-26 01:04:42 -07:00
Matthew Hooker a03807f8e8 wip 2013-09-26 00:58:25 -07:00
Matthew Hooker 73c5aec24d notes/reorg. 2013-09-26 00:31:07 -07:00
Matthew Hooker cd0cae207c pass chroot command to /bin/sh
this allows us to specify an arbitrary chroot command, i.e. for sudo
2013-09-25 03:20:22 -07:00
Mitchell Hashimoto 67d8803223 communicator/ssh: dir upload works when dir contains symlinks [Gh-449] 2013-09-25 10:42:49 +02:00
Mitchell Hashimoto cf89e8b783 Update CHANGELOG 2013-09-25 09:29:01 +02:00
Mitchell Hashimoto a3a654732c Merge pull request #459 from bpot/fix_amazon_chroot
builder/amazon-chroot: fix builder-amazon-chroot find states
2013-09-25 00:28:13 -07:00
Mitchell Hashimoto 2e095aa292 Merge pull request #460 from bpot/add_iam_role_to_chroot_documentation
website: document permissions required for amazon-chroot build to function.
2013-09-25 00:27:12 -07:00
Bob Potter 71d3f89b00 Document permissions required for amazon-chroot build to function. 2013-09-25 00:09:10 -05:00
Bob Potter db84c9f547 Update builder-amazon-chroot to work with WaitForState changes in 7c56148f 2013-09-24 23:44:03 -05:00
Mitchell Hashimoto 633d6d8d45 packer: skip the user var if there was an error 2013-09-24 23:01:16 +02:00
Mitchell Hashimoto 1e4d207149 packer: default user var values needn't be strings [GH-456] 2013-09-24 23:00:19 +02:00
Mitchell Hashimoto 6067167713 Merge pull request #458 from mwhooker/fixmake
use interactive shell to run build script
2013-09-24 13:27:18 -07:00
Matthew Hooker e1cb7a5ed4 use interactive shell to run build script. 2013-09-24 01:40:42 -07:00
Mitchell Hashimoto f3ef6fc1da Merge pull request #457 from CpuID/master
website: fix variable name
2013-09-24 00:32:23 -07:00
Nathan Sullivan 59e7c94bbc fix variable name 2013-09-24 16:14:34 +10:00
Mitchell Hashimoto 6509e899d0 Update version for dev 2013-09-22 16:15:55 -05:00
Matthew Hooker 277d54fe69 some documentation. 2013-09-17 06:01:23 -07:00
Matthew Hooker bac3d8ffed Allow user to override chroot command. 2013-09-17 05:58:16 -07:00
31 changed files with 415 additions and 198 deletions
+20
View File
@@ -1,3 +1,23 @@
## 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:
+1 -1
View File
@@ -7,7 +7,7 @@ DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
all: deps
@mkdir -p bin/
@echo "$(OK_COLOR)==> Building$(NO_COLOR)"
@./scripts/build.sh
@bash --norc -i ./scripts/build.sh
deps:
@echo "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)"
+17 -12
View File
@@ -27,16 +27,19 @@ 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
@@ -78,18 +81,14 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.CopyFiles = []string{"/etc/resolv.conf"}
}
if b.config.MountCommand == "" {
b.config.MountCommand = "mount"
if b.config.CommandWrapper == "" {
b.config.CommandWrapper = "{{.Command}}"
}
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)...)
@@ -127,10 +126,8 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
templates := map[string]*string{
"device_path": &b.config.DevicePath,
"mount_command": &b.config.MountCommand,
"source_ami": &b.config.SourceAmi,
"unmount_command": &b.config.UnmountCommand,
"device_path": &b.config.DevicePath,
"source_ami": &b.config.SourceAmi,
}
for n, ptr := range templates {
@@ -167,12 +164,20 @@ 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,3 +82,14 @@ 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
@@ -0,0 +1,15 @@
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)
}
+24 -38
View File
@@ -1,8 +1,10 @@
package chroot
import (
"fmt"
"github.com/mitchellh/packer/packer"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
@@ -13,16 +15,18 @@ import (
// Communicator is a special communicator that works by executing
// commands locally but within a chroot.
type Communicator struct {
Chroot string
Chroot string
CmdWrapper CommandWrapper
}
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
chrootCmdPath, err := exec.LookPath("chroot")
command, err := c.CmdWrapper(
fmt.Sprintf("chroot %s %s", c.Chroot, cmd.Command))
if err != nil {
return err
}
localCmd := exec.Command(chrootCmdPath, c.Chroot, "/bin/sh", "-c", cmd.Command)
localCmd := ShellCommand(command)
localCmd.Stdin = cmd.Stdin
localCmd.Stdout = cmd.Stdout
localCmd.Stderr = cmd.Stderr
@@ -46,7 +50,7 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
}
log.Printf(
"Chroot executation ended with '%d': '%s'",
"Chroot execution exited with '%d': '%s'",
exitStatus, cmd.Command)
cmd.SetExited(exitStatus)
}()
@@ -57,49 +61,31 @@ 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)
f, err := os.Create(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))
if err != nil {
return err
}
defer f.Close()
if _, err := io.Copy(f, r); err != nil {
return err
}
return nil
return ShellCommand(cpCmd).Run()
}
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
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)
// 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
}
log.Printf("Uploading directory '%s' to '%s'", src, dst)
return filepath.Walk(src, walkFn)
return ShellCommand(cpCmd).Run()
}
func (c *Communicator) Download(src string, w io.Writer) error {
+1
View File
@@ -0,0 +1 @@
package chroot
+44
View File
@@ -0,0 +1,44 @@
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.")
}
}
+7 -6
View File
@@ -60,7 +60,8 @@ func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", errors.New("No attachments on volume.")
}
return nil, resp.Volumes[0].Attachments[0].Status, nil
a := resp.Volumes[0].Attachments[0]
return a, a.Status, nil
},
}
@@ -111,12 +112,12 @@ func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
return nil, "", err
}
state := "detached"
if len(resp.Volumes[0].Attachments) > 0 {
state = resp.Volumes[0].Attachments[0].Status
v := resp.Volumes[0]
if len(v.Attachments) > 0 {
return v, v.Attachments[0].Status, nil
} else {
return v, "detached", nil
}
return nil, state, nil
},
}
@@ -15,10 +15,12 @@ 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,
Chroot: mountPath,
CmdWrapper: wrappedCommand,
}
// Provision
+26 -44
View File
@@ -1,12 +1,11 @@
package chroot
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io"
"log"
"os"
"path/filepath"
)
@@ -23,6 +22,8 @@ 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 {
@@ -32,8 +33,20 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
chrootPath := filepath.Join(mountPath, path)
log.Printf("Copying '%s' to '%s'", path, chrootPath)
if err := s.copySingle(chrootPath, path); err != nil {
err := fmt.Errorf("Error copying file: %s", err)
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())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
@@ -54,11 +67,18 @@ func (s *StepCopyFiles) Cleanup(state multistep.StateBag) {
}
}
func (s *StepCopyFiles) CleanupFunc(multistep.StateBag) error {
func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error {
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
if s.files != nil {
for _, file := range s.files {
log.Printf("Removing: %s", file)
if err := os.Remove(file); err != nil {
localCmdText, err := wrappedCommand(fmt.Sprintf("rm -f %s", file))
if err != nil {
return err
}
localCmd := ShellCommand(localCmdText)
if err := localCmd.Run(); err != nil {
return err
}
}
@@ -67,41 +87,3 @@ func (s *StepCopyFiles) CleanupFunc(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
}
+2 -1
View File
@@ -75,7 +75,8 @@ func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", err
}
return nil, resp.Volumes[0].Status, nil
v := resp.Volumes[0]
return v, v.Status, nil
},
}
+19 -7
View File
@@ -7,7 +7,6 @@ import (
"github.com/mitchellh/packer/packer"
"log"
"os"
"os/exec"
"path/filepath"
)
@@ -28,6 +27,7 @@ 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,8 +59,16 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
ui.Say("Mounting the root device...")
stderr := new(bytes.Buffer)
mountCommand := fmt.Sprintf("%s %s %s", config.MountCommand, device, mountPath)
cmd := exec.Command("/bin/sh", "-c", mountCommand)
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)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
@@ -90,12 +98,16 @@ func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
return nil
}
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Unmounting the root device...")
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, s.mountPath)
cmd := exec.Command("/bin/sh", "-c", unmountCommand)
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)
if err := cmd.Run(); err != nil {
return fmt.Errorf("Error unmounting root device: %s", err)
}
+18 -9
View File
@@ -6,7 +6,6 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"os/exec"
)
// StepMountExtra mounts the attached device.
@@ -21,6 +20,7 @@ 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,13 +42,19 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2]))
stderr := new(bytes.Buffer)
mountCommand := fmt.Sprintf(
"%s %s %s %s",
config.MountCommand,
mountCommand, err := wrappedCommand(fmt.Sprintf(
"mount %s %s %s",
flags,
mountInfo[1],
innerPath)
cmd := exec.Command("/bin/sh", "-c", mountCommand)
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)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
@@ -79,15 +85,18 @@ func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error {
return nil
}
config := state.Get("config").(*Config)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
for len(s.mounts) > 0 {
var path string
lastIndex := len(s.mounts) - 1
path, s.mounts = s.mounts[lastIndex], s.mounts[:lastIndex]
unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, path)
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", path))
if err != nil {
return fmt.Errorf("Error creating unmount command: %s", err)
}
stderr := new(bytes.Buffer)
cmd := exec.Command("/bin/sh", "-c", unmountCommand)
cmd := ShellCommand(unmountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf(
+2 -1
View File
@@ -51,7 +51,8 @@ func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
return nil, "", errors.New("No snapshots found.")
}
return nil, resp.Snapshots[0].Status, nil
s := resp.Snapshots[0]
return s, s.Status, nil
},
}
+1 -1
View File
@@ -227,7 +227,7 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin
}
if status == "ERROR" {
status = decodedResponse["error_message"].(string)
status = decodedResponse["message"].(string)
}
lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s",
+21 -2
View File
@@ -408,8 +408,27 @@ func scpUploadDir(root string, fs []os.FileInfo, w io.Writer, r *bufio.Reader) e
for _, fi := range fs {
realPath := filepath.Join(root, fi.Name())
if !fi.IsDir() {
// It is a regular file, just upload it
// 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
f, err := os.Open(realPath)
if err != nil {
return err
+16 -9
View File
@@ -124,18 +124,25 @@ 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
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
}
// 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)
}
variable.Default = def
err = decoder.Decode(v)
if err != nil {
errors = append(errors,
fmt.Errorf("Error decoding default value for user var '%s': %s", k, err))
continue
}
t.Variables[k] = variable
+11 -2
View File
@@ -391,7 +391,8 @@ func TestParseTemplate_Variables(t *testing.T) {
{
"variables": {
"foo": "bar",
"bar": null
"bar": null,
"baz": 27
},
"builders": [{"type": "something"}]
@@ -403,7 +404,7 @@ func TestParseTemplate_Variables(t *testing.T) {
t.Fatalf("err: %s", err)
}
if result.Variables == nil || len(result.Variables) != 2 {
if result.Variables == nil || len(result.Variables) != 3 {
t.Fatalf("bad vars: %#v", result.Variables)
}
@@ -422,6 +423,14 @@ 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) {
+1 -1
View File
@@ -10,7 +10,7 @@ import (
var GitCommit string
// The version of packer.
const Version = "0.3.8"
const Version = "0.3.9"
// 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
+49 -18
View File
@@ -18,10 +18,12 @@ 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"`
@@ -39,15 +41,18 @@ type Provisioner struct {
}
type ConfigTemplate struct {
CookbookPaths string
DataBagsPath string
RolesPath string
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
HasDataBagsPath bool
HasRolesPath bool
HasEnvironmentsPath bool
}
type ExecuteTemplate struct {
@@ -92,10 +97,12 @@ 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,
"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,
}
for n, ptr := range templates {
@@ -174,6 +181,15 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
}
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()
@@ -212,7 +228,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
rolesPath := ""
if p.config.RolesPath != "" {
rolesPath := fmt.Sprintf("%s/roles", p.config.StagingDir)
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)
}
@@ -220,13 +236,21 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
dataBagsPath := ""
if p.config.DataBagsPath != "" {
dataBagsPath := fmt.Sprintf("%s/data_bags", p.config.StagingDir)
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)
}
}
configPath, err := p.createConfig(ui, comm, cookbookPaths, rolesPath, dataBagsPath)
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)
if err != nil {
return fmt.Errorf("Error creating Chef config file: %s", err)
}
@@ -263,7 +287,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) (string, error) {
func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string, rolesPath string, dataBagsPath string, environmentsPath string, chefEnvironment string) (string, error) {
ui.Message("Creating configuration file 'solo.rb'")
cookbook_paths := make([]string, len(p.config.RemoteCookbookPaths)+len(localCookbooks))
@@ -294,11 +318,14 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local
}
configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{
CookbookPaths: strings.Join(cookbook_paths, ","),
RolesPath: rolesPath,
DataBagsPath: dataBagsPath,
HasRolesPath: rolesPath != "",
HasDataBagsPath: dataBagsPath != "",
CookbookPaths: strings.Join(cookbook_paths, ","),
RolesPath: rolesPath,
DataBagsPath: dataBagsPath,
EnvironmentsPath: environmentsPath,
HasRolesPath: rolesPath != "",
HasDataBagsPath: dataBagsPath != "",
HasEnvironmentsPath: environmentsPath != "",
ChefEnvironment: chefEnvironment,
})
if err != nil {
return "", err
@@ -456,4 +483,8 @@ role_path "{{.RolesPath}}"
{{if .HasDataBagsPath}}
data_bag_path "{{.DataBagsPath}}"
{{end}}
{{if .HasEnvironmentsPath}}
environments_path "{{.EnvironmentsPath}}"
chef_environment "{{.ChefEnvironment}}"
{{end}}
`
+38
View File
@@ -19,6 +19,22 @@ 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
@@ -139,6 +155,28 @@ func TestProvisionerPrepare_dataBagsPath(t *testing.T) {
}
}
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
+1 -1
View File
@@ -269,7 +269,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
r = &UnixReader{Reader: r}
}
if err := comm.Upload(p.config.RemotePath, f); err != nil {
if err := comm.Upload(p.config.RemotePath, r); err != nil {
return fmt.Errorf("Error uploading script: %s", err)
}
+2 -32
View File
@@ -2,7 +2,6 @@ package shell
import (
"bufio"
"bytes"
"io"
"sync"
)
@@ -54,35 +53,6 @@ func (r *UnixReader) Read(p []byte) (n int, err error) {
// 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) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\n'); i >= 0 {
// We have a new-line terminated line. Return the line with the newline
return i + 1, dropCR(data[0 : i+1]), nil
}
if atEOF {
// We have a final, non-terminated line
return len(data), dropCR(data), nil
}
if data[len(data)-1] != '\r' {
// We have a normal line, just let it tokenize
return len(data), data, nil
}
// We need more data
return 0, nil, nil
}
func dropCR(data []byte) []byte {
if len(data) > 0 && data[len(data)-2] == '\r' {
// Trim off the last byte and replace it with a '\n'
data = data[0 : len(data)-1]
data[len(data)-1] = '\n'
}
return data
advance, token, err = bufio.ScanLines(data, atEOF)
return advance, append(token, "\n"...), err
}
+18
View File
@@ -31,3 +31,21 @@ func TestUnixReader(t *testing.T) {
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())
}
}
+3
View File
@@ -92,3 +92,6 @@ for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do
done
waitAll
# Reset signal trapping to avoid "Terminated: 15" at the end
trap - SIGINT SIGTERM EXIT
@@ -111,9 +111,11 @@ Optional:
of the source AMI will be attached. This defaults to "" (empty string),
which forces Packer to find an open device automatically.
* `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.
* `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_path` (string) - The path where the volume will be mounted. This is
where the chroot environment will be. This defaults to
@@ -123,9 +125,6 @@ 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:
@@ -184,3 +183,37 @@ 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,11 +35,6 @@ 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."
@@ -42,6 +42,8 @@ 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
```
@@ -102,7 +102,7 @@ root privileges without worrying about password prompts.
## Default Environmental Variables
In addition to being able to specify custom environmental variables using
the `environmental_vars` configuration, the provisioner automatically
the `environment_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.
@@ -45,6 +45,8 @@ 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
```