Merge branch 'master' of github.com:mitchellh/packer
This commit is contained in:
commit
27063b236e
|
@ -4,3 +4,5 @@
|
|||
/src
|
||||
/website/.sass-cache
|
||||
/website/build
|
||||
.vagrant
|
||||
Vagrantfile
|
||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -1,10 +1,20 @@
|
|||
## 0.4.0 (unreleased)
|
||||
## 0.4.1 (unreleased)
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: Don't change background color on CLI anymore, making things look
|
||||
a tad nicer in some terminals.
|
||||
|
||||
## 0.4.0 (November 19, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* Docker builder: build and export Docker containers, easily provisioned
|
||||
with any of the Packer built-in provisioners.
|
||||
* QEMU builder: builds a new VM compatible with KVM or Xen using QEMU.
|
||||
* Remote ESXi builder: builds a VMware VM using ESXi remotely using only
|
||||
SSH to an ESXi machine directly.
|
||||
* vSphere post-processor: Can upload VMware artifacts to vSphere
|
||||
* Vagrant post-processor can now make DigitalOcean provider boxes. [GH-504]
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
@ -13,12 +23,22 @@ IMPROVEMENTS:
|
|||
IDs to apply. [GH-499]
|
||||
* builder/amazon/all: AWS API requests are now retried when a temporary
|
||||
network error occurs as well as 500 errors. [GH-559]
|
||||
* builder/virtualbox: Use VBOX\_INSTALL\_PATH env var on Windows to find
|
||||
VBoxManage. [GH-628]
|
||||
* post-processor/vagrant: skips gzip compression when compression_level=0
|
||||
* provisioner/chef-solo: Encrypted data bag support [GH-625]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* builder/amazon/chroot: Copying empty directories works. [GH-588]
|
||||
* builder/amazon/chroot: Chroot commands work with shell provisioners. [GH-581]
|
||||
* builder/amazon/chroot: Don't choose a mount point that is a partition of
|
||||
an already mounted device. [GH-635]
|
||||
* builder/virtualbox: Ctrl-C interrupts during waiting for boot. [GH-618]
|
||||
* builder/vmware: VMX modifications are now case-insensitive. [GH-608]
|
||||
* builder/vmware: VMware Fusion won't ask for VM upgrade.
|
||||
* builder/vmware: Ctrl-C interrupts during waiting for boot. [GH-618]
|
||||
* provisioner/chef-solo: Output is slightly prettier and more informative.
|
||||
|
||||
## 0.3.11 (November 4, 2013)
|
||||
|
||||
|
|
2
Makefile
2
Makefile
|
@ -7,7 +7,7 @@ DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
|
|||
all: deps
|
||||
@mkdir -p bin/
|
||||
@echo "$(OK_COLOR)==> Building$(NO_COLOR)"
|
||||
@bash --norc -i ./scripts/build.sh
|
||||
@bash --norc -i ./scripts/devcompile.sh
|
||||
|
||||
deps:
|
||||
@echo "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)"
|
||||
|
|
11
README.md
11
README.md
|
@ -74,8 +74,15 @@ installed (version 1.1+ is _required_). Make sure you have Go properly installed
|
|||
including setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH).
|
||||
|
||||
For some additional dependencies, Go needs [Mercurial](http://mercurial.selenic.com/)
|
||||
to be installed. Packer itself doesn't require this but a dependency of a
|
||||
dependency does.
|
||||
and [Bazaar](http://bazaar.canonical.com/en/) to be installed.
|
||||
Packer itself doesn't require these, but a dependency of a dependency does.
|
||||
|
||||
You'll also need [`gox`](https://github.com/mitchellh/packer)
|
||||
to compile packer. You can install that with:
|
||||
|
||||
```
|
||||
$ go get -u github.com/mitchellh/gox
|
||||
```
|
||||
|
||||
Next, clone this repository into `$GOPATH/src/github.com/mitchellh/packer` and
|
||||
then just type `make`. In a few moments, you'll have a working `packer` executable:
|
||||
|
|
|
@ -19,6 +19,14 @@ func AvailableDevice() (string, error) {
|
|||
|
||||
letters := "fghijklmnop"
|
||||
for _, letter := range letters {
|
||||
device := fmt.Sprintf("/dev/%s%c", prefix, letter)
|
||||
|
||||
// If the block device itself, i.e. /dev/sf, exists, then we
|
||||
// can't use any of the numbers either.
|
||||
if _, err := os.Stat(device); err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := 1; i < 16; i++ {
|
||||
device := fmt.Sprintf("/dev/%s%c%d", prefix, letter, i)
|
||||
if _, err := os.Stat(device); err != nil {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ExportArtifact is an Artifact implementation for when a container is
|
||||
// exported from docker into a single flat file.
|
||||
type ExportArtifact struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (*ExportArtifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
func (a *ExportArtifact) Files() []string {
|
||||
return []string{a.path}
|
||||
}
|
||||
|
||||
func (*ExportArtifact) Id() string {
|
||||
return "Container"
|
||||
}
|
||||
|
||||
func (a *ExportArtifact) String() string {
|
||||
return fmt.Sprintf("Exported Docker file: %s", a.path)
|
||||
}
|
||||
|
||||
func (a *ExportArtifact) Destroy() error {
|
||||
return os.Remove(a.path)
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExportArtifact_impl(t *testing.T) {
|
||||
var _ packer.Artifact = new(ExportArtifact)
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
const BuilderId = "packer.docker"
|
||||
|
||||
type Builder struct {
|
||||
config *Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
c, warnings, errs := NewConfig(raws...)
|
||||
if errs != nil {
|
||||
return warnings, errs
|
||||
}
|
||||
b.config = c
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
driver := &DockerDriver{Ui: ui}
|
||||
if err := driver.Verify(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
steps := []multistep.Step{
|
||||
&StepTempDir{},
|
||||
&StepPull{},
|
||||
&StepRun{},
|
||||
&StepProvision{},
|
||||
&StepExport{},
|
||||
}
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", b.config)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
// Setup the driver that will talk to Docker
|
||||
state.Put("driver", driver)
|
||||
|
||||
// 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.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// No errors, must've worked
|
||||
artifact := &ExportArtifact{path: b.config.ExportPath}
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuilder_implBuilder(t *testing.T) {
|
||||
var _ packer.Builder = new(Builder)
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/ActiveState/tail"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Communicator struct {
|
||||
ContainerId string
|
||||
HostDir string
|
||||
ContainerDir string
|
||||
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (c *Communicator) Start(remote *packer.RemoteCmd) error {
|
||||
// Create a temporary file to store the output. Because of a bug in
|
||||
// Docker, sometimes all the output doesn't properly show up. This
|
||||
// file will capture ALL of the output, and we'll read that.
|
||||
//
|
||||
// https://github.com/dotcloud/docker/issues/2625
|
||||
outputFile, err := ioutil.TempFile(c.HostDir, "cmd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outputFile.Close()
|
||||
|
||||
// This file will store the exit code of the command once it is complete.
|
||||
exitCodePath := outputFile.Name() + "-exit"
|
||||
|
||||
cmd := exec.Command("docker", "attach", c.ContainerId)
|
||||
stdin_w, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
// We have to do some cleanup since run was never called
|
||||
os.Remove(outputFile.Name())
|
||||
os.Remove(exitCodePath)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Run the actual command in a goroutine so that Start doesn't block
|
||||
go c.run(cmd, remote, stdin_w, outputFile, exitCodePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Communicator) Upload(dst string, src io.Reader) error {
|
||||
// Create a temporary file to store the upload
|
||||
tempfile, err := ioutil.TempFile(c.HostDir, "upload")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempfile.Name())
|
||||
|
||||
// Copy the contents to the temporary file
|
||||
_, err = io.Copy(tempfile, src)
|
||||
tempfile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy the file into place by copying the temporary file we put
|
||||
// into the shared folder into the proper location in the container
|
||||
cmd := &packer.RemoteCmd{
|
||||
Command: fmt.Sprintf("cp %s/%s %s", c.ContainerDir,
|
||||
filepath.Base(tempfile.Name()), dst),
|
||||
}
|
||||
|
||||
if err := c.Start(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the copy to complete
|
||||
cmd.Wait()
|
||||
if cmd.ExitStatus != 0 {
|
||||
return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
|
||||
// Create the temporary directory that will store the contents of "src"
|
||||
// for copying into the container.
|
||||
td, err := ioutil.TempDir(c.HostDir, "dirupload")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
walkFn := func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relpath, err := filepath.Rel(src, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostpath := filepath.Join(td, relpath)
|
||||
|
||||
// If it is a directory, just create it
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(hostpath, info.Mode())
|
||||
}
|
||||
|
||||
// It is a file, copy it over, including mode.
|
||||
src, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(hostpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
si, err := src.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dst.Chmod(si.Mode())
|
||||
}
|
||||
|
||||
// Copy the entire directory tree to the temporary directory
|
||||
if err := filepath.Walk(src, walkFn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Determine the destination directory
|
||||
containerSrc := filepath.Join(c.ContainerDir, filepath.Base(td))
|
||||
containerDst := dst
|
||||
if src[len(src)-1] != '/' {
|
||||
containerDst = filepath.Join(dst, filepath.Base(src))
|
||||
}
|
||||
|
||||
// Make the directory, then copy into it
|
||||
cmd := &packer.RemoteCmd{
|
||||
Command: fmt.Sprintf("set -e; mkdir -p %s; cp -R %s/* %s",
|
||||
containerDst, containerSrc, containerDst),
|
||||
}
|
||||
if err := c.Start(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the copy to complete
|
||||
cmd.Wait()
|
||||
if cmd.ExitStatus != 0 {
|
||||
return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Communicator) Download(src string, dst io.Writer) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Runs the given command and blocks until completion
|
||||
func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.WriteCloser, outputFile *os.File, exitCodePath string) {
|
||||
// For Docker, remote communication must be serialized since it
|
||||
// only supports single execution.
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// Clean up after ourselves by removing our temporary files
|
||||
defer os.Remove(outputFile.Name())
|
||||
defer os.Remove(exitCodePath)
|
||||
|
||||
// Tail the output file and send the data to the stdout listener
|
||||
tail, err := tail.TailFile(outputFile.Name(), tail.Config{
|
||||
Poll: true,
|
||||
ReOpen: true,
|
||||
Follow: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Error tailing output file: %s", err)
|
||||
remote.SetExited(254)
|
||||
return
|
||||
}
|
||||
defer tail.Stop()
|
||||
|
||||
// Modify the remote command so that all the output of the commands
|
||||
// go to a single file and so that the exit code is redirected to
|
||||
// a single file. This lets us determine both when the command
|
||||
// is truly complete (because the file will have data), what the
|
||||
// exit status is (because Docker loses it because of the pty, not
|
||||
// Docker's fault), and get the output (Docker bug).
|
||||
remoteCmd := fmt.Sprintf("(%s) >%s 2>&1; echo $? >%s",
|
||||
remote.Command,
|
||||
filepath.Join(c.ContainerDir, filepath.Base(outputFile.Name())),
|
||||
filepath.Join(c.ContainerDir, filepath.Base(exitCodePath)))
|
||||
|
||||
// Start the command
|
||||
log.Printf("Executing in container %s: %#v", c.ContainerId, remoteCmd)
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Printf("Error executing: %s", err)
|
||||
remote.SetExited(254)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer stdin_w.Close()
|
||||
|
||||
// This sleep needs to be here because of the issue linked to below.
|
||||
// Basically, without it, Docker will hang on reading stdin forever,
|
||||
// and won't see what we write, for some reason.
|
||||
//
|
||||
// https://github.com/dotcloud/docker/issues/2628
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
stdin_w.Write([]byte(remoteCmd + "\n"))
|
||||
}()
|
||||
|
||||
// Start a goroutine to read all the lines out of the logs
|
||||
go func() {
|
||||
for line := range tail.Lines {
|
||||
if remote.Stdout != nil {
|
||||
remote.Stdout.Write([]byte(line.Text + "\n"))
|
||||
} else {
|
||||
log.Printf("Command stdout: %#v", line.Text)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err = cmd.Wait()
|
||||
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()
|
||||
}
|
||||
|
||||
// Say that we ended, since if Docker itself failed, then
|
||||
// the command must've not run, or so we assume
|
||||
remote.SetExited(exitStatus)
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for the exit code to appear in our file...
|
||||
log.Println("Waiting for exit code to appear for remote command...")
|
||||
for {
|
||||
fi, err := os.Stat(exitCodePath)
|
||||
if err == nil && fi.Size() > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
// Read the exit code
|
||||
exitRaw, err := ioutil.ReadFile(exitCodePath)
|
||||
if err != nil {
|
||||
log.Printf("Error executing: %s", err)
|
||||
remote.SetExited(254)
|
||||
return
|
||||
}
|
||||
|
||||
exitStatus, err := strconv.ParseInt(string(bytes.TrimSpace(exitRaw)), 10, 0)
|
||||
if err != nil {
|
||||
log.Printf("Error executing: %s", err)
|
||||
remote.SetExited(254)
|
||||
return
|
||||
}
|
||||
log.Printf("Executed command exit status: %d", exitStatus)
|
||||
|
||||
// Finally, we're done
|
||||
remote.SetExited(int(exitStatus))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommunicator_impl(t *testing.T) {
|
||||
var _ packer.Communicator = new(Communicator)
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
ExportPath string `mapstructure:"export_path"`
|
||||
Image string
|
||||
Pull bool
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
c := new(Config)
|
||||
md, err := common.DecodeConfig(c, raws...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Default Pull if it wasn't set
|
||||
hasPull := false
|
||||
for _, k := range md.Keys {
|
||||
if k == "Pull" {
|
||||
hasPull = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasPull {
|
||||
c.Pull = true
|
||||
}
|
||||
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
templates := map[string]*string{
|
||||
"export_path": &c.ExportPath,
|
||||
"image": &c.Image,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
var err error
|
||||
*ptr, err = c.tpl.Process(*ptr, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
if c.ExportPath == "" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("export_path must be specified"))
|
||||
}
|
||||
|
||||
if c.Image == "" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("image must be specified"))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, nil, errs
|
||||
}
|
||||
|
||||
return c, nil, nil
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"export_path": "foo",
|
||||
"image": "bar",
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigStruct(t *testing.T) *Config {
|
||||
c, warns, errs := NewConfig(testConfig())
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", len(warns))
|
||||
}
|
||||
if errs != nil {
|
||||
t.Fatalf("bad: %#v", errs)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func testConfigErr(t *testing.T, warns []string, err error) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigOk(t *testing.T, warns []string, err error) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepare_exportPath(t *testing.T) {
|
||||
raw := testConfig()
|
||||
|
||||
// No export path
|
||||
delete(raw, "export_path")
|
||||
_, warns, errs := NewConfig(raw)
|
||||
testConfigErr(t, warns, errs)
|
||||
|
||||
// Good export path
|
||||
raw["export_path"] = "good"
|
||||
_, warns, errs = NewConfig(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
|
||||
func TestConfigPrepare_image(t *testing.T) {
|
||||
raw := testConfig()
|
||||
|
||||
// No image
|
||||
delete(raw, "image")
|
||||
_, warns, errs := NewConfig(raw)
|
||||
testConfigErr(t, warns, errs)
|
||||
|
||||
// Good image
|
||||
raw["image"] = "path"
|
||||
_, warns, errs = NewConfig(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
|
||||
func TestConfigPrepare_pull(t *testing.T) {
|
||||
raw := testConfig()
|
||||
|
||||
// No pull set
|
||||
delete(raw, "pull")
|
||||
c, warns, errs := NewConfig(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
if !c.Pull {
|
||||
t.Fatal("should pull by default")
|
||||
}
|
||||
|
||||
// Pull set
|
||||
raw["pull"] = false
|
||||
c, warns, errs = NewConfig(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
if c.Pull {
|
||||
t.Fatal("should not pull")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Driver is the interface that has to be implemented to communicate with
|
||||
// Docker. The Driver interface also allows the steps to be tested since
|
||||
// a mock driver can be shimmed in.
|
||||
type Driver interface {
|
||||
// Export exports the container with the given ID to the given writer.
|
||||
Export(id string, dst io.Writer) error
|
||||
|
||||
// Pull should pull down the given image.
|
||||
Pull(image string) error
|
||||
|
||||
// StartContainer starts a container and returns the ID for that container,
|
||||
// along with a potential error.
|
||||
StartContainer(*ContainerConfig) (string, error)
|
||||
|
||||
// StopContainer forcibly stops a container.
|
||||
StopContainer(id string) error
|
||||
|
||||
// Verify verifies that the driver can run
|
||||
Verify() error
|
||||
}
|
||||
|
||||
// ContainerConfig is the configuration used to start a container.
|
||||
type ContainerConfig struct {
|
||||
Image string
|
||||
Volumes map[string]string
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"log"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DockerDriver struct {
|
||||
Ui packer.Ui
|
||||
}
|
||||
|
||||
func (d *DockerDriver) Export(id string, dst io.Writer) error {
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.Command("docker", "export", id)
|
||||
cmd.Stdout = dst
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
log.Printf("Exporting container: %s", id)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
err = fmt.Errorf("Error exporting: %s\nStderr: %s",
|
||||
err, stderr.String())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DockerDriver) Pull(image string) error {
|
||||
cmd := exec.Command("docker", "pull", image)
|
||||
return runAndStream(cmd, d.Ui)
|
||||
}
|
||||
|
||||
func (d *DockerDriver) StartContainer(config *ContainerConfig) (string, error) {
|
||||
// Args that we're going to pass to Docker
|
||||
args := []string{"run", "-d", "-i", "-t"}
|
||||
|
||||
if len(config.Volumes) > 0 {
|
||||
volumes := make([]string, 0, len(config.Volumes))
|
||||
for host, guest := range config.Volumes {
|
||||
volumes = append(volumes, fmt.Sprintf("%s:%s", host, guest))
|
||||
}
|
||||
|
||||
args = append(args, "-v", strings.Join(volumes, ","))
|
||||
}
|
||||
|
||||
args = append(args, config.Image, "/bin/bash")
|
||||
|
||||
// Start the container
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd := exec.Command("docker", args...)
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
log.Printf("Starting container with args: %v", args)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Println("Waiting for container to finish starting")
|
||||
if err := cmd.Wait(); err != nil {
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
err = fmt.Errorf("Docker exited with a non-zero exit status.\nStderr: %s",
|
||||
stderr.String())
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Capture the container ID, which is alone on stdout
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
|
||||
func (d *DockerDriver) StopContainer(id string) error {
|
||||
return exec.Command("docker", "kill", id).Run()
|
||||
}
|
||||
|
||||
func (d *DockerDriver) Verify() error {
|
||||
if _, err := exec.LookPath("docker"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// MockDriver is a driver implementation that can be used for tests.
|
||||
type MockDriver struct {
|
||||
ExportReader io.Reader
|
||||
ExportError error
|
||||
PullError error
|
||||
StartID string
|
||||
StartError error
|
||||
StopError error
|
||||
VerifyError error
|
||||
|
||||
ExportCalled bool
|
||||
ExportID string
|
||||
PullCalled bool
|
||||
PullImage string
|
||||
StartCalled bool
|
||||
StartConfig *ContainerConfig
|
||||
StopCalled bool
|
||||
StopID string
|
||||
VerifyCalled bool
|
||||
}
|
||||
|
||||
func (d *MockDriver) Export(id string, dst io.Writer) error {
|
||||
d.ExportCalled = true
|
||||
d.ExportID = id
|
||||
|
||||
if d.ExportReader != nil {
|
||||
_, err := io.Copy(dst, d.ExportReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return d.ExportError
|
||||
}
|
||||
|
||||
func (d *MockDriver) Pull(image string) error {
|
||||
d.PullCalled = true
|
||||
d.PullImage = image
|
||||
return d.PullError
|
||||
}
|
||||
|
||||
func (d *MockDriver) StartContainer(config *ContainerConfig) (string, error) {
|
||||
d.StartCalled = true
|
||||
d.StartConfig = config
|
||||
return d.StartID, d.StartError
|
||||
}
|
||||
|
||||
func (d *MockDriver) StopContainer(id string) error {
|
||||
d.StopCalled = true
|
||||
d.StopID = id
|
||||
return d.StopError
|
||||
}
|
||||
|
||||
func (d *MockDriver) Verify() error {
|
||||
d.VerifyCalled = true
|
||||
return d.VerifyError
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package docker
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMockDriver_impl(t *testing.T) {
|
||||
var _ Driver = new(MockDriver)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package docker
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDockerDriver_impl(t *testing.T) {
|
||||
var _ Driver = new(DockerDriver)
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/iochan"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"log"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func runAndStream(cmd *exec.Cmd, ui packer.Ui) error {
|
||||
stdout_r, stdout_w := io.Pipe()
|
||||
stderr_r, stderr_w := io.Pipe()
|
||||
defer stdout_w.Close()
|
||||
defer stderr_w.Close()
|
||||
|
||||
log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
|
||||
cmd.Stdout = stdout_w
|
||||
cmd.Stderr = stderr_w
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the channels we'll use for data
|
||||
exitCh := make(chan int, 1)
|
||||
stdoutCh := iochan.DelimReader(stdout_r, '\n')
|
||||
stderrCh := iochan.DelimReader(stderr_r, '\n')
|
||||
|
||||
// Start the goroutine to watch for the exit
|
||||
go func() {
|
||||
defer stdout_w.Close()
|
||||
defer stderr_w.Close()
|
||||
exitStatus := 0
|
||||
|
||||
err := cmd.Wait()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
exitCh <- exitStatus
|
||||
}()
|
||||
|
||||
// This waitgroup waits for the streaming to end
|
||||
var streamWg sync.WaitGroup
|
||||
streamWg.Add(2)
|
||||
|
||||
streamFunc := func(ch <-chan string) {
|
||||
defer streamWg.Done()
|
||||
|
||||
for data := range ch {
|
||||
data = cleanOutputLine(data)
|
||||
if data != "" {
|
||||
ui.Message(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stream stderr/stdout
|
||||
go streamFunc(stderrCh)
|
||||
go streamFunc(stdoutCh)
|
||||
|
||||
// Wait for the process to end and then wait for the streaming to end
|
||||
exitStatus := <-exitCh
|
||||
streamWg.Wait()
|
||||
|
||||
if exitStatus != 0 {
|
||||
return fmt.Errorf("Bad exit status: %d", exitStatus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanOutputLine cleans up a line so that '\r' don't muck up the
|
||||
// UI output when we're reading from a remote command.
|
||||
func cleanOutputLine(line string) string {
|
||||
// Build a regular expression that will get rid of shell codes
|
||||
re := regexp.MustCompile("(?i)\x1b\\[([0-9]{1,2}(;[0-9]{1,2})?)?[a|b|m|k]")
|
||||
line = re.ReplaceAllString(line, "")
|
||||
|
||||
// Trim surrounding whitespace
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
// Trim up to the first carriage return, since that text would be
|
||||
// lost anyways.
|
||||
idx := strings.LastIndex(line, "\r")
|
||||
if idx > -1 {
|
||||
line = line[idx+1:]
|
||||
}
|
||||
|
||||
return line
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCleanLine(t *testing.T) {
|
||||
cases := []struct {
|
||||
input string
|
||||
output string
|
||||
}{
|
||||
{
|
||||
"\x1b[0A\x1b[2K\r8dbd9e392a96: Pulling image (precise) from ubuntu\r\x1b[0B\x1b[1A\x1b[2K\r8dbd9e392a96: Pulling image (precise) from ubuntu, endpoint: https://cdn-registry-1.docker.io/v1/\r\x1b[1B",
|
||||
"8dbd9e392a96: Pulling image (precise) from ubuntu, endpoint: https://cdn-registry-1.docker.io/v1/",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
actual := cleanOutputLine(tc.input)
|
||||
if actual != tc.output {
|
||||
t.Fatalf("bad: %#v %#v", tc.input, actual)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
)
|
||||
|
||||
// StepExport exports the container to a flat tar file.
|
||||
type StepExport struct{}
|
||||
|
||||
func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
containerId := state.Get("container_id").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Open the file that we're going to write to
|
||||
f, err := os.Create(config.ExportPath)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating output file: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Exporting the container")
|
||||
if err := driver.Export(containerId, f); err != nil {
|
||||
f.Close()
|
||||
os.Remove(f.Name())
|
||||
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
f.Close()
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepExport) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,99 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/mitchellh/multistep"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testStepExportState(t *testing.T) multistep.StateBag {
|
||||
state := testState(t)
|
||||
state.Put("container_id", "foo")
|
||||
return state
|
||||
}
|
||||
|
||||
func TestStepExport_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepExport)
|
||||
}
|
||||
|
||||
func TestStepExport(t *testing.T) {
|
||||
state := testStepExportState(t)
|
||||
step := new(StepExport)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// Create a tempfile for our output path
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.ExportPath = tf.Name()
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.ExportReader = bytes.NewReader([]byte("data!"))
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we did the right thing
|
||||
if !driver.ExportCalled {
|
||||
t.Fatal("should've exported")
|
||||
}
|
||||
if driver.ExportID != "foo" {
|
||||
t.Fatalf("bad: %#v", driver.ExportID)
|
||||
}
|
||||
|
||||
// verify the data exported to the file
|
||||
contents, err := ioutil.ReadFile(tf.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if string(contents) != "data!" {
|
||||
t.Fatalf("bad: %#v", string(contents))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepExport_error(t *testing.T) {
|
||||
state := testStepExportState(t)
|
||||
step := new(StepExport)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// Create a tempfile for our output path
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
|
||||
if err := os.Remove(tf.Name()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.ExportPath = tf.Name()
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.ExportError = errors.New("foo")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we have an error
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// verify we didn't make that file
|
||||
if _, err := os.Stat(tf.Name()); err == nil {
|
||||
t.Fatal("export path shouldn't exist")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common"
|
||||
)
|
||||
|
||||
type StepProvision struct{}
|
||||
|
||||
func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction {
|
||||
containerId := state.Get("container_id").(string)
|
||||
tempDir := state.Get("temp_dir").(string)
|
||||
|
||||
// Create the communicator that talks to Docker via various
|
||||
// os/exec tricks.
|
||||
comm := &Communicator{
|
||||
ContainerId: containerId,
|
||||
HostDir: tempDir,
|
||||
ContainerDir: "/packer-files",
|
||||
}
|
||||
|
||||
prov := common.StepProvision{Comm: comm}
|
||||
return prov.Run(state)
|
||||
}
|
||||
|
||||
func (s *StepProvision) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,34 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
type StepPull struct{}
|
||||
|
||||
func (s *StepPull) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if !config.Pull {
|
||||
log.Println("Pull disabled, won't docker pull")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Pulling Docker image: %s", config.Image))
|
||||
if err := driver.Pull(config.Image); err != nil {
|
||||
err := fmt.Errorf("Error pulling Docker image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepPull) Cleanup(state multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mitchellh/multistep"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepPull_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepPull)
|
||||
}
|
||||
|
||||
func TestStepPull(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepPull)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we did the right thing
|
||||
if !driver.PullCalled {
|
||||
t.Fatal("should've pulled")
|
||||
}
|
||||
if driver.PullImage != config.Image {
|
||||
t.Fatalf("bad: %#v", driver.PullImage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepPull_error(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepPull)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.PullError = errors.New("foo")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we have an error
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepPull_noPull(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepPull)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.Pull = false
|
||||
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we did the right thing
|
||||
if driver.PullCalled {
|
||||
t.Fatal("shouldn't have pulled")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepRun struct {
|
||||
containerId string
|
||||
}
|
||||
|
||||
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
tempDir := state.Get("temp_dir").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
runConfig := ContainerConfig{
|
||||
Image: config.Image,
|
||||
Volumes: map[string]string{
|
||||
tempDir: "/packer-files",
|
||||
},
|
||||
}
|
||||
|
||||
ui.Say("Starting docker container with /bin/bash")
|
||||
containerId, err := driver.StartContainer(&runConfig)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error running container: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Save the container ID
|
||||
s.containerId = containerId
|
||||
state.Put("container_id", s.containerId)
|
||||
ui.Message(fmt.Sprintf("Container ID: %s", s.containerId))
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepRun) Cleanup(state multistep.StateBag) {
|
||||
if s.containerId == "" {
|
||||
return
|
||||
}
|
||||
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Kill the container. We don't handle errors because errors usually
|
||||
// just mean that the container doesn't exist anymore, which isn't a
|
||||
// big deal.
|
||||
ui.Say(fmt.Sprintf("Killing the container: %s", s.containerId))
|
||||
driver.StopContainer(s.containerId)
|
||||
|
||||
// Reset the container ID so that we're idempotent
|
||||
s.containerId = ""
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mitchellh/multistep"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testStepRunState(t *testing.T) multistep.StateBag {
|
||||
state := testState(t)
|
||||
state.Put("temp_dir", "/foo")
|
||||
return state
|
||||
}
|
||||
|
||||
func TestStepRun_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepRun)
|
||||
}
|
||||
|
||||
func TestStepRun(t *testing.T) {
|
||||
state := testStepRunState(t)
|
||||
step := new(StepRun)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.StartID = "foo"
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we did the right thing
|
||||
if !driver.StartCalled {
|
||||
t.Fatal("should've called")
|
||||
}
|
||||
if driver.StartConfig.Image != config.Image {
|
||||
t.Fatalf("bad: %#v", driver.StartConfig.Image)
|
||||
}
|
||||
|
||||
// verify the ID is saved
|
||||
idRaw, ok := state.GetOk("container_id")
|
||||
if !ok {
|
||||
t.Fatal("should've saved ID")
|
||||
}
|
||||
|
||||
id := idRaw.(string)
|
||||
if id != "foo" {
|
||||
t.Fatalf("bad: %#v", id)
|
||||
}
|
||||
|
||||
// Verify we haven't called stop yet
|
||||
if driver.StopCalled {
|
||||
t.Fatal("should not have stopped")
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
step.Cleanup(state)
|
||||
if !driver.StopCalled {
|
||||
t.Fatal("should've stopped")
|
||||
}
|
||||
if driver.StopID != id {
|
||||
t.Fatalf("bad: %#v", driver.StopID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepRun_error(t *testing.T) {
|
||||
state := testStepRunState(t)
|
||||
step := new(StepRun)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.StartError = errors.New("foo")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify the ID is not saved
|
||||
if _, ok := state.GetOk("container_id"); ok {
|
||||
t.Fatal("shouldn't save container ID")
|
||||
}
|
||||
|
||||
// Verify we haven't called stop yet
|
||||
if driver.StopCalled {
|
||||
t.Fatal("should not have stopped")
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
step.Cleanup(state)
|
||||
if driver.StopCalled {
|
||||
t.Fatal("should not have stopped")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// StepTempDir creates a temporary directory that we use in order to
|
||||
// share data with the docker container over the communicator.
|
||||
type StepTempDir struct {
|
||||
tempDir string
|
||||
}
|
||||
|
||||
func (s *StepTempDir) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Creating a temporary directory for sharing data...")
|
||||
td, err := ioutil.TempDir("", "packer-docker")
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error making temp dir: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.tempDir = td
|
||||
state.Put("temp_dir", s.tempDir)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepTempDir) Cleanup(state multistep.StateBag) {
|
||||
if s.tempDir != "" {
|
||||
os.RemoveAll(s.tempDir)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepTempDir_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepTempDir)
|
||||
}
|
||||
|
||||
func TestStepTempDir(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepTempDir)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
// sanity test
|
||||
if _, ok := state.GetOk("temp_dir"); ok {
|
||||
t.Fatalf("temp_dir should not be in state yet")
|
||||
}
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify that we got the temp dir
|
||||
dirRaw, ok := state.GetOk("temp_dir")
|
||||
if !ok {
|
||||
t.Fatalf("should've made temp_dir")
|
||||
}
|
||||
dir := dirRaw.(string)
|
||||
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
step.Cleanup(state)
|
||||
if _, err := os.Stat(dir); err == nil {
|
||||
t.Fatalf("dir should be gone")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testState(t *testing.T) multistep.StateBag {
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", testConfigStruct(t))
|
||||
state.Put("driver", &MockDriver{})
|
||||
state.Put("hook", &packer.MockHook{})
|
||||
state.Put("ui", &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
})
|
||||
return state
|
||||
}
|
|
@ -10,6 +10,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
@ -485,9 +486,29 @@ func (b *Builder) Cancel() {
|
|||
}
|
||||
|
||||
func (b *Builder) newDriver() (Driver, error) {
|
||||
vboxmanagePath, err := exec.LookPath("VBoxManage")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var vboxmanagePath string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// On Windows, we check VBOX_INSTALL_PATH env var for the path
|
||||
if installPath := os.Getenv("VBOX_INSTALL_PATH"); installPath != "" {
|
||||
log.Printf("[DEBUG] builder/virtualbox: VBOX_INSTALL_PATH: %s",
|
||||
installPath)
|
||||
for _, path := range strings.Split(installPath, ";") {
|
||||
path = filepath.Join(path, "VBoxManage.exe")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
vboxmanagePath = path
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if vboxmanagePath == "" {
|
||||
var err error
|
||||
vboxmanagePath, err = exec.LookPath("VBoxManage")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("VBoxManage path: %s", vboxmanagePath)
|
||||
|
|
|
@ -42,7 +42,18 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
|
|||
|
||||
if int64(config.bootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait))
|
||||
time.Sleep(config.bootWait)
|
||||
wait := time.After(config.bootWait)
|
||||
WAITLOOP:
|
||||
for {
|
||||
select {
|
||||
case <-wait:
|
||||
break WAITLOOP
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
|
|
|
@ -8,12 +8,13 @@ import (
|
|||
// Artifact is the result of running the VMware builder, namely a set
|
||||
// of files associated with the resulting machine.
|
||||
type Artifact struct {
|
||||
dir string
|
||||
f []string
|
||||
builderId string
|
||||
dir string
|
||||
f []string
|
||||
}
|
||||
|
||||
func (*Artifact) BuilderId() string {
|
||||
return BuilderId
|
||||
func (a *Artifact) BuilderId() string {
|
||||
return a.builderId
|
||||
}
|
||||
|
||||
func (a *Artifact) Files() []string {
|
||||
|
|
|
@ -10,13 +10,13 @@ import (
|
|||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
const BuilderId = "mitchellh.vmware"
|
||||
const BuilderIdESX = "mitchellh.vmware-esx"
|
||||
|
||||
type Builder struct {
|
||||
config config
|
||||
|
@ -405,6 +405,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
},
|
||||
&stepCreateDisk{},
|
||||
&stepCreateVMX{},
|
||||
&stepSuppressMessages{},
|
||||
&stepHTTPServer{},
|
||||
&stepConfigureVNC{},
|
||||
&stepRun{},
|
||||
|
@ -458,24 +459,22 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
}
|
||||
|
||||
// Compile the artifact list
|
||||
files := make([]string, 0, 10)
|
||||
visit := func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
files = append(files, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := filepath.Walk(b.config.OutputDir, visit); err != nil {
|
||||
files, err := state.Get("dir").(OutputDir).ListFiles()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Artifact{b.config.OutputDir, files}, nil
|
||||
// Set the proper builder ID
|
||||
builderId := BuilderId
|
||||
if b.config.RemoteType != "" {
|
||||
builderId = BuilderIdESX
|
||||
}
|
||||
|
||||
return &Artifact{
|
||||
builderId: builderId,
|
||||
dir: b.config.OutputDir,
|
||||
f: files,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
|
|
|
@ -31,6 +31,10 @@ type Driver interface {
|
|||
// Stop stops a VM specified by the path to the VMX given.
|
||||
Stop(string) error
|
||||
|
||||
// SuppressMessages modifies the VMX or surrounding directory so that
|
||||
// VMware doesn't show any annoying messages.
|
||||
SuppressMessages(string) error
|
||||
|
||||
// Get the path to the VMware ISO for the given flavor.
|
||||
ToolsIsoPath(string) string
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package vmware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"encoding/csv"
|
||||
|
@ -42,7 +43,7 @@ func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, typeId string
|
|||
}
|
||||
|
||||
func (d *ESX5Driver) IsRunning(vmxPathLocal string) (bool, error) {
|
||||
vmxPath := d.datastorePath(vmxPathLocal)
|
||||
vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
|
||||
state, err := d.run(nil, "vim-cmd", "vmsvc/power.getstate", vmxPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@ -68,6 +69,10 @@ func (d *ESX5Driver) Register(vmxPathLocal string) error {
|
|||
return d.sh("vim-cmd", "solo/registervm", vmxPath)
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) SuppressMessages(vmxPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) Unregister(vmxPathLocal string) error {
|
||||
vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
|
||||
return d.sh("vim-cmd", "vmsvc/unregister", vmxPath)
|
||||
|
@ -80,11 +85,11 @@ func (d *ESX5Driver) UploadISO(localPath string) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
if err := d.mkdir(filepath.Dir(targetFile)); err != nil {
|
||||
finalPath := d.datastorePath(targetFile)
|
||||
if err := d.mkdir(filepath.Dir(finalPath)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
finalPath := d.datastorePath(targetFile)
|
||||
if err := d.upload(finalPath, localPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -210,10 +215,37 @@ func (d *ESX5Driver) DirExists() (bool, error) {
|
|||
return err == nil, nil
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) ListFiles() ([]string, error) {
|
||||
stdout, err := d.ssh("ls -1p "+d.outputDir, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := make([]string, 0, 10)
|
||||
reader := bufio.NewReader(stdout)
|
||||
for {
|
||||
line, _, err := reader.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if line[len(line)-1] == '/' {
|
||||
continue
|
||||
}
|
||||
|
||||
files = append(files, filepath.Join(d.outputDir, string(line)))
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) MkdirAll() error {
|
||||
return d.mkdir(d.outputDir)
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) Remove(path string) error {
|
||||
return d.sh("rm", path)
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) RemoveAll() error {
|
||||
return d.sh("rm", "-rf", d.outputDir)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package vmware
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -86,6 +87,15 @@ func (d *Fusion5Driver) Stop(vmxPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) SuppressMessages(vmxPath string) error {
|
||||
dir := filepath.Dir(vmxPath)
|
||||
base := filepath.Base(vmxPath)
|
||||
base = strings.Replace(base, ".vmx", "", -1)
|
||||
|
||||
plistPath := filepath.Join(dir, base+".plist")
|
||||
return ioutil.WriteFile(plistPath, []byte(fusionSuppressPlist), 0644)
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) Verify() error {
|
||||
if _, err := os.Stat(d.AppPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
@ -129,3 +139,12 @@ func (d *Fusion5Driver) ToolsIsoPath(k string) string {
|
|||
func (d *Fusion5Driver) DhcpLeasesPath(device string) string {
|
||||
return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases"
|
||||
}
|
||||
|
||||
const fusionSuppressPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>disallowUpgrade</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>`
|
||||
|
|
|
@ -114,6 +114,10 @@ func (d *Player5LinuxDriver) Stop(vmxPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) SuppressMessages(vmxPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) Verify() error {
|
||||
if err := d.findApp(); err != nil {
|
||||
return fmt.Errorf("VMware Player application ('vmplayer') not found in path.")
|
||||
|
|
|
@ -89,6 +89,10 @@ func (d *Workstation9Driver) Stop(vmxPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9Driver) SuppressMessages(vmxPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9Driver) Verify() error {
|
||||
var err error
|
||||
if d.AppPath == "" {
|
||||
|
|
|
@ -2,6 +2,7 @@ package vmware
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// OutputDir is an interface type that abstracts the creation and handling
|
||||
|
@ -10,7 +11,9 @@ import (
|
|||
// VMware products as well as local.
|
||||
type OutputDir interface {
|
||||
DirExists() (bool, error)
|
||||
ListFiles() ([]string, error)
|
||||
MkdirAll() error
|
||||
Remove(string) error
|
||||
RemoveAll() error
|
||||
SetOutputDir(string)
|
||||
}
|
||||
|
@ -26,10 +29,30 @@ func (d *localOutputDir) DirExists() (bool, error) {
|
|||
return err == nil, nil
|
||||
}
|
||||
|
||||
func (d *localOutputDir) ListFiles() ([]string, error) {
|
||||
files := make([]string, 0, 10)
|
||||
|
||||
visit := func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
files = append(files, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return files, filepath.Walk(d.dir, visit)
|
||||
}
|
||||
|
||||
func (d *localOutputDir) MkdirAll() error {
|
||||
return os.MkdirAll(d.dir, 0755)
|
||||
}
|
||||
|
||||
func (d *localOutputDir) Remove(path string) error {
|
||||
return os.Remove(path)
|
||||
}
|
||||
|
||||
func (d *localOutputDir) RemoveAll() error {
|
||||
return os.RemoveAll(d.dir)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
@ -16,7 +15,7 @@ var KeepFileExtensions = []string{".nvram", ".vmdk", ".vmsd", ".vmx", ".vmxf"}
|
|||
// This step removes unnecessary files from the final result.
|
||||
//
|
||||
// Uses:
|
||||
// config *config
|
||||
// dir OutputDir
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
|
@ -24,41 +23,37 @@ var KeepFileExtensions = []string{".nvram", ".vmdk", ".vmsd", ".vmx", ".vmxf"}
|
|||
type stepCleanFiles struct{}
|
||||
|
||||
func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*config)
|
||||
dir := state.Get("dir").(OutputDir)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deleting unnecessary VMware files...")
|
||||
visit := func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
// If the file isn't critical to the function of the
|
||||
// virtual machine, we get rid of it.
|
||||
keep := false
|
||||
ext := filepath.Ext(path)
|
||||
for _, goodExt := range KeepFileExtensions {
|
||||
if goodExt == ext {
|
||||
keep = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !keep {
|
||||
ui.Message(fmt.Sprintf("Deleting: %s", path))
|
||||
return os.Remove(path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := filepath.Walk(config.OutputDir, visit); err != nil {
|
||||
files, err := dir.ListFiles()
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
for _, path := range files {
|
||||
// If the file isn't critical to the function of the
|
||||
// virtual machine, we get rid of it.
|
||||
keep := false
|
||||
ext := filepath.Ext(path)
|
||||
for _, goodExt := range KeepFileExtensions {
|
||||
if goodExt == ext {
|
||||
keep = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !keep {
|
||||
ui.Message(fmt.Sprintf("Deleting: %s", path))
|
||||
if err = dir.Remove(path); err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
|
|
@ -85,8 +85,8 @@ func (s *stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
|
|||
log.Printf("Found available VNC port: %d", vncPort)
|
||||
|
||||
vmxData := ParseVMX(string(vmxBytes))
|
||||
vmxData["RemoteDisplay.vnc.enabled"] = "TRUE"
|
||||
vmxData["RemoteDisplay.vnc.port"] = fmt.Sprintf("%d", vncPort)
|
||||
vmxData["remotedisplay.vnc.enabled"] = "TRUE"
|
||||
vmxData["remotedisplay.vnc.port"] = fmt.Sprintf("%d", vncPort)
|
||||
|
||||
if err := WriteVMX(vmxPath, vmxData); err != nil {
|
||||
err := fmt.Errorf("Error writing VMX data: %s", err)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package vmware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
|
@ -24,9 +25,14 @@ func (s *stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepActio
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if exists && config.PackerForce {
|
||||
ui.Say("Deleting previous output directory...")
|
||||
dir.RemoveAll()
|
||||
if exists {
|
||||
if config.PackerForce {
|
||||
ui.Say("Deleting previous output directory...")
|
||||
dir.RemoveAll()
|
||||
} else {
|
||||
state.Put("error", fmt.Errorf("Output directory '%s' already exists.", config.OutputDir))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
if err := dir.MkdirAll(); err != nil {
|
||||
|
@ -36,6 +42,8 @@ func (s *stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepActio
|
|||
|
||||
s.dir = dir
|
||||
|
||||
state.Put("dir", dir)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,19 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
|
|||
// Wait the wait amount
|
||||
if int64(config.bootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait.String()))
|
||||
time.Sleep(config.bootWait)
|
||||
wait := time.After(config.bootWait)
|
||||
WAITLOOP:
|
||||
for {
|
||||
select {
|
||||
case <-wait:
|
||||
break WAITLOOP
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package vmware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
// This step suppresses any messages that VMware product might show.
|
||||
type stepSuppressMessages struct{}
|
||||
|
||||
func (s *stepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vmxPath := state.Get("vmx_path").(string)
|
||||
|
||||
log.Println("Suppressing messages in VMX")
|
||||
if err := driver.SuppressMessages(vmxPath); err != nil {
|
||||
err := fmt.Errorf("Error suppressing messages: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepSuppressMessages) Cleanup(state multistep.StateBag) {}
|
|
@ -16,10 +16,16 @@ import (
|
|||
//
|
||||
// Produces:
|
||||
// <nothing>
|
||||
type StepProvision struct{}
|
||||
type StepProvision struct {
|
||||
Comm packer.Communicator
|
||||
}
|
||||
|
||||
func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction {
|
||||
comm := s.Comm
|
||||
if comm == nil {
|
||||
comm = state.Get("communicator").(packer.Communicator)
|
||||
}
|
||||
|
||||
func (*StepProvision) Run(state multistep.StateBag) multistep.StepAction {
|
||||
comm := state.Get("communicator").(packer.Communicator)
|
||||
hook := state.Get("hook").(packer.Hook)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ const defaultConfig = `
|
|||
"amazon-chroot": "packer-builder-amazon-chroot",
|
||||
"amazon-instance": "packer-builder-amazon-instance",
|
||||
"digitalocean": "packer-builder-digitalocean",
|
||||
"docker": "packer-builder-docker",
|
||||
"openstack": "packer-builder-openstack",
|
||||
"qemu": "packer-builder-qemu",
|
||||
"virtualbox": "packer-builder-virtualbox",
|
||||
|
@ -37,7 +38,8 @@ const defaultConfig = `
|
|||
},
|
||||
|
||||
"post-processors": {
|
||||
"vagrant": "packer-post-processor-vagrant"
|
||||
"vagrant": "packer-post-processor-vagrant",
|
||||
"vsphere": "packer-post-processor-vsphere"
|
||||
},
|
||||
|
||||
"provisioners": {
|
||||
|
|
|
@ -82,6 +82,7 @@ func wrappedMain() int {
|
|||
"Packer Version: %s %s %s",
|
||||
packer.Version, packer.VersionPrerelease, packer.GitCommit)
|
||||
log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH)
|
||||
log.Printf("Built with Go Version: %s", runtime.Version())
|
||||
|
||||
// Prepare stdin for plugin usage by switching it to a pipe
|
||||
setupStdin()
|
||||
|
|
|
@ -106,7 +106,7 @@ func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
|
|||
attr = 1
|
||||
}
|
||||
|
||||
return fmt.Sprintf("\033[%d;%d;40m%s\033[0m", attr, color, message)
|
||||
return fmt.Sprintf("\033[%d;%dm%s\033[0m", attr, color, message)
|
||||
}
|
||||
|
||||
func (u *ColoredUi) supportsColors() bool {
|
||||
|
|
|
@ -19,19 +19,19 @@ func TestColoredUi(t *testing.T) {
|
|||
|
||||
ui.Say("foo")
|
||||
result := readWriter(bufferUi)
|
||||
if result != "\033[1;33;40mfoo\033[0m\n" {
|
||||
if result != "\033[1;33mfoo\033[0m\n" {
|
||||
t.Fatalf("invalid output: %s", result)
|
||||
}
|
||||
|
||||
ui.Message("foo")
|
||||
result = readWriter(bufferUi)
|
||||
if result != "\033[0;33;40mfoo\033[0m\n" {
|
||||
if result != "\033[0;33mfoo\033[0m\n" {
|
||||
t.Fatalf("invalid output: %s", result)
|
||||
}
|
||||
|
||||
ui.Error("foo")
|
||||
result = readWriter(bufferUi)
|
||||
if result != "\033[1;31;40mfoo\033[0m\n" {
|
||||
if result != "\033[1;31mfoo\033[0m\n" {
|
||||
t.Fatalf("invalid output: %s", result)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
var GitCommit string
|
||||
|
||||
// The version of packer.
|
||||
const Version = "0.4.0"
|
||||
const Version = "0.4.1"
|
||||
|
||||
// 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,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/builder/docker"
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.ServeBuilder(new(docker.Builder))
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package main
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
"github.com/mitchellh/packer/post-processor/vsphere"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.ServePostProcessor(new(vsphere.PostProcessor))
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package main
|
|
@ -2,6 +2,7 @@ package vagrant
|
|||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -52,13 +53,19 @@ func DirToBox(dst, dir string, ui packer.Ui, level int) error {
|
|||
}
|
||||
defer dstF.Close()
|
||||
|
||||
gzipWriter, err := gzip.NewWriterLevel(dstF, level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzipWriter.Close()
|
||||
var dstWriter io.Writer = dstF
|
||||
if level != flate.NoCompression {
|
||||
log.Printf("Compressing with gzip compression level: %d", level)
|
||||
gzipWriter, err := gzip.NewWriterLevel(dstWriter, level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzipWriter.Close()
|
||||
|
||||
tarWriter := tar.NewWriter(gzipWriter)
|
||||
dstWriter = gzipWriter
|
||||
}
|
||||
|
||||
tarWriter := tar.NewWriter(dstWriter)
|
||||
defer tarWriter.Close()
|
||||
|
||||
// This is the walk func that tars each of the files in the dir
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
package vsphere
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var builtins = map[string]string{
|
||||
"mitchellh.vmware": "vmware",
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
Datacenter string `mapstructure:"datacenter"`
|
||||
Datastore string `mapstructure:"datastore"`
|
||||
Host string `mapstructure:"host"`
|
||||
Password string `mapstructure:"password"`
|
||||
PathToResourcePool string `mapstructure:"path_to_resource_pool"`
|
||||
Username string `mapstructure:"username"`
|
||||
VMFolder string `mapstructure:"vm_folder"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
VMNetwork string `mapstructure:"vm_network"`
|
||||
}
|
||||
|
||||
type PostProcessor struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||
_, err := common.DecodeConfig(&p.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tpl, err := packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tpl.UserVars = p.config.PackerUserVars
|
||||
|
||||
// Accumulate any errors
|
||||
errs := new(packer.MultiError)
|
||||
|
||||
if _, err := exec.LookPath("ovftool"); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("ovftool not found: %s", err))
|
||||
}
|
||||
|
||||
validates := map[string]*string{
|
||||
"datacenter": &p.config.Datacenter,
|
||||
"datastore": &p.config.Datastore,
|
||||
"host": &p.config.Host,
|
||||
"vm_network": &p.config.VMNetwork,
|
||||
"password": &p.config.Password,
|
||||
"path_to_resource_pool": &p.config.PathToResourcePool,
|
||||
"username": &p.config.Username,
|
||||
"vm_folder": &p.config.VMFolder,
|
||||
"vm_name": &p.config.VMName,
|
||||
}
|
||||
|
||||
for n := range validates {
|
||||
if *validates[n] == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("%s must be set", n))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
if _, ok := builtins[artifact.BuilderId()]; !ok {
|
||||
return nil, false, fmt.Errorf("Unknown artifact type, can't build box: %s", artifact.BuilderId())
|
||||
}
|
||||
|
||||
vmx := ""
|
||||
for _, path := range artifact.Files() {
|
||||
if strings.HasSuffix(path, ".vmx") {
|
||||
vmx = path
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if vmx == "" {
|
||||
return nil, false, fmt.Errorf("VMX file not found")
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Uploading %s to vSphere", vmx))
|
||||
|
||||
args := []string{
|
||||
fmt.Sprintf("--noSSLVerify=%t", p.config.Insecure),
|
||||
"--acceptAllEulas",
|
||||
fmt.Sprintf("--name=%s", p.config.VMName),
|
||||
fmt.Sprintf("--datastore=%s", p.config.Datastore),
|
||||
fmt.Sprintf("--network=%s", p.config.VMNetwork),
|
||||
fmt.Sprintf("--vmFolder=%s", p.config.VMFolder),
|
||||
fmt.Sprintf("vi://%s:%s@%s/%s/%s",
|
||||
p.config.Username,
|
||||
p.config.Password,
|
||||
p.config.Host,
|
||||
p.config.Datacenter,
|
||||
p.config.PathToResourcePool),
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
cmd := exec.Command("ovftool", args...)
|
||||
cmd.Stdout = &out
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, false, fmt.Errorf("Failed: %s\nStdout: %s", err, out.String())
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("%s", out.String()))
|
||||
|
||||
return artifact, false, nil
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package vsphere
|
|
@ -18,20 +18,21 @@ import (
|
|||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
ChefEnvironment string `mapstructure:"chef_environment"`
|
||||
ConfigTemplate string `mapstructure:"config_template"`
|
||||
CookbookPaths []string `mapstructure:"cookbook_paths"`
|
||||
RolesPath string `mapstructure:"roles_path"`
|
||||
DataBagsPath string `mapstructure:"data_bags_path"`
|
||||
EnvironmentsPath string `mapstructure:"environments_path"`
|
||||
ExecuteCommand string `mapstructure:"execute_command"`
|
||||
InstallCommand string `mapstructure:"install_command"`
|
||||
RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths"`
|
||||
Json map[string]interface{}
|
||||
PreventSudo bool `mapstructure:"prevent_sudo"`
|
||||
RunList []string `mapstructure:"run_list"`
|
||||
SkipInstall bool `mapstructure:"skip_install"`
|
||||
StagingDir string `mapstructure:"staging_directory"`
|
||||
ChefEnvironment string `mapstructure:"chef_environment"`
|
||||
ConfigTemplate string `mapstructure:"config_template"`
|
||||
CookbookPaths []string `mapstructure:"cookbook_paths"`
|
||||
RolesPath string `mapstructure:"roles_path"`
|
||||
DataBagsPath string `mapstructure:"data_bags_path"`
|
||||
EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"`
|
||||
EnvironmentsPath string `mapstructure:"environments_path"`
|
||||
ExecuteCommand string `mapstructure:"execute_command"`
|
||||
InstallCommand string `mapstructure:"install_command"`
|
||||
RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths"`
|
||||
Json map[string]interface{}
|
||||
PreventSudo bool `mapstructure:"prevent_sudo"`
|
||||
RunList []string `mapstructure:"run_list"`
|
||||
SkipInstall bool `mapstructure:"skip_install"`
|
||||
StagingDir string `mapstructure:"staging_directory"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
@ -41,18 +42,20 @@ type Provisioner struct {
|
|||
}
|
||||
|
||||
type ConfigTemplate struct {
|
||||
CookbookPaths string
|
||||
DataBagsPath string
|
||||
RolesPath string
|
||||
EnvironmentsPath string
|
||||
ChefEnvironment string
|
||||
CookbookPaths string
|
||||
DataBagsPath string
|
||||
EncryptedDataBagSecretPath string
|
||||
RolesPath string
|
||||
EnvironmentsPath string
|
||||
ChefEnvironment string
|
||||
|
||||
// Templates don't support boolean statements until Go 1.2. In the
|
||||
// mean time, we do this.
|
||||
// TODO(mitchellh): Remove when Go 1.2 is released
|
||||
HasDataBagsPath bool
|
||||
HasRolesPath bool
|
||||
HasEnvironmentsPath bool
|
||||
HasDataBagsPath bool
|
||||
HasEncryptedDataBagSecretPath bool
|
||||
HasRolesPath bool
|
||||
HasEnvironmentsPath bool
|
||||
}
|
||||
|
||||
type ExecuteTemplate struct {
|
||||
|
@ -97,12 +100,13 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
|||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
templates := map[string]*string{
|
||||
"config_template": &p.config.ConfigTemplate,
|
||||
"data_bags_path": &p.config.DataBagsPath,
|
||||
"roles_path": &p.config.RolesPath,
|
||||
"staging_dir": &p.config.StagingDir,
|
||||
"environments_path": &p.config.EnvironmentsPath,
|
||||
"chef_environment": &p.config.ChefEnvironment,
|
||||
"config_template": &p.config.ConfigTemplate,
|
||||
"data_bags_path": &p.config.DataBagsPath,
|
||||
"encrypted_data_bag_secret": &p.config.EncryptedDataBagSecretPath,
|
||||
"roles_path": &p.config.RolesPath,
|
||||
"staging_dir": &p.config.StagingDir,
|
||||
"environments_path": &p.config.EnvironmentsPath,
|
||||
"chef_environment": &p.config.ChefEnvironment,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
|
@ -181,6 +185,15 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
|||
}
|
||||
}
|
||||
|
||||
if p.config.EncryptedDataBagSecretPath != "" {
|
||||
pFileInfo, err := os.Stat(p.config.EncryptedDataBagSecretPath)
|
||||
|
||||
if err != nil || pFileInfo.IsDir() {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Bad encrypted data bag secret '%s': %s", p.config.EncryptedDataBagSecretPath, err))
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.EnvironmentsPath != "" {
|
||||
pFileInfo, err := os.Stat(p.config.EnvironmentsPath)
|
||||
|
||||
|
@ -206,6 +219,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
|||
}
|
||||
|
||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
ui.Say("Provisioning with chef-solo")
|
||||
|
||||
if !p.config.SkipInstall {
|
||||
if err := p.installChef(ui, comm); err != nil {
|
||||
return fmt.Errorf("Error installing Chef: %s", err)
|
||||
|
@ -242,6 +257,14 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|||
}
|
||||
}
|
||||
|
||||
encryptedDataBagSecretPath := ""
|
||||
if p.config.EncryptedDataBagSecretPath != "" {
|
||||
encryptedDataBagSecretPath = fmt.Sprintf("%s/encrypted_data_bag_secret", p.config.StagingDir)
|
||||
if err := p.uploadFile(ui, comm, encryptedDataBagSecretPath, p.config.EncryptedDataBagSecretPath); err != nil {
|
||||
return fmt.Errorf("Error uploading encrypted data bag secret: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
environmentsPath := ""
|
||||
if p.config.EnvironmentsPath != "" {
|
||||
environmentsPath = fmt.Sprintf("%s/environments", p.config.StagingDir)
|
||||
|
@ -250,7 +273,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|||
}
|
||||
}
|
||||
|
||||
configPath, err := p.createConfig(ui, comm, cookbookPaths, rolesPath, dataBagsPath, environmentsPath, p.config.ChefEnvironment)
|
||||
configPath, err := p.createConfig(ui, comm, cookbookPaths, rolesPath, dataBagsPath, encryptedDataBagSecretPath, environmentsPath, p.config.ChefEnvironment)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating Chef config file: %s", err)
|
||||
}
|
||||
|
@ -287,7 +310,17 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
|
|||
return comm.UploadDir(dst, src, nil)
|
||||
}
|
||||
|
||||
func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string, rolesPath string, dataBagsPath string, environmentsPath string, chefEnvironment string) (string, error) {
|
||||
func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return comm.Upload(dst, f)
|
||||
}
|
||||
|
||||
func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string, rolesPath string, dataBagsPath string, encryptedDataBagSecretPath string, environmentsPath string, chefEnvironment string) (string, error) {
|
||||
ui.Message("Creating configuration file 'solo.rb'")
|
||||
|
||||
cookbook_paths := make([]string, len(p.config.RemoteCookbookPaths)+len(localCookbooks))
|
||||
|
@ -318,14 +351,16 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local
|
|||
}
|
||||
|
||||
configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{
|
||||
CookbookPaths: strings.Join(cookbook_paths, ","),
|
||||
RolesPath: rolesPath,
|
||||
DataBagsPath: dataBagsPath,
|
||||
EnvironmentsPath: environmentsPath,
|
||||
HasRolesPath: rolesPath != "",
|
||||
HasDataBagsPath: dataBagsPath != "",
|
||||
HasEnvironmentsPath: environmentsPath != "",
|
||||
ChefEnvironment: chefEnvironment,
|
||||
CookbookPaths: strings.Join(cookbook_paths, ","),
|
||||
RolesPath: rolesPath,
|
||||
DataBagsPath: dataBagsPath,
|
||||
EncryptedDataBagSecretPath: encryptedDataBagSecretPath,
|
||||
EnvironmentsPath: environmentsPath,
|
||||
HasRolesPath: rolesPath != "",
|
||||
HasDataBagsPath: dataBagsPath != "",
|
||||
HasEncryptedDataBagSecretPath: encryptedDataBagSecretPath != "",
|
||||
HasEnvironmentsPath: environmentsPath != "",
|
||||
ChefEnvironment: chefEnvironment,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -483,6 +518,9 @@ role_path "{{.RolesPath}}"
|
|||
{{if .HasDataBagsPath}}
|
||||
data_bag_path "{{.DataBagsPath}}"
|
||||
{{end}}
|
||||
{{if .HasEncryptedDataBagSecretPath}}
|
||||
encrypted_data_bag_secret "{{.EncryptedDataBagSecretPath}}"
|
||||
{{end}}
|
||||
{{if .HasEnvironmentsPath}}
|
||||
environments_path "{{.EnvironmentsPath}}"
|
||||
chef_environment "{{.ChefEnvironment}}"
|
||||
|
|
|
@ -155,6 +155,49 @@ func TestProvisionerPrepare_dataBagsPath(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_encryptedDataBagSecretPath(t *testing.T) {
|
||||
var err error
|
||||
var p Provisioner
|
||||
|
||||
// Test no config template
|
||||
config := testConfig()
|
||||
delete(config, "encrypted_data_bag_secret_path")
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a file
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config = testConfig()
|
||||
config["encrypted_data_bag_secret_path"] = tf.Name()
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with a directory
|
||||
td, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
config = testConfig()
|
||||
config["encrypted_data_bag_secret_path"] = td
|
||||
p = Provisioner{}
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_environmentsPath(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
|
|
101
scripts/build.sh
101
scripts/build.sh
|
@ -1,101 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# This script only builds the application from source.
|
||||
set -e
|
||||
|
||||
NO_COLOR="\x1b[0m"
|
||||
OK_COLOR="\x1b[32;01m"
|
||||
ERROR_COLOR="\x1b[31;01m"
|
||||
WARN_COLOR="\x1b[33;01m"
|
||||
|
||||
# Get the parent directory of where this script is.
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
|
||||
|
||||
# Change into that directory
|
||||
cd $DIR
|
||||
|
||||
# Get the git commit
|
||||
GIT_COMMIT=$(git rev-parse HEAD)
|
||||
GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||
|
||||
# If we're building on Windows, specify an extension
|
||||
EXTENSION=""
|
||||
if [ "$(go env GOOS)" = "windows" ]; then
|
||||
EXTENSION=".exe"
|
||||
fi
|
||||
|
||||
# Make sure that if we're killed, we kill all our subprocseses
|
||||
trap "kill 0" SIGINT SIGTERM EXIT
|
||||
|
||||
# If we're building a race-enabled build, then set that up.
|
||||
if [ ! -z $PACKER_RACE ]; then
|
||||
echo -e "${OK_COLOR}--> Building with race detection enabled${NO_COLOR}"
|
||||
PACKER_RACE="-race"
|
||||
fi
|
||||
|
||||
echo -e "${OK_COLOR}--> Installing dependencies to speed up builds...${NO_COLOR}"
|
||||
go get ./...
|
||||
|
||||
# This function waits for all background tasks to complete
|
||||
waitAll() {
|
||||
RESULT=0
|
||||
for job in `jobs -p`; do
|
||||
wait $job
|
||||
if [ $? -ne 0 ]; then
|
||||
RESULT=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $RESULT -ne 0 ]; then
|
||||
exit $RESULT
|
||||
fi
|
||||
}
|
||||
|
||||
waitSingle() {
|
||||
if [ ! -z $PACKER_NO_BUILD_PARALLEL ]; then
|
||||
waitAll
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -z $PACKER_NO_BUILD_PARALLEL ]; then
|
||||
echo -e "${OK_COLOR}--> NOTE: Compilation of components " \
|
||||
"will be done in parallel.${NO_COLOR}"
|
||||
fi
|
||||
|
||||
# Compile the main Packer app
|
||||
echo -e "${OK_COLOR}--> Compiling Packer${NO_COLOR}"
|
||||
(
|
||||
go build \
|
||||
${PACKER_RACE} \
|
||||
-ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
|
||||
-v \
|
||||
-o bin/packer${EXTENSION} .
|
||||
|
||||
cp bin/packer${EXTENSION} ${GOPATH}/bin
|
||||
) &
|
||||
|
||||
waitSingle
|
||||
|
||||
# Go over each plugin and build it
|
||||
for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do
|
||||
PLUGIN_NAME=$(basename ${PLUGIN})
|
||||
echo -e "${OK_COLOR}--> Compiling Plugin: ${PLUGIN_NAME}${NO_COLOR}"
|
||||
(
|
||||
go build \
|
||||
${PACKER_RACE} \
|
||||
-ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
|
||||
-v \
|
||||
-o bin/packer-${PLUGIN_NAME}${EXTENSION} ${PLUGIN}
|
||||
|
||||
cp bin/packer-${PLUGIN_NAME}${EXTENSION} ${GOPATH}/bin
|
||||
) &
|
||||
|
||||
waitSingle
|
||||
done
|
||||
|
||||
waitAll
|
||||
|
||||
# Reset signal trapping to avoid "Terminated: 15" at the end
|
||||
trap - SIGINT SIGTERM EXIT
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# This script compiles Packer for various platforms (specified by the
|
||||
# PACKER_OS and PACKER_ARCH environmental variables).
|
||||
set -e
|
||||
|
||||
NO_COLOR="\x1b[0m"
|
||||
OK_COLOR="\x1b[32;01m"
|
||||
ERROR_COLOR="\x1b[31;01m"
|
||||
WARN_COLOR="\x1b[33;01m"
|
||||
|
||||
# Get the parent directory of where this script is.
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
|
||||
|
||||
# Change into that directory
|
||||
cd $DIR
|
||||
|
||||
# Get the git commit
|
||||
GIT_COMMIT=$(git rev-parse HEAD)
|
||||
GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||
|
||||
# Determine the arch/os combos we're building for
|
||||
XC_ARCH=${XC_ARCH:-"386 amd64 arm"}
|
||||
XC_OS=${XC_OS:-linux darwin windows freebsd openbsd}
|
||||
|
||||
# Make sure that if we're killed, we kill all our subprocseses
|
||||
trap "kill 0" SIGINT SIGTERM EXIT
|
||||
|
||||
echo -e "${OK_COLOR}==> Installing dependencies to speed up builds...${NO_COLOR}"
|
||||
go get ./...
|
||||
|
||||
echo -e "${OK_COLOR}==> Beginning compile...${NO_COLOR}"
|
||||
rm -rf pkg/
|
||||
gox \
|
||||
-os="${XC_OS}" \
|
||||
-arch="${XC_ARCH}" \
|
||||
-ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
|
||||
-output "pkg/{{.OS}}_{{.Arch}}/packer-{{.Dir}}" \
|
||||
./...
|
||||
|
||||
# Make sure "packer-packer" is renamed properly
|
||||
for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do
|
||||
set +e
|
||||
mv ${PLATFORM}/packer-packer ${PLATFORM}/packer 2>/dev/null
|
||||
mv ${PLATFORM}/packer-packer.exe ${PLATFORM}/packer.exe 2>/dev/null
|
||||
set -e
|
||||
done
|
||||
|
||||
# Reset signal trapping to avoid "Terminated: 15" at the end
|
||||
trap - SIGINT SIGTERM EXIT
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# This script only builds the application from source.
|
||||
set -e
|
||||
|
||||
NO_COLOR="\x1b[0m"
|
||||
OK_COLOR="\x1b[32;01m"
|
||||
ERROR_COLOR="\x1b[31;01m"
|
||||
WARN_COLOR="\x1b[33;01m"
|
||||
|
||||
# Get the parent directory of where this script is.
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
|
||||
|
||||
# Change into that directory
|
||||
cd $DIR
|
||||
|
||||
# Compile the thing
|
||||
export XC_ARCH=$(go env GOARCH)
|
||||
export XC_OS=$(go env GOOS)
|
||||
./scripts/compile.sh
|
||||
|
||||
# Move all the compiled things to the PATH
|
||||
cp pkg/${XC_OS}_${XC_ARCH}/* ${GOPATH}/bin
|
|
@ -19,28 +19,6 @@ if [ ! -z $PREVERSION ]; then
|
|||
VERSIONDIR="${VERSIONDIR}-${PREVERSION}"
|
||||
fi
|
||||
|
||||
echo "Version: ${VERSION} ${PREVERSION}"
|
||||
|
||||
# Determine the arch/os combos we're building for
|
||||
XC_ARCH=${XC_ARCH:-"386 amd64 arm"}
|
||||
XC_OS=${XC_OS:-linux darwin windows freebsd openbsd}
|
||||
|
||||
echo "Arch: ${XC_ARCH}"
|
||||
echo "OS: ${XC_OS}"
|
||||
|
||||
# This function builds whatever directory we're in...
|
||||
xc() {
|
||||
goxc \
|
||||
-arch="$XC_ARCH" \
|
||||
-os="$XC_OS" \
|
||||
-d="${DIR}/pkg" \
|
||||
-pv="${VERSION}" \
|
||||
-pr="${PREVERSION}" \
|
||||
$XC_OPTS \
|
||||
go-install \
|
||||
xc
|
||||
}
|
||||
|
||||
# This function waits for all background tasks to complete
|
||||
waitAll() {
|
||||
RESULT=0
|
||||
|
@ -56,28 +34,15 @@ waitAll() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Compile the main project
|
||||
./scripts/compile.sh
|
||||
|
||||
# Make sure that if we're killed, we kill all our subprocseses
|
||||
trap "kill 0" SIGINT SIGTERM EXIT
|
||||
|
||||
# Build our root project
|
||||
xc
|
||||
|
||||
# Build all the plugins
|
||||
for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do
|
||||
PLUGIN_NAME=$(basename ${PLUGIN})
|
||||
find ./pkg \
|
||||
-type f \
|
||||
-name ${PLUGIN_NAME} \
|
||||
-execdir mv ${PLUGIN_NAME} packer-${PLUGIN_NAME} ';'
|
||||
find ./pkg \
|
||||
-type f \
|
||||
-name ${PLUGIN_NAME}.exe \
|
||||
-execdir mv ${PLUGIN_NAME}.exe packer-${PLUGIN_NAME}.exe ';'
|
||||
done
|
||||
|
||||
# Zip all the packages
|
||||
mkdir -p ./pkg/${VERSIONDIR}/dist
|
||||
for PLATFORM in $(find ./pkg/${VERSIONDIR} -mindepth 1 -maxdepth 1 -type d); do
|
||||
mkdir -p ./pkg/dist
|
||||
for PLATFORM in $(find ./pkg -mindepth 1 -maxdepth 1 -type d); do
|
||||
PLATFORM_NAME=$(basename ${PLATFORM})
|
||||
ARCHIVE_NAME="${VERSIONDIR}_${PLATFORM_NAME}"
|
||||
|
||||
|
@ -87,7 +52,7 @@ for PLATFORM in $(find ./pkg/${VERSIONDIR} -mindepth 1 -maxdepth 1 -type d); do
|
|||
|
||||
(
|
||||
pushd ${PLATFORM}
|
||||
zip ${DIR}/pkg/${VERSIONDIR}/dist/${ARCHIVE_NAME}.zip ./*
|
||||
zip ${DIR}/pkg/dist/${ARCHIVE_NAME}.zip ./*
|
||||
popd
|
||||
) &
|
||||
done
|
||||
|
@ -95,7 +60,7 @@ done
|
|||
waitAll
|
||||
|
||||
# Make the checksums
|
||||
pushd ./pkg/${VERSIONDIR}/dist
|
||||
pushd ./pkg/dist
|
||||
shasum -a256 * > ./${VERSIONDIR}_SHA256SUMS
|
||||
popd
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ if [ -z $BINTRAY_API_KEY ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
for ARCHIVE in ./pkg/${VERSION}/dist/*; do
|
||||
for ARCHIVE in ./pkg/dist/*; do
|
||||
ARCHIVE_NAME=$(basename ${ARCHIVE})
|
||||
|
||||
echo Uploading: $ARCHIVE_NAME
|
||||
|
|
|
@ -33,7 +33,7 @@ Packer in the form of plugins.
|
|||
|
||||
<a class="term" id="term-command"></a>
|
||||
**Commands** are sub-commands for the `packer` program that perform some
|
||||
job. An example somecommand is "build", which is invoked as `packer build`.
|
||||
job. An example some command is "build", which is invoked as `packer build`.
|
||||
Packer ships with a set of commands out of the box in order to define
|
||||
its [command-line interface](#). Commands can also be created and added to
|
||||
Packer in the form of plugins.
|
||||
|
|
|
@ -184,6 +184,33 @@ out of your AMI builds.
|
|||
Packer properly obtains a process lock for the parallelism-sensitive parts
|
||||
of its internals such as finding an available device.
|
||||
|
||||
## Gotchas
|
||||
|
||||
One of the difficulties with using the chroot builder is that your provisioning
|
||||
scripts must not leave any processes running or packer will be unable to unmount
|
||||
the filesystem.
|
||||
|
||||
For debian based distributions you can setup a [policy-rc.d](http://people.debian.org/~hmh/invokerc.d-policyrc.d-specification.txt) file which will
|
||||
prevent packages installed by your provisioners from starting services:
|
||||
|
||||
<pre class="prettyprint">
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"echo '#!/bin/sh' > /usr/sbin/policy-rc.d",
|
||||
"echo 'exit 101' >> /usr/sbin/policy-rc.d",
|
||||
"chmod a+x /usr/sbin/policy-rc.d"
|
||||
]
|
||||
},
|
||||
# ...
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"rm -f /usr/sbin/policy-rc.d"
|
||||
]
|
||||
}
|
||||
</pre>
|
||||
|
||||
## Using an IAM Instance Profile
|
||||
|
||||
If AWS keys are not specified in the template or through environment variables
|
||||
|
|
|
@ -238,7 +238,7 @@ sudo -n ec2-bundle-vol \
|
|||
-u {{.AccountId}} \
|
||||
-c {{.CertPath}} \
|
||||
-r {{.Architecture}} \
|
||||
-e {{.PrivatePath}} \
|
||||
-e {{.PrivatePath}}/* \
|
||||
-d {{.Destination}} \
|
||||
-p {{.Prefix}} \
|
||||
--batch
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
layout: "docs"
|
||||
---
|
||||
|
||||
# Docker Builder
|
||||
|
||||
Type: `docker`
|
||||
|
||||
The Docker builder builds [Docker](http://www.docker.io) images using
|
||||
Docker. The builder starts a Docker container, runs provisioners within
|
||||
this container, then exports the container for re-use.
|
||||
|
||||
Packer builds Docker containers _without_ the use of
|
||||
[Dockerfiles](http://docs.docker.io/en/latest/use/builder/).
|
||||
By not using Dockerfiles, Packer is able to provision
|
||||
containers with portable scripts or configuration management systems
|
||||
that are not tied to Docker in any way. It also has a simpler mental model:
|
||||
you provision containers much the same way you provision a normal virtualized
|
||||
or dedicated server. For more information, read the section on
|
||||
[Dockerfiles](#toc_3).
|
||||
|
||||
The Docker builder must run on a machine that has Docker installed. Therefore
|
||||
the builder only works on machines that support Docker (modern Linux machines).
|
||||
If you want to use Packer to build Docker containers on another platform,
|
||||
use [Vagrant](http://www.vagrantup.com) to start a Linux environment, then
|
||||
run Packer within that environment.
|
||||
|
||||
## Basic Example
|
||||
|
||||
Below is a fully functioning example. It doesn't do anything useful, since
|
||||
no provisioners are defined, but it will effectively repackage an image.
|
||||
|
||||
<pre class="prettyprint">
|
||||
{
|
||||
"type": "docker",
|
||||
"image": "ubuntu",
|
||||
"export_path": "image.tar"
|
||||
}
|
||||
</pre>
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
Configuration options are organized below into two categories: required and optional. Within
|
||||
each category, the available options are alphabetized and described.
|
||||
|
||||
Required:
|
||||
|
||||
* `export_path` (string) - The path where the final container will be exported
|
||||
as a tar file.
|
||||
|
||||
* `image` (string) - The base image for the Docker container that will
|
||||
be started. This image will be pulled from the Docker registry if it
|
||||
doesn't already exist.
|
||||
|
||||
Optional:
|
||||
|
||||
* `pull` (bool) - If true, the configured image will be pulled using
|
||||
`docker pull` prior to use. Otherwise, it is assumed the image already
|
||||
exists and can be used. This defaults to true if not set.
|
||||
|
||||
## Dockerfiles
|
||||
|
||||
This builder allows you to build Docker images _without_ Dockerfiles.
|
||||
|
||||
With this builder, you can repeatably create Docker images without the use
|
||||
a Dockerfile. You don't need to know the syntax or semantics of Dockerfiles.
|
||||
Instead, you can just provide shell scripts, Chef recipes, Puppet manifests,
|
||||
etc. to provision your Docker container just like you would a regular
|
||||
virtualized or dedicated machine.
|
||||
|
||||
While Docker has many features, Packer views Docker simply as an LXC
|
||||
container runner. To that end, Packer is able to repeatably build these
|
||||
LXC containers using portable provisioning scripts.
|
||||
|
||||
Dockerfiles have some additional features that Packer doesn't support
|
||||
which are able to be worked around. Many of these features will be automated
|
||||
by Packer in the future:
|
||||
|
||||
* Dockerfiles will snapshot the container at each step, allowing you to
|
||||
go back to any step in the history of building. Packer doesn't do this yet,
|
||||
but inter-step snapshotting is on the way.
|
||||
|
||||
* Dockerfiles can contain information such as exposed ports, shared
|
||||
volumes, and other metadata. Packer builds a raw Docker container image
|
||||
that has none of this metadata. You can pass in much of this metadata
|
||||
at runtime with `docker run`.
|
||||
|
||||
* Images made without dockerfiles are missing critical metadata that
|
||||
make them easily pushable to the Docker registry. You can work around
|
||||
this by using a metadata-only Dockerfile with the exported image and
|
||||
building that. A future Packer version will automatically do this for you.
|
|
@ -152,7 +152,7 @@ Optional:
|
|||
access the remote machine. By default this is empty. This only has an
|
||||
effect if `remote_type` is enabled.
|
||||
|
||||
* `remote_user` (string) - The username for the SSH user that will access
|
||||
* `remote_username` (string) - The username for the SSH user that will access
|
||||
the remote machine. This is required if `remote_type` is enabled.
|
||||
|
||||
* `skip_compaction` (bool) - VMware-created disks are defragmented
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "vSphere Post-Processor"
|
||||
---
|
||||
|
||||
# vSphere Post-Processor
|
||||
|
||||
Type: `vsphere-upload`
|
||||
|
||||
The vSphere post-processor takes an artifact from the VMware builder
|
||||
and uploads it to a vSphere endpoint.
|
||||
|
||||
## Configuration
|
||||
|
||||
There are many configuration options available for the post-processor. They are
|
||||
segmented below into two categories: required and optional parameters. Within
|
||||
each category, the available configuration keys are alphabetized.
|
||||
|
||||
Required:
|
||||
|
||||
* `datacenter` (string) - The name of the datacenter within vSphere to
|
||||
add the VM to.
|
||||
|
||||
* `datastore` (string) - The name of the datastore to store this VM.
|
||||
|
||||
* `host` (string) - The vSphere host that will be contacted to perform
|
||||
the VM upload.
|
||||
|
||||
* `password` (string) - Password to use to authenticate to the vSphere
|
||||
endpoint.
|
||||
|
||||
* `path_to_resource_pool` (string) - The path within the resource pool to
|
||||
store the VM.
|
||||
|
||||
* `username` (string) - The username to use to authenticate to the vSphere
|
||||
endpoint.
|
||||
|
||||
* `vm_folder` (string) - The folder within the datastore to store the VM.
|
||||
|
||||
* `vm_name` (string) - The name of the VM once it is uploaded.
|
||||
|
||||
* `vm_network` (string) - The name of the VM network this VM will be
|
||||
added to.
|
||||
|
||||
Optional:
|
||||
|
||||
* `insecure` (bool) - Whether or not the connection to vSphere can be done
|
||||
over an insecure connection. By default this is false.
|
|
@ -44,13 +44,13 @@ configuration is actually required, but at least `run_list` is recommended.
|
|||
to the remote machine in the directory specified by the `staging_directory`.
|
||||
By default, this is empty.
|
||||
|
||||
* `roles_path` (string) - The path to the "roles" directory on your local filesystem.
|
||||
These will be uploaded to the remote machine in the directory specified by the
|
||||
* `data_bags_path` (string) - The path to the "data\_bags" directory on your local filesystem.
|
||||
These will be uploaded to the remote machine in the directory specified by the
|
||||
`staging_directory`. By default, this is empty.
|
||||
|
||||
* `data_bags_path` (string) - The path to the "data_bags" directory on your local filesystem.
|
||||
These will be uploaded to the remote machine in the directory specified by the
|
||||
`staging_directory`. By default, this is empty.
|
||||
* `encrypted_data_bag_secret_path` (string) - The path to the file containing
|
||||
the secret for encrypted data bags. By default, this is empty, so no
|
||||
secret will be available.
|
||||
|
||||
* `execute_command` (string) - The command used to execute Chef. This has
|
||||
various [configuration template variables](/docs/templates/configuration-templates.html)
|
||||
|
@ -60,18 +60,22 @@ configuration is actually required, but at least `run_list` is recommended.
|
|||
various [configuration template variables](/docs/templates/configuration-templates.html)
|
||||
available. See below for more information.
|
||||
|
||||
* `json` (object) - An arbitrary mapping of JSON that will be available as
|
||||
node attributes while running Chef.
|
||||
|
||||
* `remote_cookbook_paths` (array of string) - A list of paths on the remote
|
||||
machine where cookbooks will already exist. These may exist from a previous
|
||||
provisioner or step. If specified, Chef will be configured to look for
|
||||
cookbooks here. By default, this is empty.
|
||||
|
||||
* `json` (object) - An arbitrary mapping of JSON that will be available as
|
||||
node attributes while running Chef.
|
||||
|
||||
* `prevent_sudo` (boolean) - By default, the configured commands that are
|
||||
executed to install and run Chef are executed with `sudo`. If this is true,
|
||||
then the sudo will be omitted.
|
||||
|
||||
* `roles_path` (string) - The path to the "roles" directory on your local filesystem.
|
||||
These will be uploaded to the remote machine in the directory specified by the
|
||||
`staging_directory`. By default, this is empty.
|
||||
|
||||
* `run_list` (array of strings) - The [run list](http://docs.opscode.com/essentials_node_object_run_lists.html)
|
||||
for Chef. By default this is empty.
|
||||
|
||||
|
@ -101,8 +105,14 @@ cookbook_path [{{.CookbookPaths}}]
|
|||
This template is a [configuration template](/docs/templates/configuration-templates.html)
|
||||
and has a set of variables available to use:
|
||||
|
||||
* `ChefEnvironment` - The current enabled environment. Only non-empty
|
||||
if the environment path is set.
|
||||
* `CookbookPaths` is the set of cookbook paths ready to embedded directly
|
||||
into a Ruby array to configure Chef.
|
||||
* `DataBagsPath` is the path to the data bags folder.
|
||||
* `EncryptedDataBagSecretPath` - The path to the encrypted data bag secret
|
||||
* `EnvironmentsPath` - The path to the environments folder.
|
||||
* `RolesPath` - The path the folders folder.
|
||||
|
||||
## Execute Command
|
||||
|
||||
|
@ -121,6 +131,7 @@ As you can see from the default value above, the value of this configuration
|
|||
can contain various template variables, defined below:
|
||||
|
||||
* `ConfigPath` - The path to the Chef configuration file.
|
||||
file.
|
||||
* `JsonPath` - The path to the JSON attributes file for the node.
|
||||
* `Sudo` - A boolean of whether to `sudo` the command or not, depending on
|
||||
the value of the `prevent_sudo` configuration.
|
||||
|
|
|
@ -64,7 +64,7 @@ array.
|
|||
{
|
||||
"type": "digitalocean",
|
||||
"api_key": "INSERT API KEY HERE",
|
||||
"client_id": "INSERT CILENT ID HERE"
|
||||
"client_id": "INSERT CLIENT ID HERE"
|
||||
}
|
||||
</pre>
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
<li><h4>Builders</h4></li>
|
||||
<li><a href="/docs/builders/amazon.html">Amazon EC2 (AMI)</a></li>
|
||||
<li><a href="/docs/builders/digitalocean.html">DigitalOcean</a></li>
|
||||
<li><a href="/docs/builders/docker.html">Docker</a></li>
|
||||
<li><a href="/docs/builders/openstack.html">OpenStack</a></li>
|
||||
<li><a href="/docs/builders/qemu.html">QEMU</a></li>
|
||||
<li><a href="/docs/builders/virtualbox.html">VirtualBox</a></li>
|
||||
|
@ -53,6 +54,7 @@
|
|||
<ul>
|
||||
<li><h4>Post-Processors</h4></li>
|
||||
<li><a href="/docs/post-processors/vagrant.html">Vagrant</a></li>
|
||||
<li><a href="/docs/post-processors/vsphere.html">vSphere</a></li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
|
|
Loading…
Reference in New Issue