Compare commits

..

58 Commits

Author SHA1 Message Date
Mitchell Hashimoto df560dbbe1 v0.1.4 2013-07-02 22:16:13 -07:00
Mitchell Hashimoto 259986e7e5 builder/virtualbox, builder/vmware: keyboard-interactive [GH-121] 2013-07-02 22:02:58 -07:00
Mitchell Hashimoto 40e9f9a76f communicator/ssh: add keyboard interactive auth [GH-121] 2013-07-02 22:00:31 -07:00
Mitchell Hashimoto 2f698cca77 Update CHANGELOG 2013-07-02 21:29:28 -07:00
Mitchell Hashimoto 7247a69581 website: nitpick compaction docs 2013-07-02 21:28:54 -07:00
Mitchell Hashimoto 2958b5d9c2 Merge pull request #122 from smerrill/compaction-option
builder/vmware: Add a 'skip_compaction' configuration option
2013-07-02 21:27:20 -07:00
Mitchell Hashimoto a8da773e40 Merge pull request #123 from smerrill/headless-documentation
website: Document 'headless' option for Fusion and VBox
2013-07-02 21:26:37 -07:00
Steven Merrill 663cce5aaa Remove confusing duplicate references. 2013-07-02 23:56:56 -04:00
Steven Merrill 82a3aba0d3 builder/vmware, builder/virtualbox: Document 'headless' option. 2013-07-02 23:53:31 -04:00
Steven Merrill 2e15bed7b6 Add documentation of the skip_compaction parameter. 2013-07-02 23:48:04 -04:00
Steven Merrill 9264f76053 Add a 'skip_compaction' step. 2013-07-02 20:22:11 -04:00
Mitchell Hashimoto ae279a8b61 Merge pull request #120 from bdon/master
helptext has command [options] in it
2013-07-02 13:16:39 -07:00
Brandon Liu f31f5ed759 Command helpText indicates that options must come before template. 2013-07-02 13:07:39 -07:00
Mitchell Hashimoto 8ce8171ec1 packer: PrefixedUi properly prefixes each line of output 2013-07-02 12:28:25 -07:00
Mitchell Hashimoto b4b9469d3c builder/vmware: output VNC info if headless
/cc @smerrill
2013-07-02 12:23:19 -07:00
Mitchell Hashimoto 925734d06b builder/virtualbox: Warning about headless mode
/cc @smerrill
2013-07-02 12:20:26 -07:00
Mitchell Hashimoto 2e88ddc965 Update CHANGELOG 2013-07-02 12:18:31 -07:00
Mitchell Hashimoto b729ba0e00 Merge pull request #108 from smerrill/headless-mode
builder/vmware, builder/virtualbox: Headless mode
2013-07-02 12:16:40 -07:00
Mitchell Hashimoto 4bb5f8a6aa Merge pull request #115 from bdon/wait
builder/virtualbox, builder/vmware: wait5 and wait10 timers
2013-07-02 12:14:17 -07:00
Brandon Liu 2fc795d299 Update command to run locally in website README. 2013-07-02 12:12:42 -07:00
Brandon Liu f931b28851 Document wait5 and wait10 boot command keys. 2013-07-02 10:44:49 -07:00
Brandon Liu cf13f57528 Add wait5 and wait10 special keys for boot command. 2013-07-02 10:31:22 -07:00
Mitchell Hashimoto aff4389fc8 Update CHANGELOG 2013-07-02 09:38:39 -07:00
Mitchell Hashimoto 4d4dd85ef4 builder/vmware: Improve check for VMware cleanup 2013-07-02 09:30:24 -07:00
Mitchell Hashimoto be40fb7abb builder/vmware: No redundant errors 2013-07-02 09:10:44 -07:00
Mitchell Hashimoto 00a5ca32ae builder/vmware: Style nitpick
/cc @smerrill: I prefer to do state changes at the end. :)
2013-07-02 09:09:55 -07:00
Mitchell Hashimoto 93e5082d04 Merge pull request #107 from smerrill/compact-disk
builder/vmware: Compact disk at end of the run
2013-07-02 09:09:15 -07:00
Mitchell Hashimoto f36972f52a builder/vmware: wait for shutdown to avoid corrupt disk files [GH-111] 2013-07-02 09:05:17 -07:00
Mitchell Hashimoto 8ece9a60d5 post-processor/vagrant: check for error when tarring earlier 2013-07-02 08:46:20 -07:00
Steven Merrill 432cab3fcf Small fix to be sure "full_disk_path" is put into state. 2013-07-02 01:32:50 -04:00
Steven Merrill 586a9a7c32 Small doc fixes. 2013-07-02 01:15:03 -04:00
Steven Merrill 359ba01c6a Integrate code review comments. 2013-07-02 01:12:57 -04:00
Mitchell Hashimoto dbbbc38af6 Merge pull request #109 from razzintown/patch-1
website: capitalize "Packer"
2013-07-01 22:12:49 -07:00
Razz Fox f2111b6d12 capitalize "Packer" 2013-07-01 22:01:22 -07:00
Steven Merrill f27ea933a4 Add VirtualBox headless mode. 2013-07-02 00:13:24 -04:00
Steven Merrill 4aa55c40de Add VMWare headless mode. 2013-07-02 00:13:13 -04:00
Mitchell Hashimoto 06108b4f96 Merge pull request #106 from bdon/master
website: typos
2013-07-01 21:09:59 -07:00
Brandon Liu 0e3b1e59a8 Doc fixes. 2013-07-01 19:53:04 -07:00
Steven Merrill 29fa621907 Finish initial compaction feature. 2013-07-01 22:45:46 -04:00
Steven Merrill d4cd9352d6 First try at a compaction step. 2013-07-01 22:25:33 -04:00
Steven Merrill 4de76ccd3f Add a computed FullDiskPath config option. 2013-07-01 22:22:00 -04:00
Mitchell Hashimoto ac5c90bd7e Merge pull request #101 from ericlathrop/fix-make-test
Fix import paths in "make test" dependency fetching
2013-07-01 16:37:07 -07:00
Eric Lathrop 7bff7f8a22 Remove newline from test import paths.
When running "make test" on my machine, I got many unrecognized import
path errors, and the path showed a trailing "\n". Changing the go list
format to remove the newline fixed these.
2013-07-01 19:25:23 -04:00
Mitchell Hashimoto 126f2bc07f website: more notes on AWS env vars 2013-07-01 15:26:08 -07:00
Mitchell Hashimoto 0b1b76a4db website: update website with new AWS env vars 2013-07-01 15:24:36 -07:00
Mitchell Hashimoto 9ca3d05241 builder/amazonebs: tests for env vars 2013-07-01 15:22:35 -07:00
Mitchell Hashimoto 2a7a7173f1 Merge pull request #40 from justincampbell/amazon-env-vars
builder/amazonebs: Default to AWS env vars for key and secret
2013-07-01 15:11:19 -07:00
Mitchell Hashimoto 8e231f9264 fmt 2013-07-01 15:07:37 -07:00
Mitchell Hashimoto 3f9df2992c post-processor/vagrant: make output contain build name by default
[GH-92]
2013-07-01 15:07:09 -07:00
Mitchell Hashimoto 400faa57a5 packer: Send packerConfig into PostProcessors 2013-07-01 15:00:21 -07:00
Mitchell Hashimoto fc78bf3dd6 packer: PostProcessor can take multiple configs 2013-07-01 14:59:23 -07:00
Mitchell Hashimoto 8cf21324dc packer: extra comments on JSON syntax error calc 2013-07-01 14:47:49 -07:00
Mitchell Hashimoto 974ac26c9c packer: Clean up some of the JSON syntax stuff 2013-07-01 14:46:32 -07:00
Mitchell Hashimoto b6884da2a1 Merge pull request #95 from markpeek/markpeek-jsonerror
packer: Provide line number for invalid json syntax [GH-56]
2013-07-01 14:38:01 -07:00
Mitchell Hashimoto 9f17257f19 Up version for development 2013-07-01 14:37:43 -07:00
Mark Peek 5bf33a0e91 Provide line number for invalid json syntax [GH-56] 2013-07-01 13:30:08 -07:00
Justin Campbell 4b2b23c32f Add AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY 2013-06-29 09:32:07 -04:00
Justin Campbell 9c9c60aabf Default to AWS_ env vars for key and secret 2013-06-28 13:45:15 -04:00
45 changed files with 478 additions and 69 deletions
+26
View File
@@ -1,3 +1,29 @@
## 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:
+1 -2
View File
@@ -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
+17
View File
@@ -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
}
+60
View File
@@ -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) {
+1
View File
@@ -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"`
+8 -1
View File
@@ -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
+23 -1
View File
@@ -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) {
+2
View File
@@ -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)),
},
}
+3
View File
@@ -33,11 +33,13 @@ type config struct {
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"`
@@ -239,6 +241,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&stepProvision{},
&stepShutdown{},
&stepCleanFiles{},
&stepCompactDisk{},
}
// Setup the state bag
+25 -3
View File
@@ -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,7 +22,7 @@ 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
@@ -40,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 {
@@ -70,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
}
+43
View File
@@ -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{}) {}
+5 -3
View File
@@ -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
}
+9 -1
View File
@@ -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())
+30
View File
@@ -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
}
+14
View File
@@ -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)
+2
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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.
+23
View File
@@ -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
}
+22
View File
@@ -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)
}
}
+2 -1
View File
@@ -121,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
}
}
+1 -1
View File
@@ -59,7 +59,7 @@ func TestBuild_Prepare(t *testing.T) {
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) {
+2 -2
View File
@@ -10,13 +10,13 @@ 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, bool, error) {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
type helperPostProcessor byte
func (helperPostProcessor) Configure(interface{}) error {
func (helperPostProcessor) Configure(...interface{}) error {
return nil
}
+1 -1
View File
@@ -9,7 +9,7 @@ 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. If `keep`
+2 -2
View File
@@ -4,13 +4,13 @@ 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
+9 -4
View File
@@ -17,6 +17,10 @@ type PostProcessorServer struct {
p packer.PostProcessor
}
type PostProcessorConfigureArgs struct {
Configs []interface{}
}
type PostProcessorProcessResponse struct {
Err error
Keep bool
@@ -26,8 +30,9 @@ type PostProcessorProcessResponse struct {
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
}
@@ -60,8 +65,8 @@ func (p *postProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.Art
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)
}
+4 -3
View File
@@ -3,6 +3,7 @@ package rpc
import (
"github.com/mitchellh/packer/packer"
"net/rpc"
"reflect"
"testing"
)
@@ -10,13 +11,13 @@ 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
@@ -56,7 +57,7 @@ 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)
}
+24
View File
@@ -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
View File
@@ -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) {
+3
View File
@@ -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) {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
)
// The version of packer.
const Version = "0.1.3"
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
+10 -5
View File
@@ -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,10 +26,12 @@ 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
@@ -49,7 +53,8 @@ func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact
}
// 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, false, err
}
+17 -8
View File
@@ -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)
}
+8 -1
View File
@@ -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,
}
+11 -6
View File
@@ -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,10 +30,12 @@ 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
@@ -46,7 +50,8 @@ func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifac
}
// 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, false, err
}
@@ -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
`
+10 -5
View File
@@ -14,16 +14,20 @@ 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
@@ -31,7 +35,8 @@ func (p *VMwareBoxPostProcessor) Configure(raw interface{}) 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, false, err
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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"]
@@ -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
@@ -82,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
@@ -127,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.
@@ -171,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
@@ -99,6 +106,12 @@ Optional:
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
string, which tells Packer to just forcefully shut down the machine.
@@ -116,7 +129,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.
* `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to
upload into the VM. Valid values are "darwin", "linux", and "windows".
@@ -134,7 +147,7 @@ Optional:
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 is a This defaults to "packer".
without the extension. This defaults to "packer".
* `vmx_data` (object, string keys and string values) - Arbitrary key/values
to enter into the virtual machine VMX file. This is for advanced users
@@ -168,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.
@@ -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
+1 -1
View File
@@ -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.