Compare commits
159 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e61157c5c | |||
| 7c4bd4f2d6 | |||
| 9c6cc829fa | |||
| 53b0223e20 | |||
| 15e80316fe | |||
| 485d5b86d0 | |||
| b0d5bff98f | |||
| 8c51316897 | |||
| 3bcfeafa15 | |||
| 3d461195e3 | |||
| e6a1fc448a | |||
| d1aefb38bb | |||
| e0a66fbe30 | |||
| 80db9efef5 | |||
| 8853e7a17e | |||
| ad38604390 | |||
| 64f4ee380d | |||
| 3bf88e2dd7 | |||
| c8508ade17 | |||
| 4e8db89403 | |||
| 1b8551d843 | |||
| 46ec8f758b | |||
| 46e49b745e | |||
| 4b29fab843 | |||
| 82e21622ef | |||
| e4dda36a1d | |||
| 1216fc1cbb | |||
| 2b4735e825 | |||
| 422e05d8b9 | |||
| 4c273e33c5 | |||
| b24fcbc800 | |||
| 852c10264b | |||
| ef59ee41a8 | |||
| da7febbfb9 | |||
| 995b4ffc80 | |||
| 648c59ba54 | |||
| fbc4a255d0 | |||
| caf04a572e | |||
| bfa4a055f5 | |||
| 4c1db34ac9 | |||
| 116692212c | |||
| 00921966ee | |||
| 5642fa01c8 | |||
| 53ba20041c | |||
| 14393b91b7 | |||
| dc615ff317 | |||
| d728ca24f9 | |||
| ff366ca685 | |||
| 744c52f685 | |||
| e6c94f449f | |||
| 844fde01a7 | |||
| 7411087a5c | |||
| 4b6bbfd723 | |||
| 898e468edd | |||
| 5de3a948fa | |||
| b1ccdd4e11 | |||
| 5a267ef73f | |||
| 8f6b2e4d32 | |||
| ce4db65fa0 | |||
| 1ff52979fe | |||
| 5343bc42a0 | |||
| f99797eba3 | |||
| 27a07cc9aa | |||
| e11b6617f3 | |||
| 5fd00cfd3a | |||
| f5b0b4f9d7 | |||
| 2f15d0e14f | |||
| f6245ae128 | |||
| d0a2dac8f0 | |||
| aa6a9cc2e8 | |||
| 62207912d2 | |||
| 720a72dc30 | |||
| 52b310aeda | |||
| 6f8076ca1d | |||
| 18f34173cf | |||
| 4bd3afb3ee | |||
| 0820da6039 | |||
| 02c20af35e | |||
| 8cf245eaa5 | |||
| 6740f99f26 | |||
| fd3f9e9e4c | |||
| 838e60cf08 | |||
| ab73e65a59 | |||
| 377493db4f | |||
| ffe1e5f57b | |||
| 005c485bfb | |||
| f0a0816736 | |||
| 6f8c915ef4 | |||
| cccf3ddc79 | |||
| 167bdd9a46 | |||
| 997b81da21 | |||
| 6b7f59216e | |||
| 089d284067 | |||
| 6f4db32418 | |||
| 21002e04a1 | |||
| df4c844493 | |||
| 613322d98b | |||
| 7ab4490733 | |||
| 36be9a9bf6 | |||
| 44c6103fd0 | |||
| f79f113bcc | |||
| f3a4d44066 | |||
| 998712250c | |||
| 759ff1ace1 | |||
| a3a2ace843 | |||
| 9f2399516a | |||
| 778657e995 | |||
| 9dc55ee56c | |||
| bec7b26836 | |||
| 462e48cac4 | |||
| 9bb9f02b99 | |||
| 710b6a41ec | |||
| 713f466670 | |||
| 22aad9c87c | |||
| df85c67e4a | |||
| 1e9cc89f73 | |||
| 4358830953 | |||
| 121e9791a3 | |||
| af492621de | |||
| 5a70c82e25 | |||
| 702f299343 | |||
| 7c04d634f9 | |||
| 3f0c4b0e19 | |||
| 3f49f1a6d0 | |||
| e5f0cbe298 | |||
| 726c4a68ef | |||
| 90a27bc57b | |||
| c189c7ed12 | |||
| b329323bb2 | |||
| c6fde36b5e | |||
| 8046d2635a | |||
| 8c341b0c78 | |||
| ce0322662d | |||
| 31731414be | |||
| 467474910c | |||
| 6110079401 | |||
| 1812efe3b5 | |||
| 1400645d69 | |||
| 6a940d9ea8 | |||
| d1cdd9150b | |||
| 0bff938880 | |||
| 85ec53e76c | |||
| ac8548d998 | |||
| 1ebb1b7ef6 | |||
| 728f9ecdbd | |||
| b8f1d484be | |||
| 9a37de618d | |||
| a5054cf314 | |||
| 880dd75675 | |||
| 2e4585bc9c | |||
| 1cc36f11bc | |||
| 1b0d316456 | |||
| 8fe0733676 | |||
| e5f5ba67c4 | |||
| b883abe1e2 | |||
| af3e922b26 | |||
| 3654a9a9f5 | |||
| 9bdd5a927a | |||
| beb97af6ce |
@@ -1,3 +1,41 @@
|
||||
## 0.2.3 (August 7, 2013)
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* builder/amazon/all: Added Amazon AMI tag support [GH-233]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: Absolute/relative filepaths on Windows now work for iso_url
|
||||
and other settings. [GH-240]
|
||||
* builder/amazon/all: instance info is refreshed while waiting for SSH,
|
||||
allowing Packer to see updated IP/DNS info. [GH-243]
|
||||
|
||||
## 0.2.2 (August 1, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* New builder: `amazon-chroot` can create EBS-backed AMIs without launching
|
||||
a new EC2 instance. This can shave minutes off of the AMI creation process.
|
||||
See the docs for more info.
|
||||
* New provisioner: `salt-masterless` will provision the node using Salt
|
||||
without a master.
|
||||
* The `vmware` builder now works with Workstation 9 on Windows. [GH-222]
|
||||
* The `vmware` builder now works with Player 5 on Linux. [GH-190]
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* core: Colors won't be outputted on Windows unless in Cygwin.
|
||||
* builder/amazon/all: Added `iam_instance_profile` to launch the source
|
||||
image with a given IAM profile. [GH-226]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* builder/virtualbox,vmware: relative paths work properly as URL
|
||||
configurations. [GH-215]
|
||||
* builder/virtualbox,vmware: fix race condition in deleting the output
|
||||
directory on Windows by retrying.
|
||||
|
||||
## 0.2.1 (July 26, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// The chroot package is able to create an Amazon AMI without requiring
|
||||
// the launch of a new instance for every build. It does this by attaching
|
||||
// and mounting the root volume of another AMI and chrooting into that
|
||||
// directory. It then creates an AMI from that attached drive.
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"runtime"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
const BuilderId = "mitchellh.amazon.chroot"
|
||||
|
||||
// Config is the configuration that is chained through the steps and
|
||||
// settable from the template.
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
|
||||
AMIName string `mapstructure:"ami_name"`
|
||||
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
md, err := common.DecodeConfig(&b.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if b.config.ChrootMounts == nil {
|
||||
b.config.ChrootMounts = make([][]string, 0)
|
||||
}
|
||||
|
||||
if b.config.CopyFiles == nil {
|
||||
b.config.CopyFiles = make([]string, 0)
|
||||
}
|
||||
|
||||
if len(b.config.ChrootMounts) == 0 {
|
||||
b.config.ChrootMounts = [][]string{
|
||||
[]string{"proc", "proc", "/proc"},
|
||||
[]string{"sysfs", "sysfs", "/sys"},
|
||||
[]string{"bind", "/dev", "/dev"},
|
||||
[]string{"devpts", "devpts", "/dev/pts"},
|
||||
[]string{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
|
||||
}
|
||||
}
|
||||
|
||||
if len(b.config.CopyFiles) == 0 {
|
||||
b.config.CopyFiles = []string{"/etc/resolv.conf"}
|
||||
}
|
||||
|
||||
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()...)
|
||||
|
||||
if b.config.AMIName == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("ami_name must be specified"))
|
||||
} else {
|
||||
_, err = template.New("ami").Parse(b.config.AMIName)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing ami_name: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
for _, mounts := range b.config.ChrootMounts {
|
||||
if len(mounts) != 3 {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Each chroot_mounts entry should be three elements."))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.SourceAmi == "" {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New("source_ami is required."))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
log.Printf("Config: %+v", b.config)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil, errors.New("The amazon-chroot builder only works on Linux environments.")
|
||||
}
|
||||
|
||||
region, err := b.config.Region()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth, err := b.config.AccessConfig.Auth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ec2conn := ec2.New(auth, region)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := make(map[string]interface{})
|
||||
state["config"] = &b.config
|
||||
state["ec2"] = ec2conn
|
||||
state["hook"] = hook
|
||||
state["ui"] = ui
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&StepInstanceInfo{},
|
||||
&StepSourceAMIInfo{},
|
||||
&StepFlock{},
|
||||
&StepPrepareDevice{},
|
||||
&StepCreateVolume{},
|
||||
&StepAttachVolume{},
|
||||
&StepEarlyUnflock{},
|
||||
&StepMountDevice{},
|
||||
&StepMountExtra{},
|
||||
&StepCopyFiles{},
|
||||
&StepChrootProvision{},
|
||||
&StepEarlyCleanup{},
|
||||
&StepSnapshot{},
|
||||
&StepRegisterAMI{},
|
||||
}
|
||||
|
||||
// Run!
|
||||
if b.config.PackerDebug {
|
||||
b.runner = &multistep.DebugRunner{
|
||||
Steps: steps,
|
||||
PauseFn: common.MultistepDebugFn(ui),
|
||||
}
|
||||
} else {
|
||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||
}
|
||||
|
||||
b.runner.Run(state)
|
||||
|
||||
// If there was an error, return that
|
||||
if rawErr, ok := state["error"]; ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// If there are no AMIs, then just return
|
||||
if _, ok := state["amis"]; !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Build the artifact and return it
|
||||
artifact := &awscommon.Artifact{
|
||||
Amis: state["amis"].(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Conn: ec2conn,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"ami_name": "foo",
|
||||
"source_ami": "foo",
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packer.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
config["ami_name"] = "foo {{"
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "ami_name")
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ChrootMounts(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["chroot_mounts"] = nil
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
|
||||
config["chroot_mounts"] = [][]string{
|
||||
[]string{"bad"},
|
||||
}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
func TestBuilderPrepare_SourceAmi(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["source_ami"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["source_ami"] = "foo"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package chroot
|
||||
|
||||
// Cleanup is an interface that some steps implement for early cleanup.
|
||||
type Cleanup interface {
|
||||
CleanupFunc(map[string]interface{}) error
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Communicator is a special communicator that works by executing
|
||||
// commands locally but within a chroot.
|
||||
type Communicator struct {
|
||||
Chroot string
|
||||
}
|
||||
|
||||
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
|
||||
chrootCmdPath, err := exec.LookPath("chroot")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
localCmd := exec.Command(chrootCmdPath, c.Chroot, "/bin/sh", "-c", cmd.Command)
|
||||
localCmd.Stdin = cmd.Stdin
|
||||
localCmd.Stdout = cmd.Stdout
|
||||
localCmd.Stderr = cmd.Stderr
|
||||
log.Printf("Executing: %s %#v", localCmd.Path, localCmd.Args)
|
||||
if err := localCmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
exitStatus := 0
|
||||
if err := localCmd.Wait(); err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
exitStatus = 1
|
||||
|
||||
// There is no process-independent way to get the REAL
|
||||
// exit status so we just try to go deeper.
|
||||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||
exitStatus = status.ExitStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf(
|
||||
"Chroot executation ended with '%d': '%s'",
|
||||
exitStatus, cmd.Command)
|
||||
cmd.SetExited(exitStatus)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := io.Copy(f, r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Communicator) Download(src string, w io.Writer) error {
|
||||
src = filepath.Join(c.Chroot, src)
|
||||
log.Printf("Downloading from chroot dir: %s", src)
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := io.Copy(w, f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommunicator_ImplementsCommunicator(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Communicator{}
|
||||
if _, ok := raw.(packer.Communicator); !ok {
|
||||
t.Fatalf("Communicator should be a communicator")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AvailableDevice finds an available device and returns it. Note that
|
||||
// you should externally hold a flock or something in order to guarantee
|
||||
// that this device is available across processes.
|
||||
func AvailableDevice() (string, error) {
|
||||
prefix, err := devicePrefix()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
letters := "fghijklmnop"
|
||||
for _, letter := range letters {
|
||||
for i := 1; i < 16; i++ {
|
||||
device := fmt.Sprintf("/dev/%s%c%d", prefix, letter, i)
|
||||
if _, err := os.Stat(device); err != nil {
|
||||
return device, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("available device could not be found")
|
||||
}
|
||||
|
||||
// devicePrefix returns the prefix ("sd" or "xvd" or so on) of the devices
|
||||
// on the system.
|
||||
func devicePrefix() (string, error) {
|
||||
available := []string{"sd", "xvd"}
|
||||
|
||||
f, err := os.Open("/sys/block")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dirs, err := f.Readdirnames(-1)
|
||||
if dirs != nil && len(dirs) > 0 {
|
||||
for _, dir := range dirs {
|
||||
dirBase := filepath.Base(dir)
|
||||
for _, prefix := range available {
|
||||
if strings.HasPrefix(dirBase, prefix) {
|
||||
return prefix, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", errors.New("device prefix could not be detected")
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// +build windows
|
||||
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
func lockFile(*os.File) error {
|
||||
return errors.New("not supported on Windows")
|
||||
}
|
||||
|
||||
func unlockFile(f *os.File) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// +build !windows
|
||||
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// See: http://linux.die.net/include/sys/file.h
|
||||
const LOCK_EX = 2
|
||||
const LOCK_NB = 4
|
||||
const LOCK_UN = 8
|
||||
|
||||
func lockFile(f *os.File) error {
|
||||
err := syscall.Flock(int(f.Fd()), LOCK_EX|LOCK_NB)
|
||||
if err != nil {
|
||||
errno, ok := err.(syscall.Errno)
|
||||
if ok && errno == syscall.EWOULDBLOCK {
|
||||
return errors.New("file already locked")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unlockFile(f *os.File) error {
|
||||
return syscall.Flock(int(f.Fd()), LOCK_UN)
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StepAttachVolume attaches the previously created volume to an
|
||||
// available device location.
|
||||
//
|
||||
// Produces:
|
||||
// device string - The location where the volume was attached.
|
||||
// attach_cleanup CleanupFunc
|
||||
type StepAttachVolume struct {
|
||||
attached bool
|
||||
volumeId string
|
||||
}
|
||||
|
||||
func (s *StepAttachVolume) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
device := state["device"].(string)
|
||||
instance := state["instance"].(*ec2.Instance)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
volumeId := state["volume_id"].(string)
|
||||
|
||||
// For the API call, it expects "sd" prefixed devices.
|
||||
attachVolume := strings.Replace(device, "/xvd", "/sd", 1)
|
||||
|
||||
ui.Say(fmt.Sprintf("Attaching the root volume to %s", attachVolume))
|
||||
_, err := ec2conn.AttachVolume(volumeId, instance.InstanceId, attachVolume)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error attaching volume: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Mark that we attached it so we can detach it later
|
||||
s.attached = true
|
||||
s.volumeId = volumeId
|
||||
|
||||
// Wait for the volume to become attached
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Conn: ec2conn,
|
||||
Pending: []string{"attaching"},
|
||||
StepState: state,
|
||||
Target: "attached",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.Volumes([]string{volumeId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(resp.Volumes[0].Attachments) == 0 {
|
||||
return nil, "", errors.New("No attachments on volume.")
|
||||
}
|
||||
|
||||
return nil, resp.Volumes[0].Attachments[0].Status, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["attach_cleanup"] = s
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepAttachVolume) Cleanup(state map[string]interface{}) {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
if err := s.CleanupFunc(state); err != nil {
|
||||
ui.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepAttachVolume) CleanupFunc(state map[string]interface{}) error {
|
||||
if !s.attached {
|
||||
return nil
|
||||
}
|
||||
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say("Detaching EBS volume...")
|
||||
_, err := ec2conn.DetachVolume(s.volumeId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error detaching EBS volume: %s", err)
|
||||
}
|
||||
|
||||
s.attached = false
|
||||
|
||||
// Wait for the volume to detach
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Conn: ec2conn,
|
||||
Pending: []string{"attaching", "attached", "detaching"},
|
||||
StepState: state,
|
||||
Target: "detached",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.Volumes([]string{s.volumeId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
state := "detached"
|
||||
if len(resp.Volumes[0].Attachments) > 0 {
|
||||
state = resp.Volumes[0].Attachments[0].Status
|
||||
}
|
||||
|
||||
return nil, state, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error waiting for volume: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package chroot
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAttachVolumeCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepAttachVolume)
|
||||
if _, ok := raw.(Cleanup); !ok {
|
||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
// StepChrootProvision provisions the instance within a chroot.
|
||||
type StepChrootProvision struct {
|
||||
mounts []string
|
||||
}
|
||||
|
||||
func (s *StepChrootProvision) Run(state map[string]interface{}) multistep.StepAction {
|
||||
hook := state["hook"].(packer.Hook)
|
||||
mountPath := state["mount_path"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
// Create our communicator
|
||||
comm := &Communicator{
|
||||
Chroot: mountPath,
|
||||
}
|
||||
|
||||
// Provision
|
||||
log.Println("Running the provision hook")
|
||||
if err := hook.Run(packer.HookProvision, ui, comm, nil); err != nil {
|
||||
state["error"] = err
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepChrootProvision) Cleanup(state map[string]interface{}) {}
|
||||
@@ -0,0 +1,107 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// StepCopyFiles copies some files from the host into the chroot environment.
|
||||
//
|
||||
// Produces:
|
||||
// copy_files_cleanup CleanupFunc - A function to clean up the copied files
|
||||
// early.
|
||||
type StepCopyFiles struct {
|
||||
files []string
|
||||
}
|
||||
|
||||
func (s *StepCopyFiles) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*Config)
|
||||
mountPath := state["mount_path"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
s.files = make([]string, 0, len(config.CopyFiles))
|
||||
if len(config.CopyFiles) > 0 {
|
||||
ui.Say("Copying files from host to chroot...")
|
||||
for _, path := range config.CopyFiles {
|
||||
ui.Message(path)
|
||||
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)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.files = append(s.files, chrootPath)
|
||||
}
|
||||
}
|
||||
|
||||
state["copy_files_cleanup"] = s
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCopyFiles) Cleanup(state map[string]interface{}) {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
if err := s.CleanupFunc(state); err != nil {
|
||||
ui.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepCopyFiles) CleanupFunc(map[string]interface{}) error {
|
||||
if s.files != nil {
|
||||
for _, file := range s.files {
|
||||
log.Printf("Removing: %s", file)
|
||||
if err := os.Remove(file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package chroot
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCopyFilesCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepCopyFiles)
|
||||
if _, ok := raw.(Cleanup); !ok {
|
||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
// StepCreateVolume creates a new volume from the snapshot of the root
|
||||
// device of the AMI.
|
||||
//
|
||||
// Produces:
|
||||
// volume_id string - The ID of the created volume
|
||||
type StepCreateVolume struct {
|
||||
volumeId string
|
||||
}
|
||||
|
||||
func (s *StepCreateVolume) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
image := state["source_image"].(*ec2.Image)
|
||||
instance := state["instance"].(*ec2.Instance)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
// Determine the root device snapshot
|
||||
log.Printf("Searching for root device of the image (%s)", image.RootDeviceName)
|
||||
var rootDevice *ec2.BlockDeviceMapping
|
||||
for _, device := range image.BlockDevices {
|
||||
if device.DeviceName == image.RootDeviceName {
|
||||
rootDevice = &device
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if rootDevice == nil {
|
||||
err := fmt.Errorf("Couldn't find root device!")
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Creating the root volume...")
|
||||
createVolume := &ec2.CreateVolume{
|
||||
AvailZone: instance.AvailZone,
|
||||
Size: rootDevice.VolumeSize,
|
||||
SnapshotId: rootDevice.SnapshotId,
|
||||
VolumeType: rootDevice.VolumeType,
|
||||
IOPS: rootDevice.IOPS,
|
||||
}
|
||||
log.Printf("Create args: %#v", createVolume)
|
||||
|
||||
createVolumeResp, err := ec2conn.CreateVolume(createVolume)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating root volume: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the volume ID so we remember to delete it later
|
||||
s.volumeId = createVolumeResp.VolumeId
|
||||
log.Printf("Volume ID: %s", s.volumeId)
|
||||
|
||||
// Wait for the volume to become ready
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Conn: ec2conn,
|
||||
Pending: []string{"creating"},
|
||||
StepState: state,
|
||||
Target: "available",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.Volumes([]string{s.volumeId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return nil, resp.Volumes[0].Status, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for volume: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["volume_id"] = s.volumeId
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCreateVolume) Cleanup(state map[string]interface{}) {
|
||||
if s.volumeId == "" {
|
||||
return
|
||||
}
|
||||
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say("Deleting the created EBS volume...")
|
||||
_, err := ec2conn.DeleteVolume(s.volumeId)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting EBS volume: %s", err))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
// StepEarlyCleanup performs some of the cleanup steps early in order to
|
||||
// prepare for snapshotting and creating an AMI.
|
||||
type StepEarlyCleanup struct{}
|
||||
|
||||
func (s *StepEarlyCleanup) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
cleanupKeys := []string{
|
||||
"copy_files_cleanup",
|
||||
"mount_extra_cleanup",
|
||||
"mount_device_cleanup",
|
||||
"attach_cleanup",
|
||||
}
|
||||
|
||||
for _, key := range cleanupKeys {
|
||||
c := state[key].(Cleanup)
|
||||
log.Printf("Running cleanup func: %s", key)
|
||||
if err := c.CleanupFunc(state); err != nil {
|
||||
err := fmt.Errorf("Error cleaning up: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepEarlyCleanup) Cleanup(state map[string]interface{}) {}
|
||||
@@ -0,0 +1,28 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
// StepEarlyUnflock unlocks the flock.
|
||||
type StepEarlyUnflock struct{}
|
||||
|
||||
func (s *StepEarlyUnflock) Run(state map[string]interface{}) multistep.StepAction {
|
||||
cleanup := state["flock_cleanup"].(Cleanup)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
log.Println("Unlocking file lock...")
|
||||
if err := cleanup.CleanupFunc(state); err != nil {
|
||||
err := fmt.Errorf("Error unlocking file lock: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepEarlyUnflock) Cleanup(state map[string]interface{}) {}
|
||||
@@ -0,0 +1,72 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// StepFlock provisions the instance within a chroot.
|
||||
//
|
||||
// Produces:
|
||||
// flock_cleanup Cleanup - To perform early cleanup
|
||||
type StepFlock struct {
|
||||
fh *os.File
|
||||
}
|
||||
|
||||
func (s *StepFlock) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
lockfile := "/var/lock/packer-chroot/lock"
|
||||
if err := os.MkdirAll(filepath.Dir(lockfile), 0755); err != nil {
|
||||
err := fmt.Errorf("Error creating lock: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Obtaining lock: %s", lockfile)
|
||||
f, err := os.Create(lockfile)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating lock: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// LOCK!
|
||||
if err := lockFile(f); err != nil {
|
||||
err := fmt.Errorf("Error creating lock: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the file handle, we can't close it because we need to hold
|
||||
// the lock.
|
||||
s.fh = f
|
||||
|
||||
state["flock_cleanup"] = s
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepFlock) Cleanup(state map[string]interface{}) {
|
||||
s.CleanupFunc(state)
|
||||
}
|
||||
|
||||
func (s *StepFlock) CleanupFunc(state map[string]interface{}) error {
|
||||
if s.fh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Unlocking: %s", s.fh.Name())
|
||||
if err := unlockFile(s.fh); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.fh = nil
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package chroot
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFlockCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepFlock)
|
||||
if _, ok := raw.(Cleanup); !ok {
|
||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
// StepInstanceInfo verifies that this builder is running on an EC2 instance.
|
||||
type StepInstanceInfo struct{}
|
||||
|
||||
func (s *StepInstanceInfo) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
// Get our own instance ID
|
||||
ui.Say("Gathering information about this EC2 instance...")
|
||||
instanceIdBytes, err := aws.GetMetaData("instance-id")
|
||||
if err != nil {
|
||||
log.Printf("Error: %s", err)
|
||||
err := fmt.Errorf(
|
||||
"Error retrieving the ID of the instance Packer is running on.\n" +
|
||||
"Please verify Packer is running on a proper AWS EC2 instance.")
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
instanceId := string(instanceIdBytes)
|
||||
log.Printf("Instance ID: %s", instanceId)
|
||||
|
||||
// Query the entire instance metadata
|
||||
instancesResp, err := ec2conn.Instances([]string{instanceId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error getting instance data: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(instancesResp.Reservations) == 0 {
|
||||
err := fmt.Errorf("Error getting instance data: no instance found.")
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
instance := &instancesResp.Reservations[0].Instances[0]
|
||||
state["instance"] = instance
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepInstanceInfo) Cleanup(map[string]interface{}) {}
|
||||
@@ -0,0 +1,103 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type mountPathData struct {
|
||||
Device string
|
||||
}
|
||||
|
||||
// StepMountDevice mounts the attached device.
|
||||
//
|
||||
// Produces:
|
||||
// mount_path string - The location where the volume was mounted.
|
||||
// mount_device_cleanup CleanupFunc - To perform early cleanup
|
||||
type StepMountDevice struct {
|
||||
mountPath string
|
||||
}
|
||||
|
||||
func (s *StepMountDevice) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*Config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
device := state["device"].(string)
|
||||
|
||||
mountPathRaw := new(bytes.Buffer)
|
||||
t := template.Must(template.New("mountPath").Parse(config.MountPath))
|
||||
t.Execute(mountPathRaw, &mountPathData{
|
||||
Device: filepath.Base(device),
|
||||
})
|
||||
|
||||
var err error
|
||||
mountPath := mountPathRaw.String()
|
||||
mountPath, err = filepath.Abs(mountPath)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error preparing mount directory: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Mount path: %s", mountPath)
|
||||
|
||||
if err := os.MkdirAll(mountPath, 0755); err != nil {
|
||||
err := fmt.Errorf("Error creating mount directory: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
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)
|
||||
cmd.Stderr = stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
err := fmt.Errorf(
|
||||
"Error mounting root volume: %s\nStderr: %s", err, stderr.String())
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the mount path so we remember to unmount it later
|
||||
s.mountPath = mountPath
|
||||
state["mount_path"] = s.mountPath
|
||||
state["mount_device_cleanup"] = s
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepMountDevice) Cleanup(state map[string]interface{}) {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
if err := s.CleanupFunc(state); err != nil {
|
||||
ui.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepMountDevice) CleanupFunc(state map[string]interface{}) error {
|
||||
if s.mountPath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
config := state["config"].(*Config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
ui.Say("Unmounting the root device...")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
s.mountPath = ""
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package chroot
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMountDeviceCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepMountDevice)
|
||||
if _, ok := raw.(Cleanup); !ok {
|
||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// StepMountExtra mounts the attached device.
|
||||
//
|
||||
// Produces:
|
||||
// mount_extra_cleanup CleanupFunc - To perform early cleanup
|
||||
type StepMountExtra struct {
|
||||
mounts []string
|
||||
}
|
||||
|
||||
func (s *StepMountExtra) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*Config)
|
||||
mountPath := state["mount_path"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
s.mounts = make([]string, 0, len(config.ChrootMounts))
|
||||
|
||||
ui.Say("Mounting additional paths within the chroot...")
|
||||
for _, mountInfo := range config.ChrootMounts {
|
||||
innerPath := mountPath + mountInfo[2]
|
||||
|
||||
if err := os.MkdirAll(innerPath, 0755); err != nil {
|
||||
err := fmt.Errorf("Error creating mount directory: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
flags := "-t " + mountInfo[0]
|
||||
if mountInfo[0] == "bind" {
|
||||
flags = "--bind"
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2]))
|
||||
stderr := new(bytes.Buffer)
|
||||
mountCommand := fmt.Sprintf(
|
||||
"%s %s %s %s",
|
||||
config.MountCommand,
|
||||
flags,
|
||||
mountInfo[1],
|
||||
innerPath)
|
||||
cmd := exec.Command("/bin/sh", "-c", mountCommand)
|
||||
cmd.Stderr = stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
err := fmt.Errorf(
|
||||
"Error mounting: %s\nStderr: %s", err, stderr.String())
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.mounts = append(s.mounts, innerPath)
|
||||
}
|
||||
|
||||
state["mount_extra_cleanup"] = s
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepMountExtra) Cleanup(state map[string]interface{}) {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
if err := s.CleanupFunc(state); err != nil {
|
||||
ui.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepMountExtra) CleanupFunc(state map[string]interface{}) error {
|
||||
if s.mounts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
config := state["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 := fmt.Sprintf("%s %s", config.UnmountCommand, path)
|
||||
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd := exec.Command("/bin/sh", "-c", unmountCommand)
|
||||
cmd.Stderr = stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf(
|
||||
"Error unmounting device: %s\nStderr: %s", err, stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
s.mounts = nil
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package chroot
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMountExtraCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepMountExtra)
|
||||
if _, ok := raw.(Cleanup); !ok {
|
||||
t.Fatalf("cleanup func should be a CleanupFunc")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// StepPrepareDevice finds an available device and sets it.
|
||||
type StepPrepareDevice struct {
|
||||
mounts []string
|
||||
}
|
||||
|
||||
func (s *StepPrepareDevice) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*Config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
device := config.DevicePath
|
||||
if device == "" {
|
||||
var err error
|
||||
log.Println("Device path not specified, searching for available device...")
|
||||
device, err = AvailableDevice()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error finding available device: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(device); err == nil {
|
||||
err := fmt.Errorf("Device is in use: %s", device)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Device: %s", device)
|
||||
state["device"] = device
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepPrepareDevice) Cleanup(state map[string]interface{}) {}
|
||||
@@ -0,0 +1,84 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"strconv"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
type amiNameData struct {
|
||||
CreateTime string
|
||||
}
|
||||
|
||||
// StepRegisterAMI creates the AMI.
|
||||
type StepRegisterAMI struct{}
|
||||
|
||||
func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*Config)
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
image := state["source_image"].(*ec2.Image)
|
||||
snapshotId := state["snapshot_id"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
// Parse the name of the AMI
|
||||
amiNameBuf := new(bytes.Buffer)
|
||||
tData := amiNameData{
|
||||
strconv.FormatInt(time.Now().UTC().Unix(), 10),
|
||||
}
|
||||
|
||||
t := template.Must(template.New("ami").Parse(config.AMIName))
|
||||
t.Execute(amiNameBuf, tData)
|
||||
amiName := amiNameBuf.String()
|
||||
|
||||
ui.Say("Registering the AMI...")
|
||||
blockDevices := make([]ec2.BlockDeviceMapping, len(image.BlockDevices))
|
||||
for i, device := range image.BlockDevices {
|
||||
newDevice := device
|
||||
if newDevice.DeviceName == image.RootDeviceName {
|
||||
newDevice.SnapshotId = snapshotId
|
||||
}
|
||||
|
||||
blockDevices[i] = newDevice
|
||||
}
|
||||
|
||||
registerOpts := &ec2.RegisterImage{
|
||||
Name: amiName,
|
||||
Architecture: image.Architecture,
|
||||
KernelId: image.KernelId,
|
||||
RamdiskId: image.RamdiskId,
|
||||
RootDeviceName: image.RootDeviceName,
|
||||
BlockDevices: blockDevices,
|
||||
}
|
||||
|
||||
registerResp, err := ec2conn.RegisterImage(registerOpts)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error registering AMI: %s", err)
|
||||
ui.Error(state["error"].(error).Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the AMI ID in the state
|
||||
ui.Say(fmt.Sprintf("AMI: %s", registerResp.ImageId))
|
||||
amis := make(map[string]string)
|
||||
amis[ec2conn.Region.Name] = registerResp.ImageId
|
||||
state["amis"] = amis
|
||||
|
||||
// Wait for the image to become ready
|
||||
ui.Say("Waiting for AMI to become ready...")
|
||||
if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
|
||||
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepRegisterAMI) Cleanup(state map[string]interface{}) {}
|
||||
@@ -0,0 +1,87 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepSnapshot creates a snapshot of the created volume.
|
||||
//
|
||||
// Produces:
|
||||
// snapshot_id string - ID of the created snapshot
|
||||
type StepSnapshot struct {
|
||||
snapshotId string
|
||||
}
|
||||
|
||||
func (s *StepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
volumeId := state["volume_id"].(string)
|
||||
|
||||
ui.Say("Creating snapshot...")
|
||||
createSnapResp, err := ec2conn.CreateSnapshot(volumeId, "")
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating snapshot: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the snapshot ID so we can delete it later
|
||||
s.snapshotId = createSnapResp.Id
|
||||
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
|
||||
|
||||
// Wait for the snapshot to be ready
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Conn: ec2conn,
|
||||
Pending: []string{"pending"},
|
||||
StepState: state,
|
||||
Target: "completed",
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := ec2conn.Snapshots([]string{s.snapshotId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(resp.Snapshots) == 0 {
|
||||
return nil, "", errors.New("No snapshots found.")
|
||||
}
|
||||
|
||||
return nil, resp.Snapshots[0].Status, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for snapshot: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["snapshot_id"] = s.snapshotId
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepSnapshot) Cleanup(state map[string]interface{}) {
|
||||
if s.snapshotId == "" {
|
||||
return
|
||||
}
|
||||
|
||||
_, cancelled := state[multistep.StateCancelled]
|
||||
_, halted := state[multistep.StateHalted]
|
||||
|
||||
if cancelled || halted {
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
ui.Say("Removing snapshot since we cancelled or halted...")
|
||||
_, err := ec2conn.DeleteSnapshots([]string{s.snapshotId})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepSourceAMIInfo extracts critical information from the source AMI
|
||||
// that is used throughout the AMI creation process.
|
||||
//
|
||||
// Produces:
|
||||
// source_image *ec2.Image - the source AMI info
|
||||
type StepSourceAMIInfo struct{}
|
||||
|
||||
func (s *StepSourceAMIInfo) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*Config)
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say("Inspecting the source AMI...")
|
||||
imageResp, err := ec2conn.Images([]string{config.SourceAmi}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying AMI: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(imageResp.Images) == 0 {
|
||||
err := fmt.Errorf("Source AMI '%s' was not found!", config.SourceAmi)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
image := &imageResp.Images[0]
|
||||
|
||||
// It must be EBS-backed otherwise the build won't work
|
||||
if image.RootDeviceType != "ebs" {
|
||||
err := fmt.Errorf("The root device of the source AMI must be EBS-backed.")
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["source_image"] = image
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepSourceAMIInfo) Cleanup(map[string]interface{}) {}
|
||||
@@ -1,13 +1,17 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// AccessConfig is for common configuration related to AWS access
|
||||
type AccessConfig struct {
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
RawRegion string `mapstructure:"region"`
|
||||
}
|
||||
|
||||
// Auth returns a valid aws.Auth object for access to AWS services, or
|
||||
@@ -16,6 +20,28 @@ func (c *AccessConfig) Auth() (aws.Auth, error) {
|
||||
return aws.GetAuth(c.AccessKey, c.SecretKey)
|
||||
}
|
||||
|
||||
// Region returns the aws.Region object for access to AWS services, requesting
|
||||
// the region from the instance metadata if possible.
|
||||
func (c *AccessConfig) Region() (aws.Region, error) {
|
||||
if c.RawRegion != "" {
|
||||
return aws.Regions[c.RawRegion], nil
|
||||
}
|
||||
|
||||
md, err := aws.GetMetaData("placement/availability-zone")
|
||||
if err != nil {
|
||||
return aws.Region{}, err
|
||||
}
|
||||
|
||||
region := strings.TrimRightFunc(string(md), unicode.IsLetter)
|
||||
return aws.Regions[region], nil
|
||||
}
|
||||
|
||||
func (c *AccessConfig) Prepare() []error {
|
||||
if c.RawRegion != "" {
|
||||
if _, ok := aws.Regions[c.RawRegion]; !ok {
|
||||
return []error{fmt.Errorf("Unknown region: %s", c.RawRegion)}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testAccessConfig() *AccessConfig {
|
||||
return &AccessConfig{}
|
||||
}
|
||||
|
||||
func TestAccessConfigPrepare_Region(t *testing.T) {
|
||||
c := testAccessConfig()
|
||||
c.RawRegion = ""
|
||||
if err := c.Prepare(); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.RawRegion = "us-east-12"
|
||||
if err := c.Prepare(); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
c.RawRegion = "us-east-1"
|
||||
if err := c.Prepare(); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -9,19 +9,57 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// StateRefreshFunc is a function type used for StateChangeConf that is
|
||||
// responsible for refreshing the item being watched for a state change.
|
||||
//
|
||||
// It returns three results. `result` is any object that will be returned
|
||||
// as the final object after waiting for state change. This allows you to
|
||||
// return the final updated object, for example an EC2 instance after refreshing
|
||||
// it.
|
||||
//
|
||||
// `state` is the latest state of that object. And `err` is any error that
|
||||
// may have happened while refreshing the state.
|
||||
type StateRefreshFunc func() (result interface{}, state string, err error)
|
||||
|
||||
// StateChangeConf is the configuration struct used for `WaitForState`.
|
||||
type StateChangeConf struct {
|
||||
Conn *ec2.EC2
|
||||
Instance *ec2.Instance
|
||||
Pending []string
|
||||
Refresh StateRefreshFunc
|
||||
StepState map[string]interface{}
|
||||
Target string
|
||||
}
|
||||
|
||||
func WaitForState(conf *StateChangeConf) (i *ec2.Instance, err error) {
|
||||
log.Printf("Waiting for instance state to become: %s", conf.Target)
|
||||
// InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch
|
||||
// an EC2 instance.
|
||||
func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
|
||||
return func() (interface{}, string, error) {
|
||||
resp, err := conn.Instances([]string{i.InstanceId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
i = &resp.Reservations[0].Instances[0]
|
||||
return i, i.State.Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForState watches an object and waits for it to achieve a certain
|
||||
// state.
|
||||
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
|
||||
log.Printf("Waiting for state to become: %s", conf.Target)
|
||||
|
||||
for {
|
||||
var currentState string
|
||||
i, currentState, err = conf.Refresh()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if currentState == conf.Target {
|
||||
return
|
||||
}
|
||||
|
||||
i = conf.Instance
|
||||
for i.State.Name != conf.Target {
|
||||
if conf.StepState != nil {
|
||||
if _, ok := conf.StepState[multistep.StateCancelled]; ok {
|
||||
return nil, errors.New("interrupted")
|
||||
@@ -30,24 +68,17 @@ func WaitForState(conf *StateChangeConf) (i *ec2.Instance, err error) {
|
||||
|
||||
found := false
|
||||
for _, allowed := range conf.Pending {
|
||||
if i.State.Name == allowed {
|
||||
if currentState == allowed {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
fmt.Errorf("unexpected state '%s', wanted target '%s'", i.State.Name, conf.Target)
|
||||
fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
|
||||
return
|
||||
}
|
||||
|
||||
var resp *ec2.InstancesResp
|
||||
resp, err = conf.Conn.Instances([]string{i.InstanceId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
i = &resp.Reservations[0].Instances[0]
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,22 +3,21 @@ package common
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RunConfig contains configuration for running an instance from a source
|
||||
// AMI and details on how to access that launched image.
|
||||
type RunConfig struct {
|
||||
Region string
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
RawSSHTimeout string `mapstructure:"ssh_timeout"`
|
||||
SSHUsername string `mapstructure:"ssh_username"`
|
||||
SSHPort int `mapstructure:"ssh_port"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SubnetId string `mapstructure:"subnet_id"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
RawSSHTimeout string `mapstructure:"ssh_timeout"`
|
||||
SSHUsername string `mapstructure:"ssh_username"`
|
||||
SSHPort int `mapstructure:"ssh_port"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SubnetId string `mapstructure:"subnet_id"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
|
||||
// Unexported fields that are calculated from others
|
||||
sshTimeout time.Duration
|
||||
@@ -45,12 +44,6 @@ func (c *RunConfig) Prepare() []error {
|
||||
errs = append(errs, errors.New("An instance_type must be specified"))
|
||||
}
|
||||
|
||||
if c.Region == "" {
|
||||
errs = append(errs, errors.New("A region must be specified"))
|
||||
} else if _, ok := aws.Regions[c.Region]; !ok {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", c.Region))
|
||||
}
|
||||
|
||||
if c.SSHUsername == "" {
|
||||
errs = append(errs, errors.New("An ssh_username must be specified"))
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ func init() {
|
||||
|
||||
func testConfig() *RunConfig {
|
||||
return &RunConfig{
|
||||
Region: "us-east-1",
|
||||
SourceAmi: "abcd",
|
||||
InstanceType: "m1.small",
|
||||
SSHUsername: "root",
|
||||
@@ -39,24 +38,6 @@ func TestRunConfigPrepare_InstanceType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_Region(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.Region = ""
|
||||
if err := c.Prepare(); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c.Region = "us-east-12"
|
||||
if err := c.Prepare(); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c.Region = "us-east-1"
|
||||
if err := c.Prepare(); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmi(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.SourceAmi = ""
|
||||
|
||||
@@ -2,24 +2,44 @@ package common
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SSHAddress returns a function that can be given to the SSH communicator
|
||||
// for determining the SSH address based on the instance DNS name.
|
||||
func SSHAddress(port int) func(map[string]interface{}) (string, error) {
|
||||
func SSHAddress(e *ec2.EC2, port int) func(map[string]interface{}) (string, error) {
|
||||
return func(state map[string]interface{}) (string, error) {
|
||||
var host string
|
||||
instance := state["instance"].(*ec2.Instance)
|
||||
if instance.VpcId != "" {
|
||||
host = instance.PrivateIpAddress
|
||||
} else {
|
||||
host = instance.DNSName
|
||||
for j := 0; j < 2; j++ {
|
||||
var host string
|
||||
i := state["instance"].(*ec2.Instance)
|
||||
if i.DNSName != "" {
|
||||
host = i.DNSName
|
||||
} else if i.VpcId != "" {
|
||||
host = i.PrivateIpAddress
|
||||
}
|
||||
|
||||
if host != "" {
|
||||
return fmt.Sprintf("%s:%d", host, port), nil
|
||||
}
|
||||
|
||||
r, err := e.Instances([]string{i.InstanceId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(r.Reservations) == 0 || len(r.Reservations[0].Instances) == 0 {
|
||||
return "", fmt.Errorf("instance not found: %s", i.InstanceId)
|
||||
}
|
||||
|
||||
state["instance"] = &r.Reservations[0].Instances[0]
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", host, port), nil
|
||||
return "", errors.New("couldn't determine IP address for instance")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepCreateTags struct {
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
func (s *StepCreateTags) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
amis := state["amis"].(map[string]string)
|
||||
ami := amis[ec2conn.Region.Name]
|
||||
|
||||
if len(s.Tags) > 0 {
|
||||
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
|
||||
|
||||
var ec2Tags []ec2.Tag
|
||||
for key, value := range s.Tags {
|
||||
ec2Tags = append(ec2Tags, ec2.Tag{key, value})
|
||||
}
|
||||
|
||||
_, err := ec2conn.CreateTags([]string{ami}, ec2Tags)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error adding tags to AMI (%s): %s", ami, err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCreateTags) Cleanup(state map[string]interface{}) {
|
||||
// No cleanup...
|
||||
}
|
||||
@@ -12,6 +12,7 @@ type StepRunSourceInstance struct {
|
||||
ExpectedRootDevice string
|
||||
InstanceType string
|
||||
SourceAMI string
|
||||
IamInstanceProfile string
|
||||
SubnetId string
|
||||
|
||||
instance *ec2.Instance
|
||||
@@ -24,13 +25,14 @@ func (s *StepRunSourceInstance) Run(state map[string]interface{}) multistep.Step
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
runOpts := &ec2.RunInstances{
|
||||
KeyName: keyName,
|
||||
ImageId: s.SourceAMI,
|
||||
InstanceType: s.InstanceType,
|
||||
MinCount: 0,
|
||||
MaxCount: 0,
|
||||
SecurityGroups: []ec2.SecurityGroup{ec2.SecurityGroup{Id: securityGroupId}},
|
||||
SubnetId: s.SubnetId,
|
||||
KeyName: keyName,
|
||||
ImageId: s.SourceAMI,
|
||||
InstanceType: s.InstanceType,
|
||||
MinCount: 0,
|
||||
MaxCount: 0,
|
||||
SecurityGroups: []ec2.SecurityGroup{ec2.SecurityGroup{Id: securityGroupId}},
|
||||
IamInstanceProfile: s.IamInstanceProfile,
|
||||
SubnetId: s.SubnetId,
|
||||
}
|
||||
|
||||
ui.Say("Launching a source AWS instance...")
|
||||
@@ -40,6 +42,11 @@ func (s *StepRunSourceInstance) Run(state map[string]interface{}) multistep.Step
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(imageResp.Images) != 1 {
|
||||
state["error"] = fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if s.ExpectedRootDevice != "" && imageResp.Images[0].RootDeviceType != s.ExpectedRootDevice {
|
||||
state["error"] = fmt.Errorf(
|
||||
"The provided source AMI has an invalid root device type.\n"+
|
||||
@@ -62,12 +69,13 @@ func (s *StepRunSourceInstance) Run(state map[string]interface{}) multistep.Step
|
||||
ui.Say(fmt.Sprintf("Waiting for instance (%s) to become ready...", s.instance.InstanceId))
|
||||
stateChange := StateChangeConf{
|
||||
Conn: ec2conn,
|
||||
Instance: s.instance,
|
||||
Pending: []string{"pending"},
|
||||
Target: "running",
|
||||
Refresh: InstanceStateRefreshFunc(ec2conn, s.instance),
|
||||
StepState: state,
|
||||
}
|
||||
s.instance, err = WaitForState(&stateChange)
|
||||
latestInstance, err := WaitForState(&stateChange)
|
||||
s.instance = latestInstance.(*ec2.Instance)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", s.instance.InstanceId, err)
|
||||
state["error"] = err
|
||||
@@ -95,10 +103,10 @@ func (s *StepRunSourceInstance) Cleanup(state map[string]interface{}) {
|
||||
}
|
||||
|
||||
stateChange := StateChangeConf{
|
||||
Conn: ec2conn,
|
||||
Instance: s.instance,
|
||||
Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
|
||||
Target: "running",
|
||||
Conn: ec2conn,
|
||||
Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
|
||||
Refresh: InstanceStateRefreshFunc(ec2conn, s.instance),
|
||||
Target: "terminated",
|
||||
}
|
||||
|
||||
WaitForState(&stateChange)
|
||||
|
||||
@@ -8,11 +8,10 @@ package ebs
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"text/template"
|
||||
@@ -28,6 +27,9 @@ type config struct {
|
||||
|
||||
// Configuration of the resulting AMI
|
||||
AMIName string `mapstructure:"ami_name"`
|
||||
|
||||
// Tags for the AMI
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
@@ -67,9 +69,9 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
region, ok := aws.Regions[b.config.Region]
|
||||
if !ok {
|
||||
panic("region not found")
|
||||
region, err := b.config.Region()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth, err := b.config.AccessConfig.Auth()
|
||||
@@ -98,16 +100,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
ExpectedRootDevice: "ebs",
|
||||
InstanceType: b.config.InstanceType,
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
SubnetId: b.config.SubnetId,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: awscommon.SSHAddress(b.config.SSHPort),
|
||||
SSHAddress: awscommon.SSHAddress(ec2conn, b.config.SSHPort),
|
||||
SSHConfig: awscommon.SSHConfig(b.config.SSHUsername),
|
||||
SSHWaitTimeout: b.config.SSHTimeout(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&stepStopInstance{},
|
||||
&stepCreateAMI{},
|
||||
&awscommon.StepCreateTags{b.config.Tags},
|
||||
}
|
||||
|
||||
// Run!
|
||||
|
||||
@@ -52,7 +52,7 @@ func (s *stepCreateAMI) Run(state map[string]interface{}) multistep.StepAction {
|
||||
// Set the AMI ID in the state
|
||||
ui.Say(fmt.Sprintf("AMI: %s", createResp.ImageId))
|
||||
amis := make(map[string]string)
|
||||
amis[config.Region] = createResp.ImageId
|
||||
amis[ec2conn.Region.Name] = createResp.ImageId
|
||||
state["amis"] = amis
|
||||
|
||||
// Wait for the image to become ready
|
||||
|
||||
@@ -29,12 +29,12 @@ func (s *stepStopInstance) Run(state map[string]interface{}) multistep.StepActio
|
||||
ui.Say("Waiting for the instance to stop...")
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Conn: ec2conn,
|
||||
Instance: instance,
|
||||
Pending: []string{"running", "stopping"},
|
||||
Target: "stopped",
|
||||
Refresh: awscommon.InstanceStateRefreshFunc(ec2conn, instance),
|
||||
StepState: state,
|
||||
}
|
||||
instance, err = awscommon.WaitForState(&stateChange)
|
||||
_, err = awscommon.WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for instance to stop: %s", err)
|
||||
state["error"] = err
|
||||
|
||||
@@ -5,11 +5,10 @@ package instance
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
@@ -34,6 +33,7 @@ type Config struct {
|
||||
BundleUploadCommand string `mapstructure:"bundle_upload_command"`
|
||||
BundleVolCommand string `mapstructure:"bundle_vol_command"`
|
||||
S3Bucket string `mapstructure:"s3_bucket"`
|
||||
Tags map[string]string
|
||||
X509CertPath string `mapstructure:"x509_cert_path"`
|
||||
X509KeyPath string `mapstructure:"x509_key_path"`
|
||||
X509UploadPath string `mapstructure:"x509_upload_path"`
|
||||
@@ -134,9 +134,9 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
region, ok := aws.Regions[b.config.Region]
|
||||
if !ok {
|
||||
panic("region not found")
|
||||
region, err := b.config.Region()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth, err := b.config.AccessConfig.Auth()
|
||||
@@ -168,7 +168,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
SubnetId: b.config.SubnetId,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: awscommon.SSHAddress(b.config.SSHPort),
|
||||
SSHAddress: awscommon.SSHAddress(ec2conn, b.config.SSHPort),
|
||||
SSHConfig: awscommon.SSHConfig(b.config.SSHUsername),
|
||||
SSHWaitTimeout: b.config.SSHTimeout(),
|
||||
},
|
||||
@@ -177,6 +177,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&StepBundleVolume{},
|
||||
&StepUploadBundle{},
|
||||
&StepRegisterAMI{},
|
||||
&awscommon.StepCreateTags{b.config.Tags},
|
||||
}
|
||||
|
||||
// Run!
|
||||
|
||||
@@ -50,7 +50,7 @@ func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction
|
||||
// Set the AMI ID in the state
|
||||
ui.Say(fmt.Sprintf("AMI: %s", registerResp.ImageId))
|
||||
amis := make(map[string]string)
|
||||
amis[config.Region] = registerResp.ImageId
|
||||
amis[ec2conn.Region.Name] = registerResp.ImageId
|
||||
state["amis"] = amis
|
||||
|
||||
// Wait for the image to become ready
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CheckUnusedConfig is a helper that makes sure that the there are no
|
||||
// unused configuration keys, properly ignoring keys that don't matter.
|
||||
func CheckUnusedConfig(md *mapstructure.Metadata) *packer.MultiError {
|
||||
errs := make([]error, 0)
|
||||
|
||||
if md.Unused != nil && len(md.Unused) > 0 {
|
||||
sort.Strings(md.Unused)
|
||||
for _, unused := range md.Unused {
|
||||
if unused != "type" && !strings.HasPrefix(unused, "packer_") {
|
||||
errs = append(
|
||||
errs, fmt.Errorf("Unknown configuration key: %s", unused))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &packer.MultiError{errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeConfig is a helper that handles decoding raw configuration using
|
||||
// mapstructure. It returns the metadata and any errors that may happen.
|
||||
// If you need extra configuration for mapstructure, you should configure
|
||||
// it manually and not use this helper function.
|
||||
func DecodeConfig(target interface{}, raws ...interface{}) (*mapstructure.Metadata, error) {
|
||||
var md mapstructure.Metadata
|
||||
decoderConfig := &mapstructure.DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: target,
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(decoderConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, raw := range raws {
|
||||
err := decoder.Decode(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &md, nil
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckUnusedConfig(t *testing.T) {
|
||||
md := &mapstructure.Metadata{
|
||||
Unused: make([]string, 0),
|
||||
}
|
||||
|
||||
err := CheckUnusedConfig(md)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
md.Unused = []string{"foo", "bar"}
|
||||
err = CheckUnusedConfig(md)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeConfig(t *testing.T) {
|
||||
type Local struct {
|
||||
Foo string
|
||||
Bar string
|
||||
}
|
||||
|
||||
raws := []interface{}{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bar": "baz",
|
||||
"baz": "what",
|
||||
},
|
||||
}
|
||||
|
||||
var result Local
|
||||
md, err := DecodeConfig(&result, raws...)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if result.Foo != "bar" {
|
||||
t.Fatalf("invalid: %#v", result.Foo)
|
||||
}
|
||||
|
||||
if result.Bar != "baz" {
|
||||
t.Fatalf("invalid: %#v", result.Bar)
|
||||
}
|
||||
|
||||
if md == nil {
|
||||
t.Fatal("metadata should not be nil")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(md.Unused, []string{"baz"}) {
|
||||
t.Fatalf("unused: %#v", md.Unused)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
@@ -4,10 +4,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -153,42 +152,10 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("An iso_url must be specified."))
|
||||
} else {
|
||||
url, err := url.Parse(b.config.ISOUrl)
|
||||
b.config.ISOUrl, err = common.DownloadableURL(b.config.ISOUrl)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("iso_url is not a valid URL: %s", err))
|
||||
} else {
|
||||
if url.Scheme == "" {
|
||||
url.Scheme = "file"
|
||||
}
|
||||
|
||||
if url.Scheme == "file" {
|
||||
if _, err := os.Stat(url.Path); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("iso_url points to bad file: %s", err))
|
||||
}
|
||||
} else {
|
||||
supportedSchemes := []string{"file", "http", "https"}
|
||||
scheme := strings.ToLower(url.Scheme)
|
||||
|
||||
found := false
|
||||
for _, supported := range supportedSchemes {
|
||||
if scheme == supported {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Unsupported URL scheme in iso_url: %s", scheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errs == nil || len(errs.Errors) == 0 {
|
||||
// Put the URL back together since we may have modified it
|
||||
b.config.ISOUrl = url.String()
|
||||
errs, fmt.Errorf("iso_url: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,42 +164,10 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
if b.config.GuestAdditionsURL != "" {
|
||||
url, err := url.Parse(b.config.GuestAdditionsURL)
|
||||
b.config.GuestAdditionsURL, err = common.DownloadableURL(b.config.GuestAdditionsURL)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("guest_additions_url is not a valid URL: %s", err))
|
||||
} else {
|
||||
if url.Scheme == "" {
|
||||
url.Scheme = "file"
|
||||
}
|
||||
|
||||
if url.Scheme == "file" {
|
||||
if _, err := os.Stat(url.Path); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("guest_additions_url points to bad file: %s", err))
|
||||
}
|
||||
} else {
|
||||
supportedSchemes := []string{"file", "http", "https"}
|
||||
scheme := strings.ToLower(url.Scheme)
|
||||
|
||||
found := false
|
||||
for _, supported := range supportedSchemes {
|
||||
if scheme == supported {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Unsupported URL scheme in guest_additions_url: %s", scheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errs == nil || len(errs.Errors) == 0 {
|
||||
// Put the URL back together since we may have modified it
|
||||
b.config.GuestAdditionsURL = url.String()
|
||||
errs, fmt.Errorf("guest_additions_url: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -209,39 +209,11 @@ func TestBuilderPrepare_GuestAdditionsURL(t *testing.T) {
|
||||
t.Fatalf("should be empty: %s", b.config.GuestAdditionsURL)
|
||||
}
|
||||
|
||||
config["guest_additions_url"] = "i/am/a/file/that/doesnt/exist"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
config["guest_additions_url"] = "file:i/am/a/file/that/doesnt/exist"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
config["guest_additions_url"] = "http://www.packer.io"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Errorf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["guest_additions_url"] = tf.Name()
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.GuestAdditionsURL != "file://"+tf.Name() {
|
||||
t.Fatalf("guest_additions_url should be modified: %s", b.config.GuestAdditionsURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_HTTPPort(t *testing.T) {
|
||||
@@ -347,39 +319,11 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["iso_url"] = "i/am/a/file/that/doesnt/exist"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
config["iso_url"] = "file:i/am/a/file/that/doesnt/exist"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
config["iso_url"] = "http://www.packer.io"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Errorf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["iso_url"] = tf.Name()
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ISOUrl != "file://"+tf.Name() {
|
||||
t.Fatalf("iso_url should be modified: %s", b.config.ISOUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_OutputDir(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
@@ -3,7 +3,9 @@ package virtualbox
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stepPrepareOutputDir struct{}
|
||||
@@ -34,6 +36,14 @@ func (stepPrepareOutputDir) Cleanup(state map[string]interface{}) {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say("Deleting output directory...")
|
||||
os.RemoveAll(config.OutputDir)
|
||||
for i := 0; i < 5; i++ {
|
||||
err := os.RemoveAll(config.OutputDir)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Error removing output dir: %s", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -150,42 +149,10 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("An iso_url must be specified."))
|
||||
} else {
|
||||
url, err := url.Parse(b.config.ISOUrl)
|
||||
b.config.ISOUrl, err = common.DownloadableURL(b.config.ISOUrl)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("iso_url is not a valid URL: %s", err))
|
||||
} else {
|
||||
if url.Scheme == "" {
|
||||
url.Scheme = "file"
|
||||
}
|
||||
|
||||
if url.Scheme == "file" {
|
||||
if _, err := os.Stat(url.Path); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("iso_url points to bad file: %s", err))
|
||||
}
|
||||
} else {
|
||||
supportedSchemes := []string{"file", "http", "https"}
|
||||
scheme := strings.ToLower(url.Scheme)
|
||||
|
||||
found := false
|
||||
for _, supported := range supportedSchemes {
|
||||
if scheme == supported {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Unsupported URL scheme in iso_url: %s", scheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errs == nil || len(errs.Errors) == 0 {
|
||||
// Put the URL back together since we may have modified it
|
||||
b.config.ISOUrl = url.String()
|
||||
errs, fmt.Errorf("iso_url: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -238,39 +238,11 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["iso_url"] = "i/am/a/file/that/doesnt/exist"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
config["iso_url"] = "file:i/am/a/file/that/doesnt/exist"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
config["iso_url"] = "http://www.packer.io"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Errorf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["iso_url"] = tf.Name()
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ISOUrl != "file://"+tf.Name() {
|
||||
t.Fatalf("iso_url should be modified: %s", b.config.ISOUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_OutputDir(t *testing.T) {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A driver is able to talk to VMware, control virtual machines, etc.
|
||||
@@ -38,22 +42,65 @@ type Driver interface {
|
||||
// NewDriver returns a new driver implementation for this operating
|
||||
// system, or an error if the driver couldn't be initialized.
|
||||
func NewDriver() (Driver, error) {
|
||||
var driver Driver
|
||||
drivers := []Driver{}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
driver = &Fusion5Driver{
|
||||
AppPath: "/Applications/VMware Fusion.app",
|
||||
drivers = []Driver{
|
||||
&Fusion5Driver{
|
||||
AppPath: "/Applications/VMware Fusion.app",
|
||||
},
|
||||
}
|
||||
case "linux":
|
||||
driver = &Workstation9LinuxDriver{}
|
||||
drivers = []Driver{
|
||||
new(Workstation9Driver),
|
||||
new(Player5LinuxDriver),
|
||||
}
|
||||
case "windows":
|
||||
drivers = []Driver{
|
||||
new(Workstation9Driver),
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
if err := driver.Verify(); err != nil {
|
||||
return nil, err
|
||||
errs := ""
|
||||
for _, driver := range drivers {
|
||||
err := driver.Verify()
|
||||
if err == nil {
|
||||
return driver, nil
|
||||
}
|
||||
errs += "* " + err.Error() + "\n"
|
||||
}
|
||||
|
||||
return driver, nil
|
||||
return nil, fmt.Errorf(
|
||||
"Unable to initialize any driver for this platform. The errors\n"+
|
||||
"from each driver are shown below. Please fix at least one driver\n"+
|
||||
"to continue:\n%s", errs)
|
||||
}
|
||||
|
||||
func runAndLog(cmd *exec.Cmd) (string, string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
|
||||
stdoutString := strings.TrimSpace(stdout.String())
|
||||
stderrString := strings.TrimSpace(stderr.String())
|
||||
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
err = fmt.Errorf("VMware error: %s", stderrString)
|
||||
}
|
||||
|
||||
log.Printf("stdout: %s", stdoutString)
|
||||
log.Printf("stderr: %s", stderrString)
|
||||
|
||||
// Replace these for Windows, we only want to deal with Unix
|
||||
// style line endings.
|
||||
returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1)
|
||||
returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1)
|
||||
|
||||
return returnStdout, returnStderr, err
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -18,12 +16,12 @@ type Fusion5Driver struct {
|
||||
|
||||
func (d *Fusion5Driver) CompactDisk(diskPath string) error {
|
||||
defragCmd := exec.Command(d.vdiskManagerPath(), "-d", diskPath)
|
||||
if _, _, err := d.runAndLog(defragCmd); err != nil {
|
||||
if _, _, err := runAndLog(defragCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shrinkCmd := exec.Command(d.vdiskManagerPath(), "-k", diskPath)
|
||||
if _, _, err := d.runAndLog(shrinkCmd); err != nil {
|
||||
if _, _, err := runAndLog(shrinkCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -32,7 +30,7 @@ func (d *Fusion5Driver) CompactDisk(diskPath string) error {
|
||||
|
||||
func (d *Fusion5Driver) CreateDisk(output string, size string) error {
|
||||
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -46,7 +44,7 @@ func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) {
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "list")
|
||||
stdout, _, err := d.runAndLog(cmd)
|
||||
stdout, _, err := runAndLog(cmd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -67,7 +65,7 @@ func (d *Fusion5Driver) Start(vmxPath string, headless bool) error {
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "start", vmxPath, guiArgument)
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -76,7 +74,7 @@ func (d *Fusion5Driver) Start(vmxPath string, headless bool) error {
|
||||
|
||||
func (d *Fusion5Driver) Stop(vmxPath string) error {
|
||||
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "stop", vmxPath, "hard")
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -126,17 +124,3 @@ func (d *Fusion5Driver) ToolsIsoPath(k string) string {
|
||||
func (d *Fusion5Driver) DhcpLeasesPath(device string) string {
|
||||
return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases"
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) runAndLog(cmd *exec.Cmd) (string, string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
|
||||
log.Printf("stdout: %s", strings.TrimSpace(stdout.String()))
|
||||
log.Printf("stderr: %s", strings.TrimSpace(stderr.String()))
|
||||
|
||||
return stdout.String(), stderr.String(), err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Player5LinuxDriver is a driver that can run VMware Player 5 on Linux.
|
||||
type Player5LinuxDriver struct {
|
||||
AppPath string
|
||||
VdiskManagerPath string
|
||||
QemuImgPath string
|
||||
VmrunPath string
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) CompactDisk(diskPath string) error {
|
||||
if d.QemuImgPath != "" {
|
||||
return d.qemuCompactDisk(diskPath)
|
||||
}
|
||||
|
||||
defragCmd := exec.Command(d.VdiskManagerPath, "-d", diskPath)
|
||||
if _, _, err := runAndLog(defragCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shrinkCmd := exec.Command(d.VdiskManagerPath, "-k", diskPath)
|
||||
if _, _, err := runAndLog(shrinkCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) qemuCompactDisk(diskPath string) error {
|
||||
cmd := exec.Command(d.QemuImgPath, "convert", "-f", "vmdk", "-O", "vmdk", "-o", "compat6", diskPath, diskPath+".new")
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Remove(diskPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Rename(diskPath+".new", diskPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) CreateDisk(output string, size string) error {
|
||||
var cmd *exec.Cmd
|
||||
if d.QemuImgPath != "" {
|
||||
cmd = exec.Command(d.QemuImgPath, "create", "-f", "vmdk", "-o", "compat6", output, size)
|
||||
} else {
|
||||
cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
|
||||
}
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) IsRunning(vmxPath string) (bool, error) {
|
||||
vmxPath, err := filepath.Abs(vmxPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.VmrunPath, "-T", "player", "list")
|
||||
stdout, _, err := runAndLog(cmd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
if line == vmxPath {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) Start(vmxPath string, headless bool) error {
|
||||
guiArgument := "gui"
|
||||
if headless {
|
||||
guiArgument = "nogui"
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.VmrunPath, "-T", "player", "start", vmxPath, guiArgument)
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) Stop(vmxPath string) error {
|
||||
cmd := exec.Command(d.VmrunPath, "-T", "player", "stop", vmxPath, "hard")
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) Verify() error {
|
||||
if err := d.findApp(); err != nil {
|
||||
return fmt.Errorf("VMware Player application ('vmplayer') not found in path.")
|
||||
}
|
||||
|
||||
if err := d.findVmrun(); err != nil {
|
||||
return fmt.Errorf("Critical application 'vmrun' not found in path.")
|
||||
}
|
||||
|
||||
if err := d.findVdiskManager(); err != nil {
|
||||
if err := d.findQemuImg(); err != nil {
|
||||
return fmt.Errorf(
|
||||
"Neither 'vmware-vdiskmanager', nor 'qemu-img' found in path.\n" +
|
||||
"One of these is required to configure disks for VMware Player.")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) findApp() error {
|
||||
path, err := exec.LookPath("vmplayer")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.AppPath = path
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) findVdiskManager() error {
|
||||
path, err := exec.LookPath("vmware-vdiskmanager")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.VdiskManagerPath = path
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) findQemuImg() error {
|
||||
path, err := exec.LookPath("qemu-img")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.QemuImgPath = path
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) findVmrun() error {
|
||||
path, err := exec.LookPath("vmrun")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.VmrunPath = path
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) ToolsIsoPath(flavor string) string {
|
||||
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) DhcpLeasesPath(device string) string {
|
||||
return "/etc/vmware/" + device + "/dhcpd/dhcpd.leases"
|
||||
}
|
||||
@@ -1,54 +1,53 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Workstation9LinuxDriver is a driver that can run VMware Workstation 9
|
||||
// on Linux.
|
||||
type Workstation9LinuxDriver struct {
|
||||
// Workstation9Driver is a driver that can run VMware Workstation 9
|
||||
// on non-Windows platforms.
|
||||
type Workstation9Driver struct {
|
||||
AppPath string
|
||||
VdiskManagerPath string
|
||||
VmrunPath string
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) CompactDisk(diskPath string) error {
|
||||
func (d *Workstation9Driver) CompactDisk(diskPath string) error {
|
||||
defragCmd := exec.Command(d.VdiskManagerPath, "-d", diskPath)
|
||||
if _, _, err := d.runAndLog(defragCmd); err != nil {
|
||||
if _, _, err := runAndLog(defragCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shrinkCmd := exec.Command(d.VdiskManagerPath, "-k", diskPath)
|
||||
if _, _, err := d.runAndLog(shrinkCmd); err != nil {
|
||||
if _, _, err := runAndLog(shrinkCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) CreateDisk(output string, size string) error {
|
||||
func (d *Workstation9Driver) CreateDisk(output string, size string) error {
|
||||
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) IsRunning(vmxPath string) (bool, error) {
|
||||
func (d *Workstation9Driver) IsRunning(vmxPath string) (bool, error) {
|
||||
vmxPath, err := filepath.Abs(vmxPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.VmrunPath, "-T", "ws", "list")
|
||||
stdout, _, err := d.runAndLog(cmd)
|
||||
stdout, _, err := runAndLog(cmd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -62,107 +61,77 @@ func (d *Workstation9LinuxDriver) IsRunning(vmxPath string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) Start(vmxPath string, headless bool) error {
|
||||
func (d *Workstation9Driver) Start(vmxPath string, headless bool) error {
|
||||
guiArgument := "gui"
|
||||
if headless {
|
||||
guiArgument = "nogui"
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.VmrunPath, "-T", "ws", "start", vmxPath, guiArgument)
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) Stop(vmxPath string) error {
|
||||
func (d *Workstation9Driver) Stop(vmxPath string) error {
|
||||
cmd := exec.Command(d.VmrunPath, "-T", "ws", "stop", vmxPath, "hard")
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) Verify() error {
|
||||
if err := d.findApp(); err != nil {
|
||||
return fmt.Errorf("VMware Workstation application ('vmware') not found in path.")
|
||||
func (d *Workstation9Driver) Verify() error {
|
||||
var err error
|
||||
if d.AppPath == "" {
|
||||
if d.AppPath, err = workstationFindVMware(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.findVmrun(); err != nil {
|
||||
return fmt.Errorf("Required application 'vmrun' not found in path.")
|
||||
if d.VmrunPath == "" {
|
||||
if d.VmrunPath, err = workstationFindVmrun(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.findVdiskManager(); err != nil {
|
||||
return fmt.Errorf("Required application 'vmware-vdiskmanager' not found in path.")
|
||||
if d.VdiskManagerPath == "" {
|
||||
if d.VdiskManagerPath, err = workstationFindVdiskManager(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("VMware app path: %s", d.AppPath)
|
||||
log.Printf("vmrun path: %s", d.VmrunPath)
|
||||
log.Printf("vdisk-manager path: %s", d.VdiskManagerPath)
|
||||
|
||||
if _, err := os.Stat(d.AppPath); err != nil {
|
||||
return fmt.Errorf("VMware application not found: %s", d.AppPath)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(d.VmrunPath); err != nil {
|
||||
return fmt.Errorf("'vmrun' application not found: %s", d.VmrunPath)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(d.VdiskManagerPath); err != nil {
|
||||
return fmt.Errorf("'vmrun' application not found: %s", d.VdiskManagerPath)
|
||||
}
|
||||
|
||||
// Check to see if it APPEARS to be licensed.
|
||||
matches, err := filepath.Glob("/etc/vmware/license-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error looking for VMware license: %s", err)
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
return errors.New("Workstation does not appear to be licensed. Please license it.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) findApp() error {
|
||||
path, err := exec.LookPath("vmware")
|
||||
if err != nil {
|
||||
if err := workstationCheckLicense(); err != nil {
|
||||
return err
|
||||
}
|
||||
d.AppPath = path
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) findVdiskManager() error {
|
||||
path, err := exec.LookPath("vmware-vdiskmanager")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.VdiskManagerPath = path
|
||||
return nil
|
||||
func (d *Workstation9Driver) ToolsIsoPath(flavor string) string {
|
||||
return workstationToolsIsoPath(flavor)
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) findVmrun() error {
|
||||
path, err := exec.LookPath("vmrun")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.VmrunPath = path
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) ToolsIsoPath(flavor string) string {
|
||||
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) DhcpLeasesPath(device string) string {
|
||||
return "/etc/vmware/" + device + "/dhcpd/dhcpd.leases"
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) runAndLog(cmd *exec.Cmd) (string, string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
|
||||
stdoutString := strings.TrimSpace(stdout.String())
|
||||
stderrString := strings.TrimSpace(stderr.String())
|
||||
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
err = fmt.Errorf("VMware error: %s", stderrString)
|
||||
}
|
||||
|
||||
log.Printf("stdout: %s", stdoutString)
|
||||
log.Printf("stderr: %s", stderrString)
|
||||
|
||||
return stdout.String(), stderr.String(), err
|
||||
func (d *Workstation9Driver) DhcpLeasesPath(device string) string {
|
||||
return workstationDhcpLeasesPath(device)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// +build !windows
|
||||
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func workstationCheckLicense() error {
|
||||
matches, err := filepath.Glob("/etc/vmware/license-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error looking for VMware license: %s", err)
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
return errors.New("Workstation does not appear to be licensed. Please license it.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func workstationFindVdiskManager() (string, error) {
|
||||
return exec.LookPath("vmware-vdiskmanager")
|
||||
}
|
||||
|
||||
func workstationFindVMware() (string, error) {
|
||||
return exec.LookPath("vmware")
|
||||
}
|
||||
|
||||
func workstationFindVmrun() (string, error) {
|
||||
return exec.LookPath("vmrun")
|
||||
}
|
||||
|
||||
func workstationDhcpLeasesPath(device string) string {
|
||||
return "/etc/vmware/" + device + "/dhcpd/dhcpd.leases"
|
||||
}
|
||||
|
||||
func workstationToolsIsoPath(flavor string) string {
|
||||
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// +build windows
|
||||
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func workstationCheckLicense() error {
|
||||
// Not implemented on Windows
|
||||
return nil
|
||||
}
|
||||
|
||||
func workstationFindVdiskManager() (string, error) {
|
||||
path, err := exec.LookPath("vmware-vdiskmanager.exe")
|
||||
if err == nil {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
path, err = workstationVMwareRoot()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(path, "vmware-vdiskmanager.exe"), nil
|
||||
}
|
||||
|
||||
func workstationFindVMware() (string, error) {
|
||||
path, err := exec.LookPath("vmware.exe")
|
||||
if err == nil {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
path, err = workstationVMwareRoot()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(path, "vmware.exe"), nil
|
||||
}
|
||||
|
||||
func workstationFindVmrun() (string, error) {
|
||||
path, err := exec.LookPath("vmrun.exe")
|
||||
if err == nil {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
path, err = workstationVMwareRoot()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(path, "vmrun.exe"), nil
|
||||
}
|
||||
|
||||
func workstationToolsIsoPath(flavor string) string {
|
||||
path, err := workstationVMwareRoot()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return filepath.Join(path, flavor+".iso")
|
||||
}
|
||||
|
||||
func workstationDhcpLeasesPath(device string) string {
|
||||
programData := os.Getenv("ProgramData")
|
||||
if programData == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return filepath.Join(programData, "/VMware/vmnetdhcp.leases")
|
||||
}
|
||||
|
||||
// See http://blog.natefinch.com/2012/11/go-win-stuff.html
|
||||
//
|
||||
// This is used by workstationVMwareRoot in order to read some registry data.
|
||||
func readRegString(hive syscall.Handle, subKeyPath, valueName string) (value string, err error) {
|
||||
var h syscall.Handle
|
||||
err = syscall.RegOpenKeyEx(hive, syscall.StringToUTF16Ptr(subKeyPath), 0, syscall.KEY_READ, &h)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer syscall.RegCloseKey(h)
|
||||
|
||||
var typ uint32
|
||||
var bufSize uint32
|
||||
err = syscall.RegQueryValueEx(
|
||||
h,
|
||||
syscall.StringToUTF16Ptr(valueName),
|
||||
nil,
|
||||
&typ,
|
||||
nil,
|
||||
&bufSize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := make([]uint16, bufSize/2+1)
|
||||
err = syscall.RegQueryValueEx(
|
||||
h,
|
||||
syscall.StringToUTF16Ptr(valueName),
|
||||
nil,
|
||||
&typ,
|
||||
(*byte)(unsafe.Pointer(&data[0])),
|
||||
&bufSize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return syscall.UTF16ToString(data), nil
|
||||
}
|
||||
|
||||
// This reads the VMware installation path from the Windows registry.
|
||||
func workstationVMwareRoot() (s string, err error) {
|
||||
key := `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\vmware.exe`
|
||||
subkey := "Path"
|
||||
s, err = readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey)
|
||||
if err != nil {
|
||||
log.Printf(`Unable to read registry key %s\%s`, key, subkey)
|
||||
return
|
||||
}
|
||||
|
||||
s = strings.Replace(s, "\\", "/", -1)
|
||||
return
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package vmware
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -28,7 +29,13 @@ type DHCPLeaseGuestLookup struct {
|
||||
}
|
||||
|
||||
func (f *DHCPLeaseGuestLookup) GuestIP() (string, error) {
|
||||
fh, err := os.Open(f.Driver.DhcpLeasesPath(f.Device))
|
||||
dhcpLeasesPath := f.Driver.DhcpLeasesPath(f.Device)
|
||||
log.Printf("DHCP leases path: %s", dhcpLeasesPath)
|
||||
if dhcpLeasesPath == "" {
|
||||
return "", errors.New("no DHCP leases path found.")
|
||||
}
|
||||
|
||||
fh, err := os.Open(dhcpLeasesPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -50,6 +57,9 @@ func (f *DHCPLeaseGuestLookup) GuestIP() (string, error) {
|
||||
macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`)
|
||||
|
||||
for _, line := range strings.Split(string(dhcpBytes), "\n") {
|
||||
// Need to trim off CR character when running in windows
|
||||
line = strings.TrimRight(line, "\r")
|
||||
|
||||
matches := ipLineRe.FindStringSubmatch(line)
|
||||
if matches != nil {
|
||||
lastIp = matches[1]
|
||||
|
||||
@@ -1,43 +1,7 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Interface to help find the host IP that is available from within
|
||||
// the VMware virtual machines.
|
||||
type HostIPFinder interface {
|
||||
HostIP() (string, error)
|
||||
}
|
||||
|
||||
// IfconfigIPFinder finds the host IP based on the output of `ifconfig`.
|
||||
type IfconfigIPFinder struct {
|
||||
Device string
|
||||
}
|
||||
|
||||
func (f *IfconfigIPFinder) HostIP() (string, error) {
|
||||
ifconfigPath, err := exec.LookPath("ifconfig")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
stdout := new(bytes.Buffer)
|
||||
|
||||
cmd := exec.Command(ifconfigPath, f.Device)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = new(bytes.Buffer)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`inet\s*(?:addr:)?(.+?)\s`)
|
||||
matches := re.FindStringSubmatch(stdout.String())
|
||||
if matches == nil {
|
||||
return "", errors.New("IP not found in ifconfig output...")
|
||||
}
|
||||
|
||||
return matches[1], nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// IfconfigIPFinder finds the host IP based on the output of `ifconfig`.
|
||||
type IfconfigIPFinder struct {
|
||||
Device string
|
||||
}
|
||||
|
||||
func (f *IfconfigIPFinder) HostIP() (string, error) {
|
||||
ifconfigPath, err := exec.LookPath("ifconfig")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
stdout := new(bytes.Buffer)
|
||||
|
||||
cmd := exec.Command(ifconfigPath, f.Device)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = new(bytes.Buffer)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`inet\s*(?:addr:)?(.+?)\s`)
|
||||
matches := re.FindStringSubmatch(stdout.String())
|
||||
if matches == nil {
|
||||
return "", errors.New("IP not found in ifconfig output...")
|
||||
}
|
||||
|
||||
return matches[1], nil
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// VMnetNatConfIPFinder finds the IP address of the host machine by
|
||||
// retrieving the IP from the vmnetnat.conf. This isn't a full proof
|
||||
// technique but so far it has not failed.
|
||||
type VMnetNatConfIPFinder struct{}
|
||||
|
||||
func (*VMnetNatConfIPFinder) HostIP() (string, error) {
|
||||
programData := os.Getenv("ProgramData")
|
||||
if programData == "" {
|
||||
return "", errors.New("ProgramData directory not found.")
|
||||
}
|
||||
|
||||
programData = strings.Replace(programData, "\\", "/", -1)
|
||||
vmnetnat := filepath.Join(programData, "/VMware/vmnetnat.conf")
|
||||
if _, err := os.Stat(vmnetnat); err != nil {
|
||||
return "", fmt.Errorf("Error with vmnetnat.conf: %s", err)
|
||||
}
|
||||
|
||||
f, err := os.Open(vmnetnat)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
ipRe := regexp.MustCompile(`^\s*ip\s*=\s*(.+?)\s*$`)
|
||||
|
||||
r := bufio.NewReader(f)
|
||||
for {
|
||||
line, err := r.ReadString('\n')
|
||||
if line != "" {
|
||||
matches := ipRe.FindStringSubmatch(line)
|
||||
if matches != nil {
|
||||
ip := matches[1]
|
||||
dotIndex := strings.LastIndex(ip, ".")
|
||||
if dotIndex == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
ip = ip[0:dotIndex] + ".1"
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("host IP not found in NAT config")
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package vmware
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestVMnetNatConfIPFinder_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &VMnetNatConfIPFinder{}
|
||||
if _, ok := raw.(HostIPFinder); !ok {
|
||||
t.Fatalf("VMnetNatConfIPFinder is not a host IP finder")
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
@@ -3,7 +3,9 @@ package vmware
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stepPrepareOutputDir struct{}
|
||||
@@ -34,6 +36,14 @@ func (stepPrepareOutputDir) Cleanup(state map[string]interface{}) {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say("Deleting output directory...")
|
||||
os.RemoveAll(config.OutputDir)
|
||||
for i := 0; i < 5; i++ {
|
||||
err := os.RemoveAll(config.OutputDir)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Error removing output dir: %s", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -120,6 +121,13 @@ LockWaitLoop:
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows takes a while to yield control of the files when the
|
||||
// process is exiting. We just sleep here. In the future, it'd be
|
||||
// nice to find a better solution to this.
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
log.Println("VM shut down.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
@@ -64,7 +65,13 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
|
||||
log.Printf("Connected to VNC desktop: %s", c.DesktopName)
|
||||
|
||||
// Determine the host IP
|
||||
ipFinder := &IfconfigIPFinder{"vmnet8"}
|
||||
var ipFinder HostIPFinder
|
||||
if runtime.GOOS == "windows" {
|
||||
ipFinder = new(VMnetNatConfIPFinder)
|
||||
} else {
|
||||
ipFinder = &IfconfigIPFinder{Device: "vmnet8"}
|
||||
}
|
||||
|
||||
hostIp, err := ipFinder.HostIP()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error detecting host IP: %s", err)
|
||||
@@ -73,6 +80,8 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Host IP for the VMware machine: %s", hostIp)
|
||||
|
||||
tplData := &bootCommandTemplateData{
|
||||
hostIp,
|
||||
httpPort,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package validate
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommand_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(Command)
|
||||
if _, ok := raw.(packer.Command); !ok {
|
||||
t.Fatalf("must be a Command")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CheckUnusedConfig is a helper that makes sure that the there are no
|
||||
// unused configuration keys, properly ignoring keys that don't matter.
|
||||
func CheckUnusedConfig(md *mapstructure.Metadata) *packer.MultiError {
|
||||
errs := make([]error, 0)
|
||||
|
||||
if md.Unused != nil && len(md.Unused) > 0 {
|
||||
sort.Strings(md.Unused)
|
||||
for _, unused := range md.Unused {
|
||||
if unused != "type" && !strings.HasPrefix(unused, "packer_") {
|
||||
errs = append(
|
||||
errs, fmt.Errorf("Unknown configuration key: %s", unused))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &packer.MultiError{errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeConfig is a helper that handles decoding raw configuration using
|
||||
// mapstructure. It returns the metadata and any errors that may happen.
|
||||
// If you need extra configuration for mapstructure, you should configure
|
||||
// it manually and not use this helper function.
|
||||
func DecodeConfig(target interface{}, raws ...interface{}) (*mapstructure.Metadata, error) {
|
||||
var md mapstructure.Metadata
|
||||
decoderConfig := &mapstructure.DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: target,
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(decoderConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, raw := range raws {
|
||||
err := decoder.Decode(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &md, nil
|
||||
}
|
||||
|
||||
// DownloadableURL processes a URL that may also be a file path and returns
|
||||
// a completely valid URL. For example, the original URL might be "local/file.iso"
|
||||
// which isn't a valid URL. DownloadableURL will return "file:///local/file.iso"
|
||||
func DownloadableURL(original string) (string, error) {
|
||||
url, err := url.Parse(original)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if url.Scheme == "" {
|
||||
url.Scheme = "file"
|
||||
}
|
||||
|
||||
if url.Scheme == "file" {
|
||||
// For Windows absolute file paths, remove leading / prior to processing
|
||||
// since net/url turns "C:/" into "/C:/"
|
||||
if runtime.GOOS == "windows" && url.Path[0] == '/' {
|
||||
url.Path = url.Path[1:len(url.Path)]
|
||||
|
||||
// Also replace all backslashes with forwardslashes since Windows
|
||||
// users are likely to do this but the URL should actually only
|
||||
// contain forward slashes.
|
||||
url.Path = strings.Replace(url.Path, `\`, `/`, -1)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(url.Path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
url.Path, err = filepath.Abs(url.Path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
url.Path, err = filepath.EvalSymlinks(url.Path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
url.Path = filepath.Clean(url.Path)
|
||||
}
|
||||
|
||||
// Make sure it is lowercased
|
||||
url.Scheme = strings.ToLower(url.Scheme)
|
||||
|
||||
// This is to work around issue #5927. This can safely be removed once
|
||||
// we distribute with a version of Go that fixes that bug.
|
||||
//
|
||||
// See: https://code.google.com/p/go/issues/detail?id=5927
|
||||
if url.Path != "" && url.Path[0] != '/' {
|
||||
url.Path = "/" + url.Path
|
||||
}
|
||||
|
||||
// Verify that the scheme is something we support in our common downloader.
|
||||
supported := []string{"file", "http", "https"}
|
||||
found := false
|
||||
for _, s := range supported {
|
||||
if url.Scheme == s {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return "", fmt.Errorf("Unsupported URL scheme: %s", url.Scheme)
|
||||
}
|
||||
|
||||
return url.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckUnusedConfig(t *testing.T) {
|
||||
md := &mapstructure.Metadata{
|
||||
Unused: make([]string, 0),
|
||||
}
|
||||
|
||||
err := CheckUnusedConfig(md)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
md.Unused = []string{"foo", "bar"}
|
||||
err = CheckUnusedConfig(md)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeConfig(t *testing.T) {
|
||||
type Local struct {
|
||||
Foo string
|
||||
Bar string
|
||||
}
|
||||
|
||||
raws := []interface{}{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bar": "baz",
|
||||
"baz": "what",
|
||||
},
|
||||
}
|
||||
|
||||
var result Local
|
||||
md, err := DecodeConfig(&result, raws...)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if result.Foo != "bar" {
|
||||
t.Fatalf("invalid: %#v", result.Foo)
|
||||
}
|
||||
|
||||
if result.Bar != "baz" {
|
||||
t.Fatalf("invalid: %#v", result.Bar)
|
||||
}
|
||||
|
||||
if md == nil {
|
||||
t.Fatal("metadata should not be nil")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(md.Unused, []string{"baz"}) {
|
||||
t.Fatalf("unused: %#v", md.Unused)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadableURL(t *testing.T) {
|
||||
// Invalid URL: has hex code in host
|
||||
_, err := DownloadableURL("http://what%20.com")
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
|
||||
// Invalid: unsupported scheme
|
||||
_, err = DownloadableURL("ftp://host.com/path")
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
|
||||
// Valid: http
|
||||
u, err := DownloadableURL("HTTP://packer.io/path")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if u != "http://packer.io/path" {
|
||||
t.Fatalf("bad: %s", u)
|
||||
}
|
||||
|
||||
// No path
|
||||
u, err = DownloadableURL("HTTP://packer.io")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if u != "http://packer.io" {
|
||||
t.Fatalf("bad: %s", u)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadableURL_FilePaths(t *testing.T) {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("tempfile err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
tf.Close()
|
||||
|
||||
tfPath, err := filepath.EvalSymlinks(tf.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("tempfile err: %s", err)
|
||||
}
|
||||
|
||||
tfPath = filepath.Clean(tfPath)
|
||||
|
||||
// Relative filepath. We run this test in a func so that
|
||||
// the defers run right away.
|
||||
func() {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("getwd err: %s", err)
|
||||
}
|
||||
|
||||
err = os.Chdir(filepath.Dir(tfPath))
|
||||
if err != nil {
|
||||
t.Fatalf("chdir err: %s", err)
|
||||
}
|
||||
defer os.Chdir(wd)
|
||||
|
||||
filename := filepath.Base(tfPath)
|
||||
u, err := DownloadableURL(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if u != fmt.Sprintf("file://%s", tfPath) {
|
||||
t.Fatalf("unexpected: %s", u)
|
||||
}
|
||||
}()
|
||||
|
||||
// Test some cases with and without a schema prefix
|
||||
for _, prefix := range []string{"", "file://"} {
|
||||
// Nonexistent file
|
||||
_, err = DownloadableURL(prefix + "i/dont/exist")
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
|
||||
// Good file
|
||||
u, err := DownloadableURL(prefix + tfPath)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if u != fmt.Sprintf("file://%s", tfPath) {
|
||||
t.Fatalf("unexpected: %s", u)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// DownloadConfig is the configuration given to instantiate a new
|
||||
@@ -107,6 +108,11 @@ func (d *DownloadClient) Get() (string, error) {
|
||||
var finalPath string
|
||||
if url.Scheme == "file" && !d.config.CopyFile {
|
||||
finalPath = url.Path
|
||||
|
||||
// Remove forward slash on absolute Windows file URLs before processing
|
||||
if runtime.GOOS == "windows" && finalPath[0] == '/' {
|
||||
finalPath = finalPath[1:len(finalPath)]
|
||||
}
|
||||
} else {
|
||||
finalPath = d.config.TargetPath
|
||||
|
||||
@@ -34,8 +34,7 @@ type StepConnectSSH struct {
|
||||
// SSHWaitTimeout is the total timeout to wait for SSH to become available.
|
||||
SSHWaitTimeout time.Duration
|
||||
|
||||
cancel bool
|
||||
comm packer.Communicator
|
||||
comm packer.Communicator
|
||||
}
|
||||
|
||||
func (s *StepConnectSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||
@@ -44,10 +43,11 @@ func (s *StepConnectSSH) Run(state map[string]interface{}) multistep.StepAction
|
||||
var comm packer.Communicator
|
||||
var err error
|
||||
|
||||
cancel := make(chan struct{})
|
||||
waitDone := make(chan bool, 1)
|
||||
go func() {
|
||||
ui.Say("Waiting for SSH to become available...")
|
||||
comm, err = s.waitForSSH(state)
|
||||
comm, err = s.waitForSSH(state, cancel)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
@@ -70,13 +70,13 @@ WaitLoop:
|
||||
break WaitLoop
|
||||
case <-timeout:
|
||||
ui.Error("Timeout waiting for SSH.")
|
||||
s.cancel = true
|
||||
close(cancel)
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
// The step sequence was cancelled, so cancel waiting for SSH
|
||||
// and just start the halting process.
|
||||
s.cancel = true
|
||||
close(cancel)
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
@@ -89,16 +89,16 @@ WaitLoop:
|
||||
func (s *StepConnectSSH) Cleanup(map[string]interface{}) {
|
||||
}
|
||||
|
||||
func (s *StepConnectSSH) waitForSSH(state map[string]interface{}) (packer.Communicator, error) {
|
||||
func (s *StepConnectSSH) waitForSSH(state map[string]interface{}, cancel <-chan struct{}) (packer.Communicator, error) {
|
||||
handshakeAttempts := 0
|
||||
|
||||
var comm packer.Communicator
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
if s.cancel {
|
||||
select {
|
||||
case <-cancel:
|
||||
log.Println("SSH wait cancelled. Exiting loop.")
|
||||
return nil, errors.New("SSH wait cancelled")
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
|
||||
// First we request the TCP connection information
|
||||
@@ -130,6 +130,7 @@ func (s *StepConnectSSH) waitForSSH(state map[string]interface{}) (packer.Commun
|
||||
SSHConfig: sshConfig,
|
||||
}
|
||||
|
||||
log.Println("Attempting SSH connection...")
|
||||
comm, err = ssh.New(config)
|
||||
if err != nil {
|
||||
log.Printf("SSH handshake err: %s", err)
|
||||
@@ -79,15 +79,16 @@ func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
|
||||
go func() {
|
||||
defer session.Close()
|
||||
err := session.Wait()
|
||||
cmd.ExitStatus = 0
|
||||
exitStatus := 0
|
||||
if err != nil {
|
||||
exitErr, ok := err.(*ssh.ExitError)
|
||||
if ok {
|
||||
cmd.ExitStatus = exitErr.ExitStatus()
|
||||
exitStatus = exitErr.ExitStatus()
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Exited = true
|
||||
log.Printf("remote command exited with '%d': %s", exitStatus, cmd.Command)
|
||||
cmd.SetExited(exitStatus)
|
||||
}()
|
||||
|
||||
return
|
||||
|
||||
@@ -20,6 +20,7 @@ const defaultConfig = `
|
||||
|
||||
"builders": {
|
||||
"amazon-ebs": "packer-builder-amazon-ebs",
|
||||
"amazon-chroot": "packer-builder-amazon-chroot",
|
||||
"amazon-instance": "packer-builder-amazon-instance",
|
||||
"digitalocean": "packer-builder-digitalocean",
|
||||
"virtualbox": "packer-builder-virtualbox",
|
||||
@@ -38,7 +39,8 @@ const defaultConfig = `
|
||||
|
||||
"provisioners": {
|
||||
"file": "packer-provisioner-file",
|
||||
"shell": "packer-provisioner-shell"
|
||||
"shell": "packer-provisioner-shell",
|
||||
"salt-masterless": "packer-provisioner-salt-masterless"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
+28
-3
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/mitchellh/iochan"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// RemoteCmd represents a remote command being prepared or run.
|
||||
@@ -32,6 +32,10 @@ type RemoteCmd struct {
|
||||
|
||||
// Once Exited is true, this will contain the exit code of the process.
|
||||
ExitStatus int
|
||||
|
||||
// Internal locks and such used for safely setting some shared variables
|
||||
l sync.Mutex
|
||||
exitCh chan struct{}
|
||||
}
|
||||
|
||||
// A Communicator is the interface used to communicate with the machine
|
||||
@@ -125,9 +129,30 @@ OutputLoop:
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetExited is a helper for setting that this process is exited. This
|
||||
// should be called by communicators who are running a remote command in
|
||||
// order to set that the command is done.
|
||||
func (r *RemoteCmd) SetExited(status int) {
|
||||
r.l.Lock()
|
||||
defer r.l.Unlock()
|
||||
|
||||
if r.exitCh == nil {
|
||||
r.exitCh = make(chan struct{})
|
||||
}
|
||||
|
||||
r.Exited = true
|
||||
r.ExitStatus = status
|
||||
close(r.exitCh)
|
||||
}
|
||||
|
||||
// Wait waits for the remote command to complete.
|
||||
func (r *RemoteCmd) Wait() {
|
||||
for !r.Exited {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
// Make sure our condition variable is initialized.
|
||||
r.l.Lock()
|
||||
if r.exitCh == nil {
|
||||
r.exitCh = make(chan struct{})
|
||||
}
|
||||
r.l.Unlock()
|
||||
|
||||
<-r.exitCh
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestRemoteCmd_StartWithUi(t *testing.T) {
|
||||
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
rc.Exited = true
|
||||
rc.SetExited(0)
|
||||
}()
|
||||
|
||||
err := rc.StartWithUi(testComm, testUi)
|
||||
@@ -85,8 +85,7 @@ func TestRemoteCmd_Wait(t *testing.T) {
|
||||
result <- true
|
||||
}()
|
||||
|
||||
cmd.ExitStatus = 42
|
||||
cmd.Exited = true
|
||||
cmd.SetExited(42)
|
||||
|
||||
select {
|
||||
case <-result:
|
||||
|
||||
+40
-28
@@ -1,12 +1,14 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
packrpc "github.com/mitchellh/packer/packer/rpc"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/rpc"
|
||||
"os"
|
||||
@@ -14,6 +16,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// This is a slice of the "managed" clients which are cleaned up when
|
||||
@@ -26,7 +29,7 @@ var managedClients = make([]*Client, 0, 5)
|
||||
type Client struct {
|
||||
config *ClientConfig
|
||||
exited bool
|
||||
doneLogging bool
|
||||
doneLogging chan struct{}
|
||||
l sync.Mutex
|
||||
address string
|
||||
}
|
||||
@@ -53,6 +56,10 @@ type ClientConfig struct {
|
||||
// StartTimeout is the timeout to wait for the plugin to say it
|
||||
// has started successfully.
|
||||
StartTimeout time.Duration
|
||||
|
||||
// If non-nil, then the stderr of the client will be written to here
|
||||
// (as well as the log).
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
// This makes sure all the managed subprocesses are killed and properly
|
||||
@@ -94,6 +101,10 @@ func NewClient(config *ClientConfig) (c *Client) {
|
||||
config.StartTimeout = 1 * time.Minute
|
||||
}
|
||||
|
||||
if config.Stderr == nil {
|
||||
config.Stderr = ioutil.Discard
|
||||
}
|
||||
|
||||
c = &Client{config: config}
|
||||
if config.Managed {
|
||||
managedClients = append(managedClients, c)
|
||||
@@ -178,16 +189,7 @@ func (c *Client) Kill() {
|
||||
cmd.Process.Kill()
|
||||
|
||||
// Wait for the client to finish logging so we have a complete log
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
for !c.doneLogging {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
done <- true
|
||||
}()
|
||||
|
||||
<-done
|
||||
<-c.doneLogging
|
||||
}
|
||||
|
||||
// Starts the underlying subprocess, communicating with it to negotiate
|
||||
@@ -204,6 +206,8 @@ func (c *Client) Start() (address string, err error) {
|
||||
return c.address, nil
|
||||
}
|
||||
|
||||
c.doneLogging = make(chan struct{})
|
||||
|
||||
env := []string{
|
||||
fmt.Sprintf("%s=%s", MagicCookieKey, MagicCookieValue),
|
||||
fmt.Sprintf("PACKER_PLUGIN_MIN_PORT=%d", c.config.MinPort),
|
||||
@@ -211,13 +215,13 @@ func (c *Client) Start() (address string, err error) {
|
||||
}
|
||||
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
stderr_r, stderr_w := io.Pipe()
|
||||
|
||||
cmd := c.config.Cmd
|
||||
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||
cmd.Env = append(cmd.Env, env...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = stderr
|
||||
cmd.Stderr = stderr_w
|
||||
cmd.Stdout = stdout
|
||||
|
||||
log.Printf("Starting plugin: %s %#v", cmd.Path, cmd.Args)
|
||||
@@ -241,13 +245,23 @@ func (c *Client) Start() (address string, err error) {
|
||||
|
||||
// Start goroutine to wait for process to exit
|
||||
go func() {
|
||||
// Make sure we close the write end of our stderr listener so
|
||||
// that the log goroutine ends properly.
|
||||
defer stderr_w.Close()
|
||||
|
||||
// Wait for the command to end.
|
||||
cmd.Wait()
|
||||
|
||||
// Log and make sure to flush the logs write away
|
||||
log.Printf("%s: plugin process exited\n", cmd.Path)
|
||||
os.Stderr.Sync()
|
||||
|
||||
// Mark that we exited
|
||||
c.exited = true
|
||||
}()
|
||||
|
||||
// Start goroutine that logs the stderr
|
||||
go c.logStderr(stderr)
|
||||
go c.logStderr(stderr_r)
|
||||
|
||||
// Some channels for the next step
|
||||
timeout := time.After(c.config.StartTimeout)
|
||||
@@ -288,26 +302,24 @@ func (c *Client) Start() (address string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) logStderr(buf *bytes.Buffer) {
|
||||
for done := false; !done; {
|
||||
if c.Exited() {
|
||||
done = true
|
||||
func (c *Client) logStderr(r io.Reader) {
|
||||
bufR := bufio.NewReader(r)
|
||||
for {
|
||||
line, err := bufR.ReadString('\n')
|
||||
if line != "" {
|
||||
c.config.Stderr.Write([]byte(line))
|
||||
|
||||
line = strings.TrimRightFunc(line, unicode.IsSpace)
|
||||
log.Printf("%s: %s", c.config.Cmd.Path, line)
|
||||
}
|
||||
|
||||
var err error
|
||||
for err != io.EOF {
|
||||
var line string
|
||||
line, err = buf.ReadString('\n')
|
||||
if line != "" {
|
||||
log.Printf("%s: %s", c.config.Cmd.Path, line)
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Flag that we've completed logging for others
|
||||
c.doneLogging = true
|
||||
close(c.doneLogging)
|
||||
}
|
||||
|
||||
func (c *Client) rpcClient() (*rpc.Client, error) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -50,6 +52,32 @@ func TestClient_Start_Timeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Stderr(t *testing.T) {
|
||||
stderr := new(bytes.Buffer)
|
||||
process := helperProcess("stderr")
|
||||
c := NewClient(&ClientConfig{
|
||||
Cmd: process,
|
||||
Stderr: stderr,
|
||||
})
|
||||
defer c.Kill()
|
||||
|
||||
if _, err := c.Start(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
for !c.Exited() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if !strings.Contains(stderr.String(), "HELLO\n") {
|
||||
t.Fatalf("bad log data: '%s'", stderr.String())
|
||||
}
|
||||
|
||||
if !strings.Contains(stderr.String(), "WORLD\n") {
|
||||
t.Fatalf("bad log data: '%s'", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Stdin(t *testing.T) {
|
||||
// Overwrite stdin for this test with a temporary file
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
|
||||
@@ -67,6 +67,10 @@ func TestHelperProcess(*testing.T) {
|
||||
case "start-timeout":
|
||||
time.Sleep(1 * time.Minute)
|
||||
os.Exit(1)
|
||||
case "stderr":
|
||||
fmt.Println(":1234")
|
||||
log.Println("HELLO")
|
||||
log.Println("WORLD")
|
||||
case "stdin":
|
||||
fmt.Println(":1234")
|
||||
data := make([]byte, 5)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"net/rpc"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An implementation of packer.Communicator where the communicator is actually
|
||||
@@ -91,8 +90,7 @@ func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
cmd.ExitStatus = finished.ExitStatus
|
||||
cmd.Exited = true
|
||||
cmd.SetExited(finished.ExitStatus)
|
||||
}()
|
||||
|
||||
err = c.client.Call("Communicator.Start", &args, new(interface{}))
|
||||
@@ -204,10 +202,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
|
||||
defer conn.Close()
|
||||
}
|
||||
|
||||
for !cmd.Exited {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
cmd.Wait()
|
||||
responseWriter.Encode(&CommandFinished{cmd.ExitStatus})
|
||||
}()
|
||||
|
||||
|
||||
@@ -96,8 +96,7 @@ func TestCommunicatorRPC(t *testing.T) {
|
||||
assert.Equal(data, "infoo\n", "should be correct stdin")
|
||||
|
||||
// Test that we can get the exit status properly
|
||||
c.startCmd.ExitStatus = 42
|
||||
c.startCmd.Exited = true
|
||||
c.startCmd.SetExited(42)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
if cmd.Exited {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
@@ -80,6 +81,10 @@ func (u *ColoredUi) Error(message string) {
|
||||
}
|
||||
|
||||
func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
|
||||
if !u.supportsColors() {
|
||||
return message
|
||||
}
|
||||
|
||||
attr := 0
|
||||
if bold {
|
||||
attr = 1
|
||||
@@ -88,6 +93,20 @@ func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
|
||||
return fmt.Sprintf("\033[%d;%d;40m%s\033[0m", attr, color, message)
|
||||
}
|
||||
|
||||
func (u *ColoredUi) supportsColors() bool {
|
||||
// For now, on non-Windows machine, just assume it does
|
||||
if runtime.GOOS != "windows" {
|
||||
return true
|
||||
}
|
||||
|
||||
// On Windows, if we appear to be in Cygwin, then it does
|
||||
cygwin := os.Getenv("CYGWIN") != "" ||
|
||||
os.Getenv("OSTYPE") == "cygwin" ||
|
||||
os.Getenv("TERM") == "cygwin"
|
||||
|
||||
return cygwin
|
||||
}
|
||||
|
||||
func (u *PrefixedUi) Ask(query string) (string, error) {
|
||||
return u.Ui.Ask(u.prefixLines(u.SayPrefix, query))
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
var GitCommit string
|
||||
|
||||
// The version of packer.
|
||||
const Version = "0.2.1"
|
||||
const Version = "0.2.3"
|
||||
|
||||
// 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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package main
|
||||
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/builder/amazon/chroot"
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.ServeBuilder(new(chroot.Builder))
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package main
|
||||
@@ -0,0 +1 @@
|
||||
package main
|
||||
@@ -0,0 +1 @@
|
||||
package main
|
||||
@@ -0,0 +1 @@
|
||||
package main
|
||||
@@ -0,0 +1 @@
|
||||
package main
|
||||
@@ -0,0 +1 @@
|
||||
package main
|
||||
@@ -0,0 +1 @@
|
||||
package main
|
||||
@@ -0,0 +1 @@
|
||||
package main
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user