Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df560dbbe1 | |||
| 259986e7e5 | |||
| 40e9f9a76f | |||
| 2f698cca77 | |||
| 7247a69581 | |||
| 2958b5d9c2 | |||
| a8da773e40 | |||
| 663cce5aaa | |||
| 82a3aba0d3 | |||
| 2e15bed7b6 | |||
| 9264f76053 | |||
| ae279a8b61 | |||
| f31f5ed759 | |||
| 8ce8171ec1 | |||
| b4b9469d3c | |||
| 925734d06b | |||
| 2e88ddc965 | |||
| b729ba0e00 | |||
| 4bb5f8a6aa | |||
| 2fc795d299 | |||
| f931b28851 | |||
| cf13f57528 | |||
| aff4389fc8 | |||
| 4d4dd85ef4 | |||
| be40fb7abb | |||
| 00a5ca32ae | |||
| 93e5082d04 | |||
| f36972f52a | |||
| 8ece9a60d5 | |||
| 432cab3fcf | |||
| 586a9a7c32 | |||
| 359ba01c6a | |||
| dbbbc38af6 | |||
| f2111b6d12 | |||
| f27ea933a4 | |||
| 4aa55c40de | |||
| 06108b4f96 | |||
| 0e3b1e59a8 | |||
| 29fa621907 | |||
| d4cd9352d6 | |||
| 4de76ccd3f | |||
| ac5c90bd7e | |||
| 7bff7f8a22 | |||
| 126f2bc07f | |||
| 0b1b76a4db | |||
| 9ca3d05241 | |||
| 2a7a7173f1 | |||
| 8e231f9264 | |||
| 3f9df2992c | |||
| 400faa57a5 | |||
| fc78bf3dd6 | |||
| 8cf21324dc | |||
| 974ac26c9c | |||
| b6884da2a1 | |||
| 9f17257f19 | |||
| 5bf33a0e91 | |||
| f623c0cb3b | |||
| 4ad77b9d9a | |||
| f270701858 | |||
| 855e8aab85 | |||
| da225c0d06 | |||
| b781077ed9 | |||
| 8120cf9adf | |||
| 222b15e8f3 | |||
| 7f17c58335 | |||
| 8da6ea720e | |||
| 05c6c87eb2 | |||
| 60c0cb6fc8 | |||
| cf99d640e3 | |||
| d306fe742c | |||
| 2708c5e3eb | |||
| 986a66ee19 | |||
| e9f7f94599 | |||
| 6f705d5bcb | |||
| 280d176f73 | |||
| 832223f321 | |||
| c7d9e88db3 | |||
| d9ab4e210d | |||
| 666a8e11cb | |||
| 05eab61df4 | |||
| 373f093a98 | |||
| b60d0ad834 | |||
| 9d8977fe54 | |||
| a7b684cc7e | |||
| 8f72a06ef6 | |||
| 6878c75c84 | |||
| d997224cc3 | |||
| eb905d30e3 | |||
| 3cda96f9a6 | |||
| 177a374667 | |||
| 8325fbb2f5 | |||
| f155f40f2c | |||
| 31106086fe | |||
| ba483bce71 | |||
| 0021f31931 | |||
| 552ee0b8ee | |||
| 00a16a7ce9 | |||
| 4b2b23c32f | |||
| 9c9c60aabf |
@@ -1,5 +1,6 @@
|
||||
/bin
|
||||
/local
|
||||
/pkg
|
||||
/src
|
||||
/website/.sass-cache
|
||||
/website/build
|
||||
|
||||
+49
-1
@@ -1,4 +1,52 @@
|
||||
## 0.1.2 (unreleased)
|
||||
## 0.1.4 (July 2, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* virtualbox: Can now be built headless with the "Headless" option. [GH-99]
|
||||
* virtualbox: <wait5> and <wait10> codes for waiting 5 and 10 seconds
|
||||
during the boot sequence, respectively. [GH-97]
|
||||
* vmware: Can now be built headless with the "Headless" option. [GH-99]
|
||||
* vmware: <wait5> and <wait10> codes for waiting 5 and 10 seconds
|
||||
during the boot sequence, respectively. [GH-97]
|
||||
* vmware: Disks are defragmented and compacted at the end of the build.
|
||||
This can be disabled using "skip_compaction"
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* core: Template syntax errors now show line and character number. [GH-56]
|
||||
* amazon-ebs: Access key and secret access key default to
|
||||
environmental variables. [GH-40]
|
||||
* virtualbox: Send password for keyboard-interactive auth [GH-121]
|
||||
* vmware: Send password for keyboard-interactive auth [GH-121]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* vmware: Wait until shut down cleans up properly to avoid corrupt
|
||||
disk files [GH-111]
|
||||
|
||||
## 0.1.3 (July 1, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* The VMware builder can now upload the VMware tools for you into
|
||||
the VM. This is opt-in, you must specify the `tools_upload_flavor`
|
||||
option. See the website for more documentation.
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* digitalocean: Errors contain human-friendly error messages. [GH-85]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: More plugin server fixes that avoid hangs on OS X 10.7 [GH-87]
|
||||
* vagrant: AWS boxes will keep the AMI artifact around [GH-55]
|
||||
* virtualbox: More robust version parsing for uploading guest additions. [GH-69]
|
||||
* virtualbox: Output dir and VM name defaults depend on build name,
|
||||
avoiding collisions. [GH-91]
|
||||
* vmware: Output dir and VM name defaults depend on build name,
|
||||
avoiding collisions. [GH-91]
|
||||
|
||||
## 0.1.2 (June 29, 2013)
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
NO_COLOR=\x1b[0m
|
||||
OK_COLOR=\x1b[32;01m
|
||||
ERROR_COLOR=\x1b[31;01m
|
||||
WARN_COLOR=\x1b[33;01m
|
||||
NO_COLOR=\033[0m
|
||||
OK_COLOR=\033[32;01m
|
||||
ERROR_COLOR=\033[31;01m
|
||||
WARN_COLOR=\033[33;01m
|
||||
|
||||
all:
|
||||
@mkdir -p bin/
|
||||
@@ -15,8 +15,7 @@ format:
|
||||
|
||||
test:
|
||||
@echo "$(OK_COLOR)==> Testing Packer...$(NO_COLOR)"
|
||||
@go list -f '{{range .TestImports}}{{.}}\
|
||||
{{end}}' ./... | xargs -n1 go get -d
|
||||
@go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs -n1 go get -d
|
||||
go test ./...
|
||||
|
||||
.PHONY: all format test
|
||||
|
||||
@@ -11,7 +11,7 @@ Packer is lightweight, runs on every major operating system, and is highly
|
||||
performant, creating machine images for multiple platforms in parallel.
|
||||
Packer comes out of the box with support for creating AMIs (EC2), VMware
|
||||
images, and VirtualBox images. Support for more platforms can be added via
|
||||
plugins.
|
||||
plugins.
|
||||
|
||||
The images that Packer creates can easily be turned into
|
||||
[Vagrant](http://www.vagrantup.com) boxes.
|
||||
@@ -70,8 +70,15 @@ http://www.packer.io/docs
|
||||
## Developing Packer
|
||||
|
||||
If you wish to work on Packer itself, you'll first need [Go](http://golang.org)
|
||||
installed (version 1.1+ is _required_). Next, clone this repository then just type `make`.
|
||||
In a few moments, you'll have a working `packer` executable:
|
||||
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.
|
||||
|
||||
Next, clone this repository then just type `make`. In a few moments,
|
||||
you'll have a working `packer` executable:
|
||||
|
||||
```
|
||||
$ make
|
||||
@@ -80,6 +87,6 @@ $ bin/packer
|
||||
...
|
||||
```
|
||||
|
||||
You can run tests by typing `make test`.
|
||||
You can run tests by typing `make test`.
|
||||
|
||||
This will run tests for Packer core along with all the core builders and commands and such that come with Packer.
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
@@ -57,6 +58,22 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.AccessKey == "" {
|
||||
b.config.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
|
||||
}
|
||||
|
||||
if b.config.AccessKey == "" {
|
||||
b.config.AccessKey = os.Getenv("AWS_ACCESS_KEY")
|
||||
}
|
||||
|
||||
if b.config.SecretKey == "" {
|
||||
b.config.SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
|
||||
}
|
||||
|
||||
if b.config.SecretKey == "" {
|
||||
b.config.SecretKey = os.Getenv("AWS_SECRET_KEY")
|
||||
}
|
||||
|
||||
if b.config.SSHPort == 0 {
|
||||
b.config.SSHPort = 22
|
||||
}
|
||||
|
||||
@@ -2,9 +2,19 @@ package amazonebs
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Clear out the AWS access key env vars so they don't
|
||||
// affect our tests.
|
||||
os.Setenv("AWS_ACCESS_KEY_ID", "")
|
||||
os.Setenv("AWS_ACCESS_KEY", "")
|
||||
os.Setenv("AWS_SECRET_ACCESS_KEY", "")
|
||||
os.Setenv("AWS_SECRET_KEY", "")
|
||||
}
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"access_key": "foo",
|
||||
@@ -59,6 +69,31 @@ func TestBuilderPrepare_AccessKey(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test env
|
||||
delete(config, "access_key")
|
||||
os.Setenv("AWS_ACCESS_KEY_ID", "foo")
|
||||
defer os.Setenv("AWS_ACCESS_KEY_ID", "")
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AccessKey != "foo" {
|
||||
t.Errorf("access key invalid: %s", b.config.AccessKey)
|
||||
}
|
||||
|
||||
delete(config, "access_key")
|
||||
os.Setenv("AWS_ACCESS_KEY", "foo")
|
||||
defer os.Setenv("AWS_ACCESS_KEY", "")
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AccessKey != "foo" {
|
||||
t.Errorf("access key invalid: %s", b.config.AccessKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
@@ -167,6 +202,31 @@ func TestBuilderPrepare_SecretKey(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test env
|
||||
delete(config, "secret_key")
|
||||
os.Setenv("AWS_SECRET_ACCESS_KEY", "foo")
|
||||
defer os.Setenv("AWS_SECRET_ACCESS_KEY", "")
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.SecretKey != "foo" {
|
||||
t.Errorf("access key invalid: %s", b.config.SecretKey)
|
||||
}
|
||||
|
||||
delete(config, "secret_key")
|
||||
os.Setenv("AWS_SECRET_KEY", "foo")
|
||||
defer os.Setenv("AWS_SECRET_KEY", "")
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.SecretKey != "foo" {
|
||||
t.Errorf("access key invalid: %s", b.config.SecretKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SourceAmi(t *testing.T) {
|
||||
|
||||
@@ -189,28 +189,25 @@ func NewRequest(d DigitalOceanClient, path string, params string) (map[string]in
|
||||
return decodedResponse, err
|
||||
}
|
||||
|
||||
log.Printf("response from digitalocean: %s", body)
|
||||
|
||||
err = json.Unmarshal(body, &decodedResponse)
|
||||
|
||||
log.Printf("response from digitalocean: %v", decodedResponse)
|
||||
|
||||
// Catch all non-200 status and return an error
|
||||
if resp.StatusCode != 200 {
|
||||
err = errors.New(fmt.Sprintf("Received non-200 HTTP status from DigitalOcean: %v", resp.StatusCode))
|
||||
return decodedResponse, err
|
||||
}
|
||||
|
||||
// Check for bad JSON
|
||||
if err != nil {
|
||||
err = errors.New(fmt.Sprintf("Failed to decode JSON response (HTTP %v) from DigitalOcean: %s",
|
||||
resp.StatusCode, body))
|
||||
return decodedResponse, err
|
||||
}
|
||||
|
||||
// Catch all non-OK statuses from DO and return an error
|
||||
// Check for errors sent by digitalocean
|
||||
status := decodedResponse["status"]
|
||||
if status != "OK" {
|
||||
// Get the actual error message if there is one
|
||||
if status == "ERROR" {
|
||||
status = decodedResponse["error_message"]
|
||||
}
|
||||
err = errors.New(fmt.Sprintf("Received bad status from DigitalOcean: %v", status))
|
||||
err = errors.New(fmt.Sprintf("Received bad response (HTTP %v) from DigitalOcean: %s", resp.StatusCode, status))
|
||||
return decodedResponse, err
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ type config struct {
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
Headless bool `mapstructure:"headless"`
|
||||
HTTPDir string `mapstructure:"http_directory"`
|
||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||
@@ -48,7 +49,8 @@ type config struct {
|
||||
VBoxManage [][]string `mapstructure:"vboxmanage"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
|
||||
PackerDebug bool `mapstructure:"packer_debug"`
|
||||
PackerBuildName string `mapstructure:"packer_build_name"`
|
||||
PackerDebug bool `mapstructure:"packer_debug"`
|
||||
|
||||
RawBootWait string `mapstructure:"boot_wait"`
|
||||
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
|
||||
@@ -86,7 +88,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
if b.config.OutputDir == "" {
|
||||
b.config.OutputDir = "virtualbox"
|
||||
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
|
||||
}
|
||||
|
||||
if b.config.RawBootWait == "" {
|
||||
@@ -114,7 +116,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
if b.config.VMName == "" {
|
||||
b.config.VMName = "packer"
|
||||
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
|
||||
}
|
||||
|
||||
errs := make([]error, 0)
|
||||
|
||||
@@ -13,6 +13,8 @@ func testConfig() map[string]interface{} {
|
||||
"iso_md5": "foo",
|
||||
"iso_url": "http://www.google.com/",
|
||||
"ssh_username": "foo",
|
||||
|
||||
packer.BuildNameConfigKey: "foo",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +38,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||
t.Errorf("bad guest OS type: %s", b.config.GuestOSType)
|
||||
}
|
||||
|
||||
if b.config.OutputDir != "virtualbox" {
|
||||
if b.config.OutputDir != "output-foo" {
|
||||
t.Errorf("bad output dir: %s", b.config.OutputDir)
|
||||
}
|
||||
|
||||
@@ -52,7 +54,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||
t.Errorf("bad ssh port: %d", b.config.SSHPort)
|
||||
}
|
||||
|
||||
if b.config.VMName != "packer" {
|
||||
if b.config.VMName != "packer-foo" {
|
||||
t.Errorf("bad vm name: %s", b.config.VMName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,11 +124,12 @@ func (d *VBox42Driver) Version() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Printf("VBoxManage --version output: %s", stdout.String())
|
||||
versionOutput := strings.TrimSpace(stdout.String())
|
||||
log.Printf("VBoxManage --version output: %s", versionOutput)
|
||||
versionRe := regexp.MustCompile("[^.0-9]")
|
||||
matches := versionRe.Split(stdout.String(), 2)
|
||||
matches := versionRe.Split(versionOutput, 2)
|
||||
if len(matches) == 0 {
|
||||
return "", fmt.Errorf("No version found: %s", stdout.String())
|
||||
return "", fmt.Errorf("No version found: %s", versionOutput)
|
||||
}
|
||||
|
||||
log.Printf("VirtualBox version: %s", matches[0])
|
||||
|
||||
@@ -23,7 +23,14 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
|
||||
vmName := state["vmName"].(string)
|
||||
|
||||
ui.Say("Starting the virtual machine...")
|
||||
command := []string{"startvm", vmName, "--type", "gui"}
|
||||
guiArgument := "gui"
|
||||
if config.Headless == true {
|
||||
ui.Message("WARNING: The VM will be started in headless mode, as configured.\n" +
|
||||
"In headless mode, errors during the boot sequence or OS setup\n" +
|
||||
"won't be easily visible. Use at your own discresion.")
|
||||
guiArgument = "headless"
|
||||
}
|
||||
command := []string{"startvm", vmName, "--type", guiArgument}
|
||||
if err := driver.VBoxManage(command...); err != nil {
|
||||
err := fmt.Errorf("Error starting VM: %s", err)
|
||||
state["error"] = err
|
||||
|
||||
@@ -59,6 +59,16 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
|
||||
continue
|
||||
}
|
||||
|
||||
if code == "wait5" {
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
if code == "wait10" {
|
||||
time.Sleep(10 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
// Since typing is sometimes so slow, we check for an interrupt
|
||||
// in between each character.
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
@@ -116,11 +126,23 @@ func scancodes(message string) []string {
|
||||
var scancode []string
|
||||
|
||||
if strings.HasPrefix(message, "<wait>") {
|
||||
log.Printf("Special code <wait> found, will sleep at this point.")
|
||||
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
|
||||
scancode = []string{"wait"}
|
||||
message = message[len("<wait>"):]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<wait5>") {
|
||||
log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.")
|
||||
scancode = []string{"wait5"}
|
||||
message = message[len("<wait5>"):]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<wait10>") {
|
||||
log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.")
|
||||
scancode = []string{"wait10"}
|
||||
message = message[len("<wait10>"):]
|
||||
}
|
||||
|
||||
if scancode == nil {
|
||||
for specialCode, specialValue := range special {
|
||||
if strings.HasPrefix(message, specialCode) {
|
||||
|
||||
@@ -112,6 +112,8 @@ func (s *stepWaitForSSH) waitForSSH(state map[string]interface{}) (packer.Commun
|
||||
User: config.SSHUser,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+41
-24
@@ -13,6 +13,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -25,29 +26,34 @@ type Builder struct {
|
||||
}
|
||||
|
||||
type config struct {
|
||||
DiskName string `mapstructure:"vmdk_name"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
ISOMD5 string `mapstructure:"iso_md5"`
|
||||
ISOUrl string `mapstructure:"iso_url"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
HTTPDir string `mapstructure:"http_directory"`
|
||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
BootWait time.Duration ``
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
ShutdownTimeout time.Duration ``
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
SSHWaitTimeout time.Duration ``
|
||||
VMXData map[string]string `mapstructure:"vmx_data"`
|
||||
VNCPortMin uint `mapstructure:"vnc_port_min"`
|
||||
VNCPortMax uint `mapstructure:"vnc_port_max"`
|
||||
DiskName string `mapstructure:"vmdk_name"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
ISOMD5 string `mapstructure:"iso_md5"`
|
||||
ISOUrl string `mapstructure:"iso_url"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
Headless bool `mapstructure:"headless"`
|
||||
HTTPDir string `mapstructure:"http_directory"`
|
||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
BootWait time.Duration ``
|
||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
ShutdownTimeout time.Duration ``
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
SSHWaitTimeout time.Duration ``
|
||||
ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"`
|
||||
ToolsUploadPath string `mapstructure:"tools_upload_path"`
|
||||
VMXData map[string]string `mapstructure:"vmx_data"`
|
||||
VNCPortMin uint `mapstructure:"vnc_port_min"`
|
||||
VNCPortMax uint `mapstructure:"vnc_port_max"`
|
||||
|
||||
PackerDebug bool `mapstructure:"packer_debug"`
|
||||
PackerBuildName string `mapstructure:"packer_build_name"`
|
||||
PackerDebug bool `mapstructure:"packer_debug"`
|
||||
|
||||
RawBootWait string `mapstructure:"boot_wait"`
|
||||
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
|
||||
@@ -75,7 +81,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
if b.config.VMName == "" {
|
||||
b.config.VMName = "packer"
|
||||
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
|
||||
}
|
||||
|
||||
if b.config.HTTPPortMin == 0 {
|
||||
@@ -99,13 +105,17 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
if b.config.OutputDir == "" {
|
||||
b.config.OutputDir = "vmware"
|
||||
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
|
||||
}
|
||||
|
||||
if b.config.SSHPort == 0 {
|
||||
b.config.SSHPort = 22
|
||||
}
|
||||
|
||||
if b.config.ToolsUploadPath == "" {
|
||||
b.config.ToolsUploadPath = "{{ .Flavor }}.iso"
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
var err error
|
||||
errs := make([]error, 0)
|
||||
@@ -192,6 +202,10 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
|
||||
}
|
||||
|
||||
if _, err := template.New("path").Parse(b.config.ToolsUploadPath); err != nil {
|
||||
errs = append(errs, fmt.Errorf("tools_upload_path invalid: %s", err))
|
||||
}
|
||||
|
||||
if b.config.VNCPortMin > b.config.VNCPortMax {
|
||||
errs = append(errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
|
||||
}
|
||||
@@ -213,6 +227,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
steps := []multistep.Step{
|
||||
&stepPrepareTools{},
|
||||
&stepDownloadISO{},
|
||||
&stepPrepareOutputDir{},
|
||||
&stepCreateDisk{},
|
||||
@@ -222,9 +237,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&stepRun{},
|
||||
&stepTypeBootCommand{},
|
||||
&stepWaitForSSH{},
|
||||
&stepUploadTools{},
|
||||
&stepProvision{},
|
||||
&stepShutdown{},
|
||||
&stepCleanFiles{},
|
||||
&stepCompactDisk{},
|
||||
}
|
||||
|
||||
// Setup the state bag
|
||||
|
||||
@@ -13,6 +13,8 @@ func testConfig() map[string]interface{} {
|
||||
"iso_md5": "foo",
|
||||
"iso_url": "http://www.packer.io",
|
||||
"ssh_username": "foo",
|
||||
|
||||
packer.BuildNameConfigKey: "foo",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +68,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||
t.Errorf("bad disk name: %s", b.config.DiskName)
|
||||
}
|
||||
|
||||
if b.config.OutputDir != "vmware" {
|
||||
if b.config.OutputDir != "output-foo" {
|
||||
t.Errorf("bad output dir: %s", b.config.OutputDir)
|
||||
}
|
||||
|
||||
@@ -74,7 +76,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||
t.Errorf("bad wait timeout: %s", b.config.SSHWaitTimeout)
|
||||
}
|
||||
|
||||
if b.config.VMName != "packer" {
|
||||
if b.config.VMName != "packer-foo" {
|
||||
t.Errorf("bad vm name: %s", b.config.VMName)
|
||||
}
|
||||
}
|
||||
@@ -309,6 +311,36 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ToolsUploadPath(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test a default
|
||||
delete(config, "tools_upload_path")
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ToolsUploadPath == "" {
|
||||
t.Fatalf("bad value: %s", b.config.ToolsUploadPath)
|
||||
}
|
||||
|
||||
// Test with a bad value
|
||||
config["tools_upload_path"] = "{{{nope}"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
config["tools_upload_path"] = "hey"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_VNCPort(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
@@ -12,6 +12,9 @@ import (
|
||||
|
||||
// A driver is able to talk to VMware, control virtual machines, etc.
|
||||
type Driver interface {
|
||||
// CompactDisk compacts a virtual disk.
|
||||
CompactDisk(string) error
|
||||
|
||||
// CreateDisk creates a virtual disk with the given size.
|
||||
CreateDisk(string, string) error
|
||||
|
||||
@@ -19,11 +22,14 @@ type Driver interface {
|
||||
IsRunning(string) (bool, error)
|
||||
|
||||
// Start starts a VM specified by the path to the VMX given.
|
||||
Start(string) error
|
||||
Start(string, bool) error
|
||||
|
||||
// Stop stops a VM specified by the path to the VMX given.
|
||||
Stop(string) error
|
||||
|
||||
// Get the path to the VMware ISO for the given flavor.
|
||||
ToolsIsoPath(string) string
|
||||
|
||||
// Verify checks to make sure that this driver should function
|
||||
// properly. This should check that all the files it will use
|
||||
// appear to exist and so on. If everything is okay, this doesn't
|
||||
@@ -37,6 +43,20 @@ type Fusion5Driver struct {
|
||||
AppPath string
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) CompactDisk(diskPath string) error {
|
||||
defragCmd := exec.Command(d.vdiskManagerPath(), "-d", diskPath)
|
||||
if _, _, err := d.runAndLog(defragCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shrinkCmd := exec.Command(d.vdiskManagerPath(), "-k", diskPath)
|
||||
if _, _, err := d.runAndLog(shrinkCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) CreateDisk(output string, size string) error {
|
||||
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
@@ -67,8 +87,13 @@ func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) Start(vmxPath string) error {
|
||||
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "start", vmxPath, "gui")
|
||||
func (d *Fusion5Driver) Start(vmxPath string, headless bool) error {
|
||||
guiArgument := "gui"
|
||||
if headless == true {
|
||||
guiArgument = "nogui"
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "start", vmxPath, guiArgument)
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -121,6 +146,10 @@ func (d *Fusion5Driver) vmrunPath() string {
|
||||
return filepath.Join(d.AppPath, "Contents", "Library", "vmrun")
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) ToolsIsoPath(k string) string {
|
||||
return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", k+".iso")
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) runAndLog(cmd *exec.Cmd) (string, string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
// This step compacts the virtual disk for the VM unless the "skip_compaction"
|
||||
// boolean is true.
|
||||
//
|
||||
// Uses:
|
||||
// config *config
|
||||
// driver Driver
|
||||
// full_disk_path string
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// <nothing>
|
||||
type stepCompactDisk struct{}
|
||||
|
||||
func (stepCompactDisk) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*config)
|
||||
driver := state["driver"].(Driver)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
full_disk_path := state["full_disk_path"].(string)
|
||||
|
||||
if config.SkipCompaction == true {
|
||||
log.Println("Skipping disk compaction step...")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say("Compacting the disk image")
|
||||
if err := driver.CompactDisk(full_disk_path); err != nil {
|
||||
state["error"] = fmt.Errorf("Error compacting disk: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (stepCompactDisk) Cleanup(map[string]interface{}) {}
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// <nothing>
|
||||
// full_disk_path (string) - The full path to the created disk.
|
||||
type stepCreateDisk struct{}
|
||||
|
||||
func (stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction {
|
||||
@@ -24,14 +24,16 @@ func (stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say("Creating virtual machine disk")
|
||||
output := filepath.Join(config.OutputDir, config.DiskName+".vmdk")
|
||||
if err := driver.CreateDisk(output, fmt.Sprintf("%dM", config.DiskSize)); err != nil {
|
||||
full_disk_path := filepath.Join(config.OutputDir, config.DiskName+".vmdk")
|
||||
if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize)); err != nil {
|
||||
err := fmt.Errorf("Error creating disk: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["full_disk_path"] = full_disk_path
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"os"
|
||||
)
|
||||
|
||||
type stepPrepareTools struct{}
|
||||
|
||||
func (*stepPrepareTools) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*config)
|
||||
driver := state["driver"].(Driver)
|
||||
|
||||
if config.ToolsUploadFlavor == "" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
path := driver.ToolsIsoPath(config.ToolsUploadFlavor)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
state["error"] = fmt.Errorf(
|
||||
"Couldn't find VMware tools for '%s'! VMware often downloads these\n"+
|
||||
"tools on-demand. However, to do this, you need to create a fake VM\n"+
|
||||
"of the proper type then click the 'install tools' option in the\n"+
|
||||
"VMware GUI.", config.ToolsUploadFlavor)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["tools_upload_source"] = path
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*stepPrepareTools) Cleanup(map[string]interface{}) {}
|
||||
@@ -27,13 +27,21 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
|
||||
driver := state["driver"].(Driver)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
vmxPath := state["vmx_path"].(string)
|
||||
vncPort := state["vnc_port"].(uint)
|
||||
|
||||
// Set the VMX path so that we know we started the machine
|
||||
s.bootTime = time.Now()
|
||||
s.vmxPath = vmxPath
|
||||
|
||||
ui.Say("Starting virtual machine...")
|
||||
if err := driver.Start(vmxPath); err != nil {
|
||||
if config.Headless {
|
||||
ui.Message(fmt.Sprintf(
|
||||
"The VM will be run headless, without a GUI. If you want to\n"+
|
||||
"view the screen of the VM, connect via VNC without a password to\n"+
|
||||
"127.0.0.1:%d", vncPort))
|
||||
}
|
||||
|
||||
if err := driver.Start(vmxPath, config.Headless); err != nil {
|
||||
err := fmt.Errorf("Error starting VM: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -72,6 +74,34 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
|
||||
}
|
||||
}
|
||||
|
||||
ui.Message("Waiting for VMware to clean up after itself...")
|
||||
lockPattern := filepath.Join(config.OutputDir, "*.lck")
|
||||
timer := time.After(15 * time.Second)
|
||||
LockWaitLoop:
|
||||
for {
|
||||
locks, err := filepath.Glob(lockPattern)
|
||||
if err == nil {
|
||||
if len(locks) == 0 {
|
||||
log.Println("No more lock files found. VMware is clean.")
|
||||
break
|
||||
}
|
||||
|
||||
if len(locks) == 1 && strings.HasSuffix(locks[0], ".vmx.lck") {
|
||||
log.Println("Only waiting on VMX lock. VMware is clean.")
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Waiting on lock files: %#v", locks)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-timer:
|
||||
log.Println("Reached timeout on waiting for clean VMware. Assuming clean.")
|
||||
break LockWaitLoop
|
||||
case <-time.After(1 * time.Second):
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("VM shut down.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
@@ -114,6 +114,20 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<wait5>") {
|
||||
log.Printf("Special code '<wait5>' found, sleeping 5 seconds")
|
||||
time.Sleep(5 * time.Second)
|
||||
original = original[len("<wait5>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<wait10>") {
|
||||
log.Printf("Special code '<wait10>' found, sleeping 10 seconds")
|
||||
time.Sleep(10 * time.Second)
|
||||
original = original[len("<wait10>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
for specialCode, specialValue := range special {
|
||||
if strings.HasPrefix(original, specialCode) {
|
||||
log.Printf("Special code '%s' found, replacing with: %d", specialCode, specialValue)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type toolsUploadPathTemplate struct {
|
||||
Flavor string
|
||||
}
|
||||
|
||||
type stepUploadTools struct{}
|
||||
|
||||
func (*stepUploadTools) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*config)
|
||||
if config.ToolsUploadFlavor == "" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
comm := state["communicator"].(packer.Communicator)
|
||||
tools_source := state["tools_upload_source"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say(fmt.Sprintf("Uploading the '%s' VMware Tools", config.ToolsUploadFlavor))
|
||||
f, err := os.Open(tools_source)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error opening VMware Tools ISO: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tplData := &toolsUploadPathTemplate{Flavor: config.ToolsUploadFlavor}
|
||||
var processedPath bytes.Buffer
|
||||
t := template.Must(template.New("path").Parse(config.ToolsUploadPath))
|
||||
t.Execute(&processedPath, tplData)
|
||||
|
||||
if err := comm.Upload(processedPath.String(), f); err != nil {
|
||||
state["error"] = fmt.Errorf("Error uploading VMware Tools: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*stepUploadTools) Cleanup(map[string]interface{}) {}
|
||||
@@ -157,6 +157,8 @@ func (s *stepWaitForSSH) waitForSSH(state map[string]interface{}) (packer.Commun
|
||||
User: config.SSHUser,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package build
|
||||
|
||||
const helpText = `
|
||||
Usage: packer build TEMPLATE
|
||||
Usage: packer build [options] TEMPLATE
|
||||
|
||||
Will execute multiple builds in parallel as defined in the template.
|
||||
The various artifacts created by the template will be outputted.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package validate
|
||||
|
||||
const helpString = `
|
||||
Usage: packer validate TEMPLATE
|
||||
Usage: packer validate [options] TEMPLATE
|
||||
|
||||
Checks the template is valid by parsing the template and also
|
||||
checking the configuration with the various builders, provisioners, etc.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package ssh
|
||||
|
||||
import "log"
|
||||
|
||||
// An implementation of ssh.ClientPassword so that you can use a static
|
||||
// string password for the password to ClientAuthPassword.
|
||||
type Password string
|
||||
@@ -7,3 +9,24 @@ type Password string
|
||||
func (p Password) Password(user string) (string, error) {
|
||||
return string(p), nil
|
||||
}
|
||||
|
||||
// An implementation of ssh.ClientKeyboardInteractive that simply sends
|
||||
// back the password for all questions. The questions are logged.
|
||||
type PasswordKeyboardInteractive string
|
||||
|
||||
func (p PasswordKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||
log.Printf("Keyboard interactive challenge: ")
|
||||
log.Printf("-- User: %s", user)
|
||||
log.Printf("-- Instructions: %s", instruction)
|
||||
for i, question := range questions {
|
||||
log.Printf("-- Question %d: %s", i+1, question)
|
||||
}
|
||||
|
||||
// Just send the password back for all questions
|
||||
answers := make([]string, len(questions))
|
||||
for i, _ := range answers {
|
||||
answers[i] = string(p)
|
||||
}
|
||||
|
||||
return answers, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package ssh
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.crypto/ssh"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -24,3 +25,24 @@ func TestPasswordPassword(t *testing.T) {
|
||||
t.Fatalf("invalid password: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordKeyboardInteractive_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = PasswordKeyboardInteractive("foo")
|
||||
if _, ok := raw.(ssh.ClientKeyboardInteractive); !ok {
|
||||
t.Fatal("PasswordKeyboardInteractive must implement ClientKeyboardInteractive")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordKeybardInteractive_Challenge(t *testing.T) {
|
||||
p := PasswordKeyboardInteractive("foo")
|
||||
result, err := p.Challenge("foo", "bar", []string{"one", "two"}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err not nil: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result, []string{"foo", "foo"}) {
|
||||
t.Fatalf("invalid password: %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
}
|
||||
|
||||
log.Printf("Packer Version: %s %s", packer.Version, packer.VersionPrerelease)
|
||||
log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
config, err := loadConfig()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err)
|
||||
|
||||
+15
-8
@@ -6,6 +6,10 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// This is the key in configurations that is set to the name of the
|
||||
// build.
|
||||
const BuildNameConfigKey = "packer_build_name"
|
||||
|
||||
// This is the key in configurations that is set to "true" when Packer
|
||||
// debugging is enabled.
|
||||
const DebugConfigKey = "packer_debug"
|
||||
@@ -91,12 +95,13 @@ func (b *coreBuild) Prepare() (err error) {
|
||||
|
||||
b.prepareCalled = true
|
||||
|
||||
debugConfig := map[string]interface{}{
|
||||
DebugConfigKey: b.debug,
|
||||
packerConfig := map[string]interface{}{
|
||||
BuildNameConfigKey: b.name,
|
||||
DebugConfigKey: b.debug,
|
||||
}
|
||||
|
||||
// Prepare the builder
|
||||
err = b.builder.Prepare(b.builderConfig, debugConfig)
|
||||
err = b.builder.Prepare(b.builderConfig, packerConfig)
|
||||
if err != nil {
|
||||
log.Printf("Build '%s' prepare failure: %s\n", b.name, err)
|
||||
return
|
||||
@@ -106,7 +111,7 @@ func (b *coreBuild) Prepare() (err error) {
|
||||
for _, coreProv := range b.provisioners {
|
||||
configs := make([]interface{}, len(coreProv.config), len(coreProv.config)+1)
|
||||
copy(configs, coreProv.config)
|
||||
configs = append(configs, debugConfig)
|
||||
configs = append(configs, packerConfig)
|
||||
|
||||
if err = coreProv.provisioner.Prepare(configs...); err != nil {
|
||||
return
|
||||
@@ -116,7 +121,8 @@ func (b *coreBuild) Prepare() (err error) {
|
||||
// Prepare the post-processors
|
||||
for _, ppSeq := range b.postProcessors {
|
||||
for _, corePP := range ppSeq {
|
||||
if err = corePP.processor.Configure(corePP.config); err != nil {
|
||||
err = corePP.processor.Configure(corePP.config, packerConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -189,7 +195,7 @@ PostProcessorRunSeqLoop:
|
||||
}
|
||||
|
||||
builderUi.Say(fmt.Sprintf("Running post-processor: %s", corePP.processorType))
|
||||
artifact, err := corePP.processor.PostProcess(ppUi, priorArtifact)
|
||||
artifact, keep, err := corePP.processor.PostProcess(ppUi, priorArtifact)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Errorf("Post-processor failed: %s", err))
|
||||
continue PostProcessorRunSeqLoop
|
||||
@@ -200,11 +206,12 @@ PostProcessorRunSeqLoop:
|
||||
continue PostProcessorRunSeqLoop
|
||||
}
|
||||
|
||||
keep = keep || corePP.keepInputArtifact
|
||||
if i == 0 {
|
||||
// This is the first post-processor. We handle deleting
|
||||
// previous artifacts a bit different because multiple
|
||||
// post-processors may be using the original and need it.
|
||||
if !keepOriginalArtifact && corePP.keepInputArtifact {
|
||||
if !keepOriginalArtifact && keep {
|
||||
log.Printf(
|
||||
"Flagging to keep original artifact from post-processor '%s'",
|
||||
corePP.processorType)
|
||||
@@ -213,7 +220,7 @@ PostProcessorRunSeqLoop:
|
||||
} else {
|
||||
// We have a prior artifact. If we want to keep it, we append
|
||||
// it to the results list. Otherwise, we destroy it.
|
||||
if corePP.keepInputArtifact {
|
||||
if keep {
|
||||
artifacts = append(artifacts, priorArtifact)
|
||||
} else {
|
||||
log.Printf("Deleting prior artifact from post-processor '%s'", corePP.processorType)
|
||||
|
||||
+40
-7
@@ -39,24 +39,27 @@ func TestBuild_Name(t *testing.T) {
|
||||
func TestBuild_Prepare(t *testing.T) {
|
||||
assert := asserts.NewTestingAsserts(t, true)
|
||||
|
||||
debugFalseConfig := map[string]interface{}{DebugConfigKey: false}
|
||||
packerConfig := map[string]interface{}{
|
||||
BuildNameConfigKey: "test",
|
||||
DebugConfigKey: false,
|
||||
}
|
||||
|
||||
build := testBuild()
|
||||
builder := build.builder.(*TestBuilder)
|
||||
|
||||
build.Prepare()
|
||||
assert.True(builder.prepareCalled, "prepare should be called")
|
||||
assert.Equal(builder.prepareConfig, []interface{}{42, debugFalseConfig}, "prepare config should be 42")
|
||||
assert.Equal(builder.prepareConfig, []interface{}{42, packerConfig}, "prepare config should be 42")
|
||||
|
||||
coreProv := build.provisioners[0]
|
||||
prov := coreProv.provisioner.(*TestProvisioner)
|
||||
assert.True(prov.prepCalled, "prepare should be called")
|
||||
assert.Equal(prov.prepConfigs, []interface{}{42, debugFalseConfig}, "prepare should be called with proper config")
|
||||
assert.Equal(prov.prepConfigs, []interface{}{42, packerConfig}, "prepare should be called with proper config")
|
||||
|
||||
corePP := build.postProcessors[0][0]
|
||||
pp := corePP.processor.(*TestPostProcessor)
|
||||
assert.True(pp.configCalled, "config should be called")
|
||||
assert.Equal(pp.configVal, 42, "config should have right value")
|
||||
assert.Equal(pp.configVal, []interface{}{42, packerConfig}, "config should have right value")
|
||||
}
|
||||
|
||||
func TestBuild_Prepare_Twice(t *testing.T) {
|
||||
@@ -82,7 +85,10 @@ func TestBuild_Prepare_Twice(t *testing.T) {
|
||||
func TestBuild_Prepare_Debug(t *testing.T) {
|
||||
assert := asserts.NewTestingAsserts(t, true)
|
||||
|
||||
debugConfig := map[string]interface{}{DebugConfigKey: true}
|
||||
packerConfig := map[string]interface{}{
|
||||
BuildNameConfigKey: "test",
|
||||
DebugConfigKey: true,
|
||||
}
|
||||
|
||||
build := testBuild()
|
||||
builder := build.builder.(*TestBuilder)
|
||||
@@ -90,12 +96,12 @@ func TestBuild_Prepare_Debug(t *testing.T) {
|
||||
build.SetDebug(true)
|
||||
build.Prepare()
|
||||
assert.True(builder.prepareCalled, "prepare should be called")
|
||||
assert.Equal(builder.prepareConfig, []interface{}{42, debugConfig}, "prepare config should be 42")
|
||||
assert.Equal(builder.prepareConfig, []interface{}{42, packerConfig}, "prepare config should be 42")
|
||||
|
||||
coreProv := build.provisioners[0]
|
||||
prov := coreProv.provisioner.(*TestProvisioner)
|
||||
assert.True(prov.prepCalled, "prepare should be called")
|
||||
assert.Equal(prov.prepConfigs, []interface{}{42, debugConfig}, "prepare should be called with proper config")
|
||||
assert.Equal(prov.prepConfigs, []interface{}{42, packerConfig}, "prepare should be called with proper config")
|
||||
}
|
||||
|
||||
func TestBuild_Run(t *testing.T) {
|
||||
@@ -239,6 +245,33 @@ func TestBuild_Run_Artifacts(t *testing.T) {
|
||||
if !reflect.DeepEqual(artifactIds, expectedIds) {
|
||||
t.Fatalf("unexpected ids: %#v", artifactIds)
|
||||
}
|
||||
|
||||
// Test case: Test that with a single post-processor that forcibly
|
||||
// keeps inputs, that the artifacts are kept.
|
||||
build = testBuild()
|
||||
build.postProcessors = [][]coreBuildPostProcessor{
|
||||
[]coreBuildPostProcessor{
|
||||
coreBuildPostProcessor{
|
||||
&TestPostProcessor{artifactId: "pp", keep: true}, "pp", 42, false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
build.Prepare()
|
||||
artifacts, err = build.Run(ui, cache)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expectedIds = []string{"b", "pp"}
|
||||
artifactIds = make([]string, len(artifacts))
|
||||
for i, artifact := range artifacts {
|
||||
artifactIds[i] = artifact.Id()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(artifactIds, expectedIds) {
|
||||
t.Fatalf("unexpected ids: %#v", artifactIds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuild_RunBeforePrepare(t *testing.T) {
|
||||
|
||||
@@ -252,7 +252,7 @@ func (c *Client) Start() (address string, err error) {
|
||||
timeout := time.After(c.config.StartTimeout)
|
||||
|
||||
// Start looking for the address
|
||||
log.Println("Waiting for RPC address for: %s", cmd.Path)
|
||||
log.Printf("Waiting for RPC address for: %s", cmd.Path)
|
||||
for done := false; !done; {
|
||||
select {
|
||||
case <-timeout:
|
||||
|
||||
@@ -10,16 +10,16 @@ type cmdPostProcessor struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
func (c *cmdPostProcessor) Configure(config interface{}) error {
|
||||
func (c *cmdPostProcessor) Configure(config ...interface{}) error {
|
||||
defer func() {
|
||||
r := recover()
|
||||
c.checkExit(r, nil)
|
||||
}()
|
||||
|
||||
return c.p.Configure(config)
|
||||
return c.p.Configure(config...)
|
||||
}
|
||||
|
||||
func (c *cmdPostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, error) {
|
||||
func (c *cmdPostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
c.checkExit(r, nil)
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
|
||||
type helperPostProcessor byte
|
||||
|
||||
func (helperPostProcessor) Configure(interface{}) error {
|
||||
func (helperPostProcessor) Configure(...interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (helperPostProcessor) PostProcess(packer.Ui, packer.Artifact) (packer.Artifact, error) {
|
||||
return nil, nil
|
||||
func (helperPostProcessor) PostProcess(packer.Ui, packer.Artifact) (packer.Artifact, bool, error) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func TestPostProcessor_NoExist(t *testing.T) {
|
||||
|
||||
@@ -9,9 +9,10 @@ type PostProcessor interface {
|
||||
// Configure is responsible for setting up configuration, storing
|
||||
// the state for later, and returning and errors, such as validation
|
||||
// errors.
|
||||
Configure(interface{}) error
|
||||
Configure(...interface{}) error
|
||||
|
||||
// PostProcess takes a previously created Artifact and produces another
|
||||
// Artifact. If an error occurs, it should return that error.
|
||||
PostProcess(Ui, Artifact) (Artifact, error)
|
||||
// Artifact. If an error occurs, it should return that error. If `keep`
|
||||
// is to true, then the previous artifact is forcibly kept.
|
||||
PostProcess(Ui, Artifact) (a Artifact, keep bool, err error)
|
||||
}
|
||||
|
||||
@@ -2,22 +2,23 @@ package packer
|
||||
|
||||
type TestPostProcessor struct {
|
||||
artifactId string
|
||||
keep bool
|
||||
configCalled bool
|
||||
configVal interface{}
|
||||
configVal []interface{}
|
||||
ppCalled bool
|
||||
ppArtifact Artifact
|
||||
ppUi Ui
|
||||
}
|
||||
|
||||
func (pp *TestPostProcessor) Configure(v interface{}) error {
|
||||
func (pp *TestPostProcessor) Configure(v ...interface{}) error {
|
||||
pp.configCalled = true
|
||||
pp.configVal = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pp *TestPostProcessor) PostProcess(ui Ui, a Artifact) (Artifact, error) {
|
||||
func (pp *TestPostProcessor) PostProcess(ui Ui, a Artifact) (Artifact, bool, error) {
|
||||
pp.ppCalled = true
|
||||
pp.ppArtifact = a
|
||||
pp.ppUi = ui
|
||||
return &TestArtifact{id: pp.artifactId}, nil
|
||||
return &TestArtifact{id: pp.artifactId}, pp.keep, nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ func PortRange(min, max int) {
|
||||
// bound to that port.
|
||||
func netListenerInRange(min, max int) net.Listener {
|
||||
for port := min; port <= max; port++ {
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
if err == nil {
|
||||
return l
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func Test_netListenerInRange(t *testing.T) {
|
||||
assert := asserts.NewTestingAsserts(t, true)
|
||||
|
||||
// Open up port 10000 so that we take up a port
|
||||
L1000, err := net.Listen("tcp", ":11000")
|
||||
L1000, err := net.Listen("tcp", "127.0.0.1:11000")
|
||||
defer L1000.Close()
|
||||
assert.Nil(err, "should be able to bind to port 10000")
|
||||
|
||||
|
||||
@@ -17,50 +17,56 @@ type PostProcessorServer struct {
|
||||
p packer.PostProcessor
|
||||
}
|
||||
|
||||
type PostProcessorConfigureArgs struct {
|
||||
Configs []interface{}
|
||||
}
|
||||
|
||||
type PostProcessorProcessResponse struct {
|
||||
Err error
|
||||
Keep bool
|
||||
RPCAddress string
|
||||
}
|
||||
|
||||
func PostProcessor(client *rpc.Client) *postProcessor {
|
||||
return &postProcessor{client}
|
||||
}
|
||||
func (p *postProcessor) Configure(raw interface{}) (err error) {
|
||||
if cerr := p.client.Call("PostProcessor.Configure", &raw, &err); cerr != nil {
|
||||
func (p *postProcessor) Configure(raw ...interface{}) (err error) {
|
||||
args := &PostProcessorConfigureArgs{Configs: raw}
|
||||
if cerr := p.client.Call("PostProcessor.Configure", args, &err); cerr != nil {
|
||||
err = cerr
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *postProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, error) {
|
||||
func (p *postProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) {
|
||||
server := rpc.NewServer()
|
||||
RegisterArtifact(server, a)
|
||||
RegisterUi(server, ui)
|
||||
|
||||
var response PostProcessorProcessResponse
|
||||
if err := p.client.Call("PostProcessor.PostProcess", serveSingleConn(server), &response); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if response.Err != nil {
|
||||
return nil, response.Err
|
||||
return nil, false, response.Err
|
||||
}
|
||||
|
||||
if response.RPCAddress == "" {
|
||||
return nil, nil
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
client, err := rpc.Dial("tcp", response.RPCAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return Artifact(client), nil
|
||||
return Artifact(client), response.Keep, nil
|
||||
}
|
||||
|
||||
func (p *PostProcessorServer) Configure(raw *interface{}, reply *error) error {
|
||||
*reply = p.p.Configure(*raw)
|
||||
func (p *PostProcessorServer) Configure(args *PostProcessorConfigureArgs, reply *error) error {
|
||||
*reply = p.p.Configure(args.Configs...)
|
||||
if *reply != nil {
|
||||
*reply = NewBasicError(*reply)
|
||||
}
|
||||
@@ -76,7 +82,7 @@ func (p *PostProcessorServer) PostProcess(address string, reply *PostProcessorPr
|
||||
|
||||
responseAddress := ""
|
||||
|
||||
artifact, err := p.p.PostProcess(&Ui{client}, Artifact(client))
|
||||
artifact, keep, err := p.p.PostProcess(&Ui{client}, Artifact(client))
|
||||
if err == nil && artifact != nil {
|
||||
server := rpc.NewServer()
|
||||
RegisterArtifact(server, artifact)
|
||||
@@ -89,6 +95,7 @@ func (p *PostProcessorServer) PostProcess(address string, reply *PostProcessorPr
|
||||
|
||||
*reply = PostProcessorProcessResponse{
|
||||
Err: err,
|
||||
Keep: keep,
|
||||
RPCAddress: responseAddress,
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package rpc
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"net/rpc"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -10,23 +11,23 @@ var testPostProcessorArtifact = new(testArtifact)
|
||||
|
||||
type TestPostProcessor struct {
|
||||
configCalled bool
|
||||
configVal interface{}
|
||||
configVal []interface{}
|
||||
ppCalled bool
|
||||
ppArtifact packer.Artifact
|
||||
ppUi packer.Ui
|
||||
}
|
||||
|
||||
func (pp *TestPostProcessor) Configure(v interface{}) error {
|
||||
func (pp *TestPostProcessor) Configure(v ...interface{}) error {
|
||||
pp.configCalled = true
|
||||
pp.configVal = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pp *TestPostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, error) {
|
||||
func (pp *TestPostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, error) {
|
||||
pp.ppCalled = true
|
||||
pp.ppArtifact = a
|
||||
pp.ppUi = ui
|
||||
return testPostProcessorArtifact, nil
|
||||
return testPostProcessorArtifact, false, nil
|
||||
}
|
||||
|
||||
func TestPostProcessorRPC(t *testing.T) {
|
||||
@@ -56,14 +57,14 @@ func TestPostProcessorRPC(t *testing.T) {
|
||||
t.Fatal("config should be called")
|
||||
}
|
||||
|
||||
if p.configVal != 42 {
|
||||
if !reflect.DeepEqual(p.configVal, []interface{}{42}) {
|
||||
t.Fatalf("unknown config value: %#v", p.configVal)
|
||||
}
|
||||
|
||||
// Test PostProcess
|
||||
a := new(testArtifact)
|
||||
ui := new(testUi)
|
||||
artifact, err := pClient.PostProcess(ui, a)
|
||||
artifact, _, err := pClient.PostProcess(ui, a)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package packer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@@ -65,6 +66,29 @@ func ParseTemplate(data []byte) (t *Template, err error) {
|
||||
var rawTpl rawTemplate
|
||||
err = json.Unmarshal(data, &rawTpl)
|
||||
if err != nil {
|
||||
syntaxErr, ok := err.(*json.SyntaxError)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// We have a syntax error. Extract out the line number and friends.
|
||||
// https://groups.google.com/forum/#!topic/golang-nuts/fizimmXtVfc
|
||||
newline := []byte{'\x0a'}
|
||||
|
||||
// Calculate the start/end position of the line where the error is
|
||||
start := bytes.LastIndex(data[:syntaxErr.Offset], newline) + 1
|
||||
end := len(data)
|
||||
if idx := bytes.Index(data[start:], newline); idx >= 0 {
|
||||
end = start + idx
|
||||
}
|
||||
|
||||
// Count the line number we're on plus the offset in the line
|
||||
line := bytes.Count(data[:start], newline) + 1
|
||||
pos := int(syntaxErr.Offset) - start - 1
|
||||
|
||||
err = fmt.Errorf("Error in line %d, char %d: %s\n%s",
|
||||
line, pos, syntaxErr, data[start:end])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+16
-4
@@ -1,12 +1,14 @@
|
||||
package packer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -86,19 +88,29 @@ func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
|
||||
}
|
||||
|
||||
func (u *PrefixedUi) Ask(query string) (string, error) {
|
||||
return u.Ui.Ask(fmt.Sprintf("%s: %s", u.SayPrefix, query))
|
||||
return u.Ui.Ask(u.prefixLines(u.SayPrefix, query))
|
||||
}
|
||||
|
||||
func (u *PrefixedUi) Say(message string) {
|
||||
u.Ui.Say(fmt.Sprintf("%s: %s", u.SayPrefix, message))
|
||||
u.Ui.Say(u.prefixLines(u.SayPrefix, message))
|
||||
}
|
||||
|
||||
func (u *PrefixedUi) Message(message string) {
|
||||
u.Ui.Message(fmt.Sprintf("%s: %s", u.MessagePrefix, message))
|
||||
u.Ui.Message(u.prefixLines(u.MessagePrefix, message))
|
||||
}
|
||||
|
||||
func (u *PrefixedUi) Error(message string) {
|
||||
u.Ui.Error(fmt.Sprintf("%s: %s", u.SayPrefix, message))
|
||||
u.Ui.Error(u.prefixLines(u.SayPrefix, message))
|
||||
}
|
||||
|
||||
func (u *PrefixedUi) prefixLines(prefix, message string) string {
|
||||
var result bytes.Buffer
|
||||
|
||||
for _, line := range strings.Split(message, "\n") {
|
||||
result.WriteString(fmt.Sprintf("%s: %s\n", prefix, line))
|
||||
}
|
||||
|
||||
return strings.TrimSpace(result.String())
|
||||
}
|
||||
|
||||
func (rw *ReaderWriterUi) Ask(query string) (string, error) {
|
||||
|
||||
@@ -50,6 +50,9 @@ func TestPrefixedUi(t *testing.T) {
|
||||
|
||||
prefixUi.Error("bar")
|
||||
assert.Equal(readWriter(bufferUi), "mitchell: bar\n", "should have prefix")
|
||||
|
||||
prefixUi.Say("foo\nbar")
|
||||
assert.Equal(readWriter(bufferUi), "mitchell: foo\nmitchell: bar\n", "should multiline")
|
||||
}
|
||||
|
||||
func TestColoredUi_ImplUi(t *testing.T) {
|
||||
|
||||
+2
-2
@@ -6,12 +6,12 @@ import (
|
||||
)
|
||||
|
||||
// The version of packer.
|
||||
const Version = "0.1.2"
|
||||
const Version = "0.1.4"
|
||||
|
||||
// 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
|
||||
// pre-release marker.
|
||||
const VersionPrerelease = "dev"
|
||||
const VersionPrerelease = ""
|
||||
|
||||
type versionCommand byte
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
type AWSBoxConfig struct {
|
||||
OutputPath string `mapstructure:"output"`
|
||||
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
|
||||
|
||||
PackerBuildName string `mapstructure:"packer_build_name"`
|
||||
}
|
||||
|
||||
type AWSVagrantfileTemplate struct {
|
||||
@@ -24,16 +26,18 @@ type AWSBoxPostProcessor struct {
|
||||
config AWSBoxConfig
|
||||
}
|
||||
|
||||
func (p *AWSBoxPostProcessor) Configure(raw interface{}) error {
|
||||
err := mapstructure.Decode(raw, &p.config)
|
||||
if err != nil {
|
||||
return err
|
||||
func (p *AWSBoxPostProcessor) Configure(raws ...interface{}) error {
|
||||
for _, raw := range raws {
|
||||
err := mapstructure.Decode(raw, &p.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, error) {
|
||||
func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
// Determine the regions...
|
||||
tplData := &AWSVagrantfileTemplate{
|
||||
Images: make(map[string]string),
|
||||
@@ -42,29 +46,30 @@ func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact
|
||||
for _, regions := range strings.Split(artifact.Id(), ",") {
|
||||
parts := strings.Split(regions, ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
|
||||
return nil, false, fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
|
||||
}
|
||||
|
||||
tplData.Images[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
// Compile the output path
|
||||
outputPath, err := ProcessOutputPath(p.config.OutputPath, "aws", artifact)
|
||||
outputPath, err := ProcessOutputPath(p.config.OutputPath,
|
||||
p.config.PackerBuildName, "aws", artifact)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Create a temporary directory for us to build the contents of the box in
|
||||
dir, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Create the Vagrantfile from the template
|
||||
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer vf.Close()
|
||||
|
||||
@@ -72,13 +77,13 @@ func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact
|
||||
if p.config.VagrantfileTemplate != "" {
|
||||
f, err := os.Open(p.config.VagrantfileTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
vagrantfileContents = string(contents)
|
||||
@@ -91,15 +96,15 @@ func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact
|
||||
// Create the metadata
|
||||
metadata := map[string]string{"provider": "aws"}
|
||||
if err := WriteMetadata(dir, metadata); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Compress the directory to the given output path
|
||||
if err := DirToBox(outputPath, dir); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return NewArtifact("aws", outputPath), nil
|
||||
return NewArtifact("aws", outputPath), true, nil
|
||||
}
|
||||
|
||||
var defaultAWSVagrantfile = `
|
||||
|
||||
@@ -19,6 +19,8 @@ var builtins = map[string]string{
|
||||
|
||||
type Config struct {
|
||||
OutputPath string `mapstructure:"output"`
|
||||
|
||||
PackerBuildName string `mapstructure:"packer_build_name"`
|
||||
}
|
||||
|
||||
type PostProcessor struct {
|
||||
@@ -26,26 +28,33 @@ type PostProcessor struct {
|
||||
premade map[string]packer.PostProcessor
|
||||
}
|
||||
|
||||
func (p *PostProcessor) Configure(raw interface{}) error {
|
||||
err := mapstructure.Decode(raw, &p.config)
|
||||
if err != nil {
|
||||
return err
|
||||
func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||
for _, raw := range raws {
|
||||
err := mapstructure.Decode(raw, &p.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.OutputPath == "" {
|
||||
p.config.OutputPath = "packer_{{.Provider}}.box"
|
||||
p.config.OutputPath = "packer_{{ .BuildName }}_{{.Provider}}.box"
|
||||
}
|
||||
|
||||
_, err = template.New("output").Parse(p.config.OutputPath)
|
||||
_, err := template.New("output").Parse(p.config.OutputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("output invalid template: %s", err)
|
||||
}
|
||||
|
||||
// TODO(mitchellh): Properly handle multiple raw configs
|
||||
var mapConfig map[string]interface{}
|
||||
if err := mapstructure.Decode(raw, &mapConfig); err != nil {
|
||||
if err := mapstructure.Decode(raws[0], &mapConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
packerConfig := map[string]interface{}{
|
||||
packer.BuildNameConfigKey: p.config.PackerBuildName,
|
||||
}
|
||||
|
||||
p.premade = make(map[string]packer.PostProcessor)
|
||||
errors := make([]error, 0)
|
||||
for k, raw := range mapConfig {
|
||||
@@ -54,7 +63,7 @@ func (p *PostProcessor) Configure(raw interface{}) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := pp.Configure(raw); err != nil {
|
||||
if err := pp.Configure(raw, packerConfig); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
@@ -68,10 +77,10 @@ func (p *PostProcessor) Configure(raw interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, error) {
|
||||
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
ppName, ok := builtins[artifact.BuilderId()]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Unknown artifact type, can't build box: %s", artifact.BuilderId())
|
||||
return nil, false, fmt.Errorf("Unknown artifact type, can't build box: %s", artifact.BuilderId())
|
||||
}
|
||||
|
||||
// Use the premade PostProcessor if we have one. Otherwise, we
|
||||
@@ -81,12 +90,12 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
|
||||
log.Printf("Premade post-processor for '%s' not found. Creating.", ppName)
|
||||
pp = keyToPostProcessor(ppName)
|
||||
if pp == nil {
|
||||
return nil, fmt.Errorf("Vagrant box post-processor not found: %s", ppName)
|
||||
return nil, false, fmt.Errorf("Vagrant box post-processor not found: %s", ppName)
|
||||
}
|
||||
|
||||
config := map[string]string{"output": p.config.OutputPath}
|
||||
if err := pp.Configure(config); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
// OutputPath variables.
|
||||
type OutputPathTemplate struct {
|
||||
ArtifactId string
|
||||
BuildName string
|
||||
Provider string
|
||||
}
|
||||
|
||||
@@ -39,6 +40,11 @@ func DirToBox(dst, dir string) error {
|
||||
|
||||
// This is the walk func that tars each of the files in the dir
|
||||
tarWalk := func(path string, info os.FileInfo, prevErr error) error {
|
||||
// If there was a prior error, return it
|
||||
if prevErr != nil {
|
||||
return prevErr
|
||||
}
|
||||
|
||||
// Skip directories
|
||||
if info.IsDir() {
|
||||
log.Printf("Skiping directory '%s' for box '%s'", path, dst)
|
||||
@@ -83,11 +89,12 @@ func DirToBox(dst, dir string) error {
|
||||
|
||||
// ProcessOutputPath takes an output path template and executes it,
|
||||
// replacing variables with their respective values.
|
||||
func ProcessOutputPath(path string, provider string, artifact packer.Artifact) (string, error) {
|
||||
func ProcessOutputPath(path string, buildName string, provider string, artifact packer.Artifact) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
tplData := &OutputPathTemplate{
|
||||
ArtifactId: artifact.Id(),
|
||||
BuildName: buildName,
|
||||
Provider: provider,
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
type VBoxBoxConfig struct {
|
||||
OutputPath string `mapstructure:"output"`
|
||||
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
|
||||
|
||||
PackerBuildName string `mapstructure:"packer_build_name"`
|
||||
}
|
||||
|
||||
type VBoxVagrantfileTemplate struct {
|
||||
@@ -28,33 +30,36 @@ type VBoxBoxPostProcessor struct {
|
||||
config VBoxBoxConfig
|
||||
}
|
||||
|
||||
func (p *VBoxBoxPostProcessor) Configure(raw interface{}) error {
|
||||
err := mapstructure.Decode(raw, &p.config)
|
||||
if err != nil {
|
||||
return err
|
||||
func (p *VBoxBoxPostProcessor) Configure(raws ...interface{}) error {
|
||||
for _, raw := range raws {
|
||||
err := mapstructure.Decode(raw, &p.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, error) {
|
||||
func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
var err error
|
||||
tplData := &VBoxVagrantfileTemplate{}
|
||||
tplData.BaseMacAddress, err = p.findBaseMacAddress(artifact)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Compile the output path
|
||||
outputPath, err := ProcessOutputPath(p.config.OutputPath, "virtualbox", artifact)
|
||||
outputPath, err := ProcessOutputPath(p.config.OutputPath,
|
||||
p.config.PackerBuildName, "virtualbox", artifact)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Create a temporary directory for us to build the contents of the box in
|
||||
dir, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
@@ -63,25 +68,25 @@ func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifac
|
||||
ui.Message(fmt.Sprintf("Copying: %s", path))
|
||||
src, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(filepath.Join(dir, filepath.Base(path)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
// Create the Vagrantfile from the template
|
||||
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer vf.Close()
|
||||
|
||||
@@ -89,13 +94,13 @@ func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifac
|
||||
if p.config.VagrantfileTemplate != "" {
|
||||
f, err := os.Open(p.config.VagrantfileTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
vagrantfileContents = string(contents)
|
||||
@@ -108,22 +113,22 @@ func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifac
|
||||
// Create the metadata
|
||||
metadata := map[string]string{"provider": "virtualbox"}
|
||||
if err := WriteMetadata(dir, metadata); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Rename the OVF file to box.ovf, as required by Vagrant
|
||||
ui.Message("Renaming the OVF to box.ovf...")
|
||||
if err := p.renameOVF(dir); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Compress the directory to the given output path
|
||||
ui.Message(fmt.Sprintf("Compressing box..."))
|
||||
if err := DirToBox(outputPath, dir); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return NewArtifact("virtualbox", outputPath), nil
|
||||
return NewArtifact("virtualbox", outputPath), false, nil
|
||||
}
|
||||
|
||||
func (p *VBoxBoxPostProcessor) findBaseMacAddress(a packer.Artifact) (string, error) {
|
||||
@@ -179,6 +184,6 @@ func (p *VBoxBoxPostProcessor) renameOVF(dir string) error {
|
||||
|
||||
var defaultVBoxVagrantfile = `
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.base_mac = "{{ .BaseMacAddress }}"
|
||||
config.vm.base_mac = "{{ .BaseMacAddress }}"
|
||||
end
|
||||
`
|
||||
|
||||
@@ -14,32 +14,37 @@ import (
|
||||
type VMwareBoxConfig struct {
|
||||
OutputPath string `mapstructure:"output"`
|
||||
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
|
||||
|
||||
PackerBuildName string `mapstructure:"packer_build_name"`
|
||||
}
|
||||
|
||||
type VMwareBoxPostProcessor struct {
|
||||
config VMwareBoxConfig
|
||||
}
|
||||
|
||||
func (p *VMwareBoxPostProcessor) Configure(raw interface{}) error {
|
||||
err := mapstructure.Decode(raw, &p.config)
|
||||
if err != nil {
|
||||
return err
|
||||
func (p *VMwareBoxPostProcessor) Configure(raws ...interface{}) error {
|
||||
for _, raw := range raws {
|
||||
err := mapstructure.Decode(raw, &p.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, error) {
|
||||
func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
// Compile the output path
|
||||
outputPath, err := ProcessOutputPath(p.config.OutputPath, "vmware", artifact)
|
||||
outputPath, err := ProcessOutputPath(p.config.OutputPath,
|
||||
p.config.PackerBuildName, "vmware", artifact)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Create a temporary directory for us to build the contents of the box in
|
||||
dir, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
@@ -48,37 +53,37 @@ func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artif
|
||||
ui.Message(fmt.Sprintf("Copying: %s", path))
|
||||
src, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(filepath.Join(dir, filepath.Base(path)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.VagrantfileTemplate != "" {
|
||||
f, err := os.Open(p.config.VagrantfileTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Create the Vagrantfile from the template
|
||||
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer vf.Close()
|
||||
|
||||
@@ -90,14 +95,14 @@ func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artif
|
||||
// Create the metadata
|
||||
metadata := map[string]string{"provider": "vmware_desktop"}
|
||||
if err := WriteMetadata(dir, metadata); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Compress the directory to the given output path
|
||||
ui.Message(fmt.Sprintf("Compressing box..."))
|
||||
if err := DirToBox(outputPath, dir); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return NewArtifact("vmware", outputPath), nil
|
||||
return NewArtifact("vmware", outputPath), false, nil
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
NO_COLOR="\x1b[0m"
|
||||
@@ -15,12 +15,12 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )"
|
||||
cd $DIR
|
||||
|
||||
# Compile the main Packer app
|
||||
echo "${OK_COLOR}--> Compiling Packer${NO_COLOR}"
|
||||
echo -e "${OK_COLOR}--> Compiling Packer${NO_COLOR}"
|
||||
go build -v -o bin/packer .
|
||||
|
||||
# Go over each plugin and build it
|
||||
for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do
|
||||
PLUGIN_NAME=$(basename ${PLUGIN})
|
||||
echo "${OK_COLOR}--> Compiling Plugin: ${PLUGIN_NAME}${NO_COLOR}"
|
||||
echo -e "${OK_COLOR}--> Compiling Plugin: ${PLUGIN_NAME}${NO_COLOR}"
|
||||
go build -v -o bin/packer-${PLUGIN_NAME} ${PLUGIN}
|
||||
done
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ commands:
|
||||
|
||||
```
|
||||
$ bundle
|
||||
$ bundle exec middleman server
|
||||
$ PACKER_DISABLE_DOWNLOAD_FETCH=true PACKER_VERSION=1.0 bundle exec middleman server
|
||||
```
|
||||
|
||||
Then open up `localhost:4567`. Note that some URLs you may need to append
|
||||
|
||||
+2
-2
@@ -1,6 +1,5 @@
|
||||
require "net/http"
|
||||
|
||||
raise "BINTRAY_API_KEY must be set." if !ENV["BINTRAY_API_KEY"]
|
||||
raise "PACKER_VERSION must be set." if !ENV["PACKER_VERSION"]
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@@ -11,6 +10,7 @@ $packer_files = {}
|
||||
$packer_os = []
|
||||
|
||||
if !ENV["PACKER_DISABLE_DOWNLOAD_FETCH"]
|
||||
raise "BINTRAY_API_KEY must be set." if !ENV["BINTRAY_API_KEY"]
|
||||
http = Net::HTTP.new("dl.bintray.com", 80)
|
||||
req = Net::HTTP::Get.new("/mitchellh/packer")
|
||||
req.basic_auth "mitchellh", ENV["BINTRAY_API_KEY"]
|
||||
@@ -82,7 +82,7 @@ helpers do
|
||||
end
|
||||
|
||||
def download_url(file)
|
||||
"http://dl.bintray.com/mitchellh/packer/#{file}?direct"
|
||||
"https://dl.bintray.com/mitchellh/packer/#{file}?direct"
|
||||
end
|
||||
|
||||
def latest_version
|
||||
|
||||
@@ -37,6 +37,8 @@ each category, the available configuration keys are alphabetized.
|
||||
Required:
|
||||
|
||||
* `access_key` (string) - The access key used to communicate with AWS.
|
||||
If not specified, Packer will attempt to read this from environmental
|
||||
variables `AWS_ACCESS_KEY_ID` or `AWS_ACCESS_KEY` (in that order).
|
||||
|
||||
* `ami_name` (string) - The name of the resulting AMI that will appear
|
||||
when managing AMIs in the AWS console or via APIs. This must be unique.
|
||||
@@ -50,6 +52,8 @@ Required:
|
||||
to launch the EC2 instance to create the AMI.
|
||||
|
||||
* `secret_key` (string) - The secret key used to communicate with AWS.
|
||||
If not specified, Packer will attempt to read this from environmental
|
||||
variables `AWS_SECRET_ACCESS_KEY` or `AWS_SECRET_KEY` (in that order).
|
||||
|
||||
* `source_ami` (string) - The initial AMI used as a base for the newly
|
||||
created machine.
|
||||
@@ -83,6 +87,13 @@ Here is a basic example. It is completely valid except for the access keys:
|
||||
}
|
||||
</pre>
|
||||
|
||||
<div class="alert alert-block alert-info">
|
||||
<strong>Note:</strong> Packer can also read the access key and secret
|
||||
access key from environmental variables. See the configuration reference in
|
||||
the section above for more information on what environmental variables Packer
|
||||
will look for.
|
||||
</div>
|
||||
|
||||
## AMI Name Variables
|
||||
|
||||
The AMI name specified by the `ami_name` configuration variable is actually
|
||||
|
||||
@@ -54,7 +54,7 @@ Required:
|
||||
Optional:
|
||||
|
||||
* `boot_command` (array of strings) - This is an array of commands to type
|
||||
when the virtual machine is firsted booted. The goal of these commands should
|
||||
when the virtual machine is first booted. The goal of these commands should
|
||||
be to type just enough to initialize the operating system installer. Special
|
||||
keys can be typed as well, and are covered in the section below on the boot
|
||||
command. If this is not specified, it is assumed the installer will start
|
||||
@@ -66,6 +66,9 @@ Optional:
|
||||
five seconds and one minute 30 seconds, respectively. If this isn't specified,
|
||||
the default is 10 seconds.
|
||||
|
||||
* `disk_size` (int) - The size, in megabytes, of the hard disk to create
|
||||
for the VM. By default, this is 40000 (40 GB).
|
||||
|
||||
* `guest_additions_path` (string) - The path on the guest virtual machine
|
||||
where the VirtualBox guest additions ISO will be uploaded. By default this
|
||||
is "VBoxGuestAdditions.iso" which should upload into the login directory
|
||||
@@ -79,6 +82,11 @@ Optional:
|
||||
how to optimize the virtual hardware to work best with that operating
|
||||
system.
|
||||
|
||||
* `headless` (bool) - Packer defaults to building VirtualBox
|
||||
virtual machines by launching a GUI that shows the console of the
|
||||
machine being built. When this value is set to true, the machine will
|
||||
start without a console.
|
||||
|
||||
* `http_directory` (string) - Path to a directory to serve using an HTTP
|
||||
server. The files in this directory will be available over HTTP that will
|
||||
be requestable from the virtual machine. This is useful for hosting
|
||||
@@ -97,8 +105,9 @@ Optional:
|
||||
* `output_directory` (string) - This is the path to the directory where the
|
||||
resulting virtual machine will be created. This may be relative or absolute.
|
||||
If relative, the path is relative to the working directory when `packer`
|
||||
is executed. By default this is "virtualbox". This directory must not exist
|
||||
or be empty prior to running the builder.
|
||||
is executed. This directory must not exist or be empty prior to running the builder.
|
||||
By default this is "output-BUILDNAME" where "BUILDNAME" is the name
|
||||
of the build.
|
||||
|
||||
* `shutdown_command` (string) - The command to use to gracefully shut down
|
||||
the machine once all the provisioning is done. By default this is an empty
|
||||
@@ -123,7 +132,7 @@ Optional:
|
||||
|
||||
* `ssh_wait_timeout` (string) - The duration to wait for SSH to become
|
||||
available. By default this is "20m", or 20 minutes. Note that this should
|
||||
be quite long since the timer begins as soon as virtual machine is booted.
|
||||
be quite long since the timer begins as soon as the virtual machine is booted.
|
||||
|
||||
* `vboxmanage` (array of array of strings) - Custom `VBoxManage` commands to
|
||||
execute in order to further customize the virtual machine being created.
|
||||
@@ -142,7 +151,8 @@ Optional:
|
||||
the home directory.
|
||||
|
||||
* `vm_name` (string) - This is the name of the VMX file for the new virtual
|
||||
machine, without the file extension. By default this is "packer".
|
||||
machine, without the file extension. By default this is "packer-BUILDNAME",
|
||||
where "BUILDNAME" is the name of the build.
|
||||
|
||||
## Boot Command
|
||||
|
||||
@@ -166,7 +176,7 @@ will be replaced by the proper key:
|
||||
|
||||
* `<tab>` - Simulates pressing the tab key.
|
||||
|
||||
* `<wait>` - Adds a one second pause before sending any additional keys. This
|
||||
* `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before sending any additional keys. This
|
||||
is useful if you have to generally wait for the UI to update before typing more.
|
||||
|
||||
In addition to the special keys, each command to type is treated as a
|
||||
|
||||
@@ -77,6 +77,13 @@ Optional:
|
||||
OS type, VMware may perform some optimizations or virtual hardware changes
|
||||
to better support the operating system running in the virtual machine.
|
||||
|
||||
* `headless` (bool) - Packer defaults to building VMware
|
||||
virtual machines by launching a GUI that shows the console of the
|
||||
machine being built. When this value is set to true, the machine will
|
||||
start without a console. For VMware machines, Packer will output VNC
|
||||
connection information in case you need to connect to the console to
|
||||
debug the build process.
|
||||
|
||||
* `http_directory` (string) - Path to a directory to serve using an HTTP
|
||||
server. The files in this directory will be available over HTTP that will
|
||||
be requestable from the virtual machine. This is useful for hosting
|
||||
@@ -95,8 +102,15 @@ Optional:
|
||||
* `output_directory` (string) - This is the path to the directory where the
|
||||
resulting virtual machine will be created. This may be relative or absolute.
|
||||
If relative, the path is relative to the working directory when `packer`
|
||||
is executed. By default this is "vmware". This directory must not exist
|
||||
or be empty prior to running the builder.
|
||||
is executed. This directory must not exist or be empty prior to running the builder.
|
||||
By default this is "output-BUILDNAME" where "BUILDNAME" is the name
|
||||
of the build.
|
||||
|
||||
* `skip_compaction` (bool) - VMware-created disks are defragmented
|
||||
and compacted at the end of the build process using `vmware-vdiskmanager`.
|
||||
In certain rare cases, this might actually end up making the resulting disks
|
||||
slightly larger. If you find this to be the case, you can disable compaction
|
||||
using this configuration value.
|
||||
|
||||
* `shutdown_command` (string) - The command to use to gracefully shut down
|
||||
the machine once all the provisioning is done. By default this is an empty
|
||||
@@ -115,10 +129,22 @@ Optional:
|
||||
|
||||
* `ssh_wait_timeout` (string) - The duration to wait for SSH to become
|
||||
available. By default this is "20m", or 20 minutes. Note that this should
|
||||
be quite long since the timer begins as soon as virtual machine is booted.
|
||||
be quite long since the timer begins as soon as the virtual machine is booted.
|
||||
|
||||
* `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to
|
||||
upload into the VM. Valid values are "darwin", "linux", and "windows".
|
||||
By default, this is empty, which means VMware tools won't be uploaded.
|
||||
|
||||
* `tools_upload_path` (string) - The path in the VM to upload the VMware
|
||||
tools. This only takes effect if `tools_upload_flavor` is non-empty.
|
||||
This is a [configuration template](/docs/templates/configuration-templates.html)
|
||||
that has a single valid variable: `Flavor`, which will be the value of
|
||||
`tools_upload_flavor`. By default the upload path is set to
|
||||
`{{.Flavor}}.iso`.
|
||||
|
||||
* `vm_name` (string) - This is the name of the VMX file for the new virtual
|
||||
machine, without the file extension. By default this is "packer".
|
||||
machine, without the file extension. By default this is "packer-BUILDNAME",
|
||||
where "BUILDNAME" is the name of the build.
|
||||
|
||||
* `vmdk_name` (string) - The filename of the virtual disk that'll be created,
|
||||
without the extension. This defaults to "packer".
|
||||
@@ -155,7 +181,7 @@ will be replaced by the proper key:
|
||||
|
||||
* `<tab>` - Simulates pressing the tab key.
|
||||
|
||||
* `<wait>` - Adds a one second pause before sending any additional keys. This
|
||||
* `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before sending any additional keys. This
|
||||
is useful if you have to generally wait for the UI to update before typing more.
|
||||
|
||||
In addition to the special keys, each command to type is treated as a
|
||||
|
||||
@@ -52,7 +52,7 @@ it is not required. You're allowed to make the help look like anything
|
||||
you please.
|
||||
|
||||
```
|
||||
Usage: packer COMMAND ARGS...
|
||||
Usage: packer COMMAND [options] ARGS...
|
||||
|
||||
Brief one or two sentence about the function of the command.
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ what each method should do.
|
||||
<pre class="prettyprint">
|
||||
type PostProcessor interface {
|
||||
Configure(interface{}) error
|
||||
PostProcess(Ui, Artifact) (Artifact, error)
|
||||
PostProcess(Ui, Artifact) (a Artifact, keep bool, err error)
|
||||
}
|
||||
</pre>
|
||||
|
||||
@@ -79,3 +79,15 @@ a "compress" post-processor that is responsible for compressing files,
|
||||
the transformation would be taking the `Files()` from the original artifact,
|
||||
compressing them, and creating a new artifact with a single file: the
|
||||
compressed archive.
|
||||
|
||||
The result signature of this method is `(Artifact, bool, error)`. Each
|
||||
return value is explained below:
|
||||
|
||||
* `Artifact` - The newly created artifact if no errors occurred.
|
||||
* `bool` - If true, the input artifact will forcefully be kept. By default,
|
||||
Packer typically deletes all input artifacts, since the user doesn't generally
|
||||
want intermediary artifacts. However, some post-processors depend on the
|
||||
previous artifact existing. If this is `true`, it forces packer to keep the
|
||||
artifact around.
|
||||
* `error` - Non-nil if there was an error in any way. If this is the case,
|
||||
the other two return values are ignored.
|
||||
|
||||
@@ -47,6 +47,6 @@ Available commands are:
|
||||
|
||||
If you get an error that `packer` could not be found, then your PATH
|
||||
environmental variable was not setup properly. Please go back and ensure
|
||||
that yoru PATH variable contains the directory which has Packer installed.
|
||||
that your PATH variable contains the directory which has Packer installed.
|
||||
|
||||
Otherwise, Packer is installed and you're ready to go!
|
||||
|
||||
@@ -22,7 +22,7 @@ $ gem install veewee-to-packer
|
||||
...
|
||||
```
|
||||
|
||||
Once installed, usage is easy! Just point call `veewee-to-packer` pointed
|
||||
Once installed, usage is easy! Just point `veewee-to-packer`
|
||||
at the `definition.rb` file of any template. The converter will output
|
||||
any warnings or messages about the conversion. The example below converts
|
||||
a CentOS template:
|
||||
@@ -49,7 +49,7 @@ specific builder configurations.
|
||||
|
||||
None, really. The tool will tell you if it can't convert a part of a
|
||||
template, and whether that is a critical error or just a warning.
|
||||
Most of Veewees functions translate perfectly over to Packer. There are
|
||||
Most of Veewee's functions translate perfectly over to Packer. There are
|
||||
still a couple missing features in Packer, but they're minimal.
|
||||
|
||||
## Bugs
|
||||
|
||||
@@ -17,7 +17,7 @@ page_title: "Downloads"
|
||||
Below are all available downloads for the latest version of Packer
|
||||
(<%= latest_version %>). Please download the proper package for your
|
||||
operating system and architecture. You can find SHA256 checksums
|
||||
for packages <a href="http://dl.bintray.com/mitchellh/packer/<%= latest_version %>_SHA256SUMS?direct">here</a>.
|
||||
for packages <a href="https://dl.bintray.com/mitchellh/packer/<%= latest_version %>_SHA256SUMS?direct">here</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ next_title: "Why Use Packer?"
|
||||
# Introduction to Packer
|
||||
|
||||
Welcome to the world of Packer! This introduction guide will show you what
|
||||
packer is, explain why it exists, the benefits it has to offer, and how
|
||||
Packer is, explain why it exists, the benefits it has to offer, and how
|
||||
you can get started with it. If you're already familiar with Packer, the
|
||||
[documentation](/docs) provides more of a reference for all available features.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user