Compare commits

..

1 Commits

Author SHA1 Message Date
Mitchell Hashimoto 9907933bf8 v0.3.2 2013-08-18 09:47:14 -06:00
210 changed files with 1646 additions and 6372 deletions
+1 -3
View File
@@ -5,9 +5,7 @@ go:
- tip
install: make deps
script:
- go test ./...
- go test -race ./...
script: make test
notifications:
flowdock:
-153
View File
@@ -1,156 +1,3 @@
## 0.3.7 (September 9, 2013)
BACKWARDS INCOMPATIBILITIES:
* The "event_delay" option for the DigitalOcean builder is now gone.
The builder automatically waits for events to go away. Run your templates
through `packer fix` to get rid of these.
FEATURES:
* **NEW PROVISIONER:** `puppet-masterless`. You can now provision with
a masterless Puppet setup. [GH-234]
* New globally available template function: `uuid`. Generates a new random
UUID.
* New globally available template function: `isotime`. Generates the
current time in ISO standard format.
* New Amazon template function: `clean_ami_name`. Substitutes '-' for
characters that are illegal to use in an AMI name.
IMPROVEMENTS:
* builder/amazon/all: Ability to specify the format of the temporary
keypair created. [GH-389]
* builder/amazon/all: Support the NoDevice flag for block mappings. [GH-396]
* builder/digitalocean: Retry on any pending event errors.
* builder/openstack: Can now specify a project. [GH-382]
* builder/virtualbox: Can now attach hard drive over SATA. [GH-391]
* provisioner/file: Can now upload directories. [GH-251]
BUG FIXES:
* core: Detect if SCP is not enabled on the other side. [GH-386]
* builder/amazon/all: When copying AMI to multiple regions, copy
the metadata (tags and attributes) as well. [GH-388]
* builder/amazon/all: Fix panic case where eventually consistent
instance state caused an index out of bounds.
* builder/virtualbox: The `vm_name` setting now properly sets the OVF
name of the output. [GH-401]
* builder/vmware: Autoanswer VMware dialogs. [GH-393]
* command/inspect: Fix weird output for default values for optional vars.
## 0.3.6 (September 2, 2013)
FEATURES:
* User variables can now be specified as "required", meaning the user
MUST specify a value. Just set the default value to "null". [GH-374]
IMPROVEMENTS:
* core: Much improved interrupt handling. For example, interrupts now
cancel much more quickly within provisioners.
* builder/amazon: In `-debug` mode, the keypair used will be saved to
the current directory so you can access the machine. [GH-373]
* builder/amazon: In `-debug` mode, the DNS is outputted.
* builder/openstack: IPv6 addresses supported for SSH. [GH-379]
* communicator/ssh: Support for private keys encrypted using PKCS8. [GH-376]
* provisioner/chef-solo: You can now use user variables in the `json`
configuration for Chef. [GH-362]
BUG FIXES:
* core: Concurrent map access is completely gone, fixing rare issues
with runtime memory corruption. [GH-307]
* core: Fix possible panic when ctrl-C during provisioner run.
* builder/digitalocean: Retry destroy a few times because DO sometimes
gives false errors.
* builder/openstack: Properly handle the case no image is made. [GH-375]
* builder/openstack: Specifying a region is now required in a template.
* provisioners/salt-masterless: Use filepath join to properly join paths.
## 0.3.5 (August 28, 2013)
FEATURES:
* **NEW BUILDER:** `openstack`. You can now build on OpenStack. [GH-155]
* **NEW PROVISIONER:** `chef-solo`. You can now provision with Chef
using `chef-solo` from local cookbooks.
* builder/amazon: Copy AMI to multiple regions with `ami_regions`. [GH-322]
* builder/virtualbox,vmware: Can now use SSH keys as an auth mechanism for
SSH using `ssh_key_path`. [GH-70]
* builder/virtualbox,vmware: Support SHA512 as a checksum type. [GH-356]
* builder/vmware: The root hard drive type can now be specified with
"disk_type_id" for advanced users. [GH-328]
* provisioner/salt-masterless: Ability to specfy a minion config. [GH-264]
* provisioner/salt-masterless: Ability to upload pillars. [GH-353]
IMPROVEMENTS:
* core: Output message when Ctrl-C received that we're cleaning up. [GH-338]
* builder/amazon: Tagging now works with all amazon builder types.
* builder/vmware: Option `ssh_skip_request_pty` for not requesting a PTY
for the SSH connection. [GH-270]
* builder/vmware: Specify a `vmx_template_path` in order to customize
the generated VMX. [GH-270]
* command/build: Machine-readable output now contains build errors, if any.
* command/build: An "end" sentinel is outputted in machine-readable output
for artifact listing so it is easier to know when it is over.
BUG FIXES:
* core: Fixed a couple cases where a double ctrl-C could panic.
* core: Template validation fails if an override is specified for a
non-existent builder. [GH-336]
* core: The SSH connection is heartbeated so that drops can be
detected. [GH-200]
* builder/amazon/instance: Remove check for ec2-ami-tools because it
didn't allow absolute paths to work properly. [GH-330]
* builder/digitalocean: Send a soft shutdown request so that files
are properly synced before shutdown. [GH-332]
* command/build,command/validate: If a non-existent build is specified to
'-only' or '-except', it is now an error. [GH-326]
* post-processor/vagrant: Setting OutputPath with a timestamp now
always works properly. [GH-324]
* post-processor/vagrant: VirtualBox OVA formats now turn into
Vagrant boxes properly. [GH-331]
* provisioner/shell: Retry upload if start command fails, making reboot
handling much more robust.
## 0.3.4 (August 21, 2013)
IMPROVEMENTS:
* post-processor/vagrant: the file being compressed will be shown
in the UI [GH-314]
BUG FIXES:
* core: Avoid panics when double-interrupting Packer.
* provisioner/shell: Retry shell script uploads, making reboots more
robust if they happen to fail in this stage. [GH-282]
## 0.3.3 (August 19, 2013)
FEATURES:
* builder/virtualbox: support exporting in OVA format. [GH-309]
IMPROVEMENTS:
* core: All HTTP downloads across Packer now support the standard
proxy environmental variables (`HTTP_PROXY`, `NO_PROXY`, etc.) [GH-252]
* builder/amazon: API requests will use HTTP proxy if specified by
enviromental variables.
* builder/digitalocean: API requests will use HTTP proxy if specified
by environmental variables.
BUG FIXES:
* core: TCP connection between plugin processes will keep-alive. [GH-312]
* core: No more "unused key keep_input_artifact" for post processors [GH-310]
* post-processor/vagrant: `output_path` templates now work again.
## 0.3.2 (August 18, 2013)
FEATURES:
-3
View File
@@ -90,6 +90,3 @@ $ bin/packer
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.
If you make any changes to the code, run `make format` in order to automatically
format the code according to Go standards.
+8 -15
View File
@@ -53,7 +53,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
b.config.tpl.Funcs(awscommon.TemplateFuncs)
// Defaults
if b.config.ChrootMounts == nil {
@@ -168,11 +167,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn := ec2.New(auth, region)
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
state := make(map[string]interface{})
state["config"] = &b.config
state["ec2"] = ec2conn
state["hook"] = hook
state["ui"] = ui
// Build the steps
steps := []multistep.Step{
@@ -190,17 +189,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepEarlyCleanup{},
&StepSnapshot{},
&StepRegisterAMI{},
&awscommon.StepAMIRegionCopy{
Regions: b.config.AMIRegions,
},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
},
}
// Run!
@@ -216,18 +209,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
if rawErr, ok := state["error"]; ok {
return nil, rawErr.(error)
}
// If there are no AMIs, then just return
if _, ok := state.GetOk("amis"); !ok {
if _, ok := state["amis"]; !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &awscommon.Artifact{
Amis: state.Get("amis").(map[string]string),
Amis: state["amis"].(map[string]string),
BuilderIdValue: BuilderId,
Conn: ec2conn,
}
+1 -5
View File
@@ -1,10 +1,6 @@
package chroot
import (
"github.com/mitchellh/multistep"
)
// Cleanup is an interface that some steps implement for early cleanup.
type Cleanup interface {
CleanupFunc(multistep.StateBag) error
CleanupFunc(map[string]interface{}) error
}
-32
View File
@@ -70,38 +70,6 @@ func (c *Communicator) Upload(dst string, r io.Reader) error {
return nil
}
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
walkFn := func(fullPath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
path, err := filepath.Rel(src, fullPath)
if err != nil {
return err
}
for _, e := range exclude {
if e == path {
log.Printf("Skipping excluded file: %s", path)
return nil
}
}
dstPath := filepath.Join(dst, path)
f, err := os.Open(fullPath)
if err != nil {
return err
}
defer f.Close()
return c.Upload(dstPath, f)
}
log.Printf("Uploading directory '%s' to '%s'", src, dst)
return filepath.Walk(src, walkFn)
}
func (c *Communicator) Download(src string, w io.Writer) error {
src = filepath.Join(c.Chroot, src)
log.Printf("Downloading from chroot dir: %s", src)
+14 -14
View File
@@ -21,12 +21,12 @@ type StepAttachVolume struct {
volumeId string
}
func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
device := state.Get("device").(string)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
volumeId := state.Get("volume_id").(string)
func (s *StepAttachVolume) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
device := state["device"].(string)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
volumeId := state["volume_id"].(string)
// For the API call, it expects "sd" prefixed devices.
attachVolume := strings.Replace(device, "/xvd", "/sd", 1)
@@ -35,7 +35,7 @@ func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
_, err := ec2conn.AttachVolume(volumeId, instance.InstanceId, attachVolume)
if err != nil {
err := fmt.Errorf("Error attaching volume: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -67,29 +67,29 @@ func (s *StepAttachVolume) Run(state multistep.StateBag) multistep.StepAction {
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("attach_cleanup", s)
state["attach_cleanup"] = s
return multistep.ActionContinue
}
func (s *StepAttachVolume) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
func (s *StepAttachVolume) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepAttachVolume) CleanupFunc(state multistep.StateBag) error {
func (s *StepAttachVolume) CleanupFunc(state map[string]interface{}) error {
if !s.attached {
return nil
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Detaching EBS volume...")
_, err := ec2conn.DetachVolume(s.volumeId)
@@ -11,10 +11,10 @@ type StepChrootProvision struct {
mounts []string
}
func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction {
hook := state.Get("hook").(packer.Hook)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
func (s *StepChrootProvision) Run(state map[string]interface{}) multistep.StepAction {
hook := state["hook"].(packer.Hook)
mountPath := state["mount_path"].(string)
ui := state["ui"].(packer.Ui)
// Create our communicator
comm := &Communicator{
@@ -24,11 +24,11 @@ func (s *StepChrootProvision) Run(state multistep.StateBag) multistep.StepAction
// Provision
log.Println("Running the provision hook")
if err := hook.Run(packer.HookProvision, ui, comm, nil); err != nil {
state.Put("error", err)
state["error"] = err
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepChrootProvision) Cleanup(state multistep.StateBag) {}
func (s *StepChrootProvision) Cleanup(state map[string]interface{}) {}
+9 -9
View File
@@ -19,10 +19,10 @@ type StepCopyFiles struct {
files []string
}
func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
func (s *StepCopyFiles) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
mountPath := state["mount_path"].(string)
ui := state["ui"].(packer.Ui)
s.files = make([]string, 0, len(config.CopyFiles))
if len(config.CopyFiles) > 0 {
@@ -34,7 +34,7 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
if err := s.copySingle(chrootPath, path); err != nil {
err := fmt.Errorf("Error copying file: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -43,18 +43,18 @@ func (s *StepCopyFiles) Run(state multistep.StateBag) multistep.StepAction {
}
}
state.Put("copy_files_cleanup", s)
state["copy_files_cleanup"] = s
return multistep.ActionContinue
}
func (s *StepCopyFiles) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
func (s *StepCopyFiles) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepCopyFiles) CleanupFunc(multistep.StateBag) error {
func (s *StepCopyFiles) CleanupFunc(map[string]interface{}) error {
if s.files != nil {
for _, file := range s.files {
log.Printf("Removing: %s", file)
+12 -12
View File
@@ -18,11 +18,11 @@ type StepCreateVolume struct {
volumeId string
}
func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
image := state.Get("source_image").(*ec2.Image)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
func (s *StepCreateVolume) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
image := state["source_image"].(*ec2.Image)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
// Determine the root device snapshot
log.Printf("Searching for root device of the image (%s)", image.RootDeviceName)
@@ -36,7 +36,7 @@ func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
if rootDevice == nil {
err := fmt.Errorf("Couldn't find root device!")
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -54,7 +54,7 @@ func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
createVolumeResp, err := ec2conn.CreateVolume(createVolume)
if err != nil {
err := fmt.Errorf("Error creating root volume: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -82,22 +82,22 @@ func (s *StepCreateVolume) Run(state multistep.StateBag) multistep.StepAction {
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for volume: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("volume_id", s.volumeId)
state["volume_id"] = s.volumeId
return multistep.ActionContinue
}
func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
func (s *StepCreateVolume) Cleanup(state map[string]interface{}) {
if s.volumeId == "" {
return
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Deleting the created EBS volume...")
_, err := ec2conn.DeleteVolume(s.volumeId)
+5 -5
View File
@@ -11,8 +11,8 @@ import (
// prepare for snapshotting and creating an AMI.
type StepEarlyCleanup struct{}
func (s *StepEarlyCleanup) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
func (s *StepEarlyCleanup) Run(state map[string]interface{}) multistep.StepAction {
ui := state["ui"].(packer.Ui)
cleanupKeys := []string{
"copy_files_cleanup",
"mount_extra_cleanup",
@@ -21,11 +21,11 @@ func (s *StepEarlyCleanup) Run(state multistep.StateBag) multistep.StepAction {
}
for _, key := range cleanupKeys {
c := state.Get(key).(Cleanup)
c := state[key].(Cleanup)
log.Printf("Running cleanup func: %s", key)
if err := c.CleanupFunc(state); err != nil {
err := fmt.Errorf("Error cleaning up: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -34,4 +34,4 @@ func (s *StepEarlyCleanup) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *StepEarlyCleanup) Cleanup(state multistep.StateBag) {}
func (s *StepEarlyCleanup) Cleanup(state map[string]interface{}) {}
+5 -5
View File
@@ -10,14 +10,14 @@ import (
// StepEarlyUnflock unlocks the flock.
type StepEarlyUnflock struct{}
func (s *StepEarlyUnflock) Run(state multistep.StateBag) multistep.StepAction {
cleanup := state.Get("flock_cleanup").(Cleanup)
ui := state.Get("ui").(packer.Ui)
func (s *StepEarlyUnflock) Run(state map[string]interface{}) multistep.StepAction {
cleanup := state["flock_cleanup"].(Cleanup)
ui := state["ui"].(packer.Ui)
log.Println("Unlocking file lock...")
if err := cleanup.CleanupFunc(state); err != nil {
err := fmt.Errorf("Error unlocking file lock: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -25,4 +25,4 @@ func (s *StepEarlyUnflock) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *StepEarlyUnflock) Cleanup(state multistep.StateBag) {}
func (s *StepEarlyUnflock) Cleanup(state map[string]interface{}) {}
+8 -8
View File
@@ -17,13 +17,13 @@ type StepFlock struct {
fh *os.File
}
func (s *StepFlock) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
func (s *StepFlock) Run(state map[string]interface{}) multistep.StepAction {
ui := state["ui"].(packer.Ui)
lockfile := "/var/lock/packer-chroot/lock"
if err := os.MkdirAll(filepath.Dir(lockfile), 0755); err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -32,7 +32,7 @@ func (s *StepFlock) Run(state multistep.StateBag) multistep.StepAction {
f, err := os.Create(lockfile)
if err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -40,7 +40,7 @@ func (s *StepFlock) Run(state multistep.StateBag) multistep.StepAction {
// LOCK!
if err := lockFile(f); err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -49,15 +49,15 @@ func (s *StepFlock) Run(state multistep.StateBag) multistep.StepAction {
// the lock.
s.fh = f
state.Put("flock_cleanup", s)
state["flock_cleanup"] = s
return multistep.ActionContinue
}
func (s *StepFlock) Cleanup(state multistep.StateBag) {
func (s *StepFlock) Cleanup(state map[string]interface{}) {
s.CleanupFunc(state)
}
func (s *StepFlock) CleanupFunc(state multistep.StateBag) error {
func (s *StepFlock) CleanupFunc(state map[string]interface{}) error {
if s.fh == nil {
return nil
}
+8 -8
View File
@@ -12,9 +12,9 @@ import (
// StepInstanceInfo verifies that this builder is running on an EC2 instance.
type StepInstanceInfo struct{}
func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
func (s *StepInstanceInfo) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
// Get our own instance ID
ui.Say("Gathering information about this EC2 instance...")
@@ -24,7 +24,7 @@ func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
err := fmt.Errorf(
"Error retrieving the ID of the instance Packer is running on.\n" +
"Please verify Packer is running on a proper AWS EC2 instance.")
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -36,22 +36,22 @@ func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
instancesResp, err := ec2conn.Instances([]string{instanceId}, ec2.NewFilter())
if err != nil {
err := fmt.Errorf("Error getting instance data: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(instancesResp.Reservations) == 0 {
err := fmt.Errorf("Error getting instance data: no instance found.")
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
instance := &instancesResp.Reservations[0].Instances[0]
state.Put("instance", instance)
state["instance"] = instance
return multistep.ActionContinue
}
func (s *StepInstanceInfo) Cleanup(multistep.StateBag) {}
func (s *StepInstanceInfo) Cleanup(map[string]interface{}) {}
+15 -15
View File
@@ -24,10 +24,10 @@ type StepMountDevice struct {
mountPath string
}
func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
func (s *StepMountDevice) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
device := state["device"].(string)
mountPath, err := config.tpl.Process(config.MountPath, &mountPathData{
Device: filepath.Base(device),
@@ -35,7 +35,7 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
if err != nil {
err := fmt.Errorf("Error preparing mount directory: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -43,7 +43,7 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
mountPath, err = filepath.Abs(mountPath)
if err != nil {
err := fmt.Errorf("Error preparing mount directory: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -52,7 +52,7 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
if err := os.MkdirAll(mountPath, 0755); err != nil {
err := fmt.Errorf("Error creating mount directory: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -65,33 +65,33 @@ func (s *StepMountDevice) Run(state multistep.StateBag) multistep.StepAction {
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"Error mounting root volume: %s\nStderr: %s", err, stderr.String())
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the mount path so we remember to unmount it later
s.mountPath = mountPath
state.Put("mount_path", s.mountPath)
state.Put("mount_device_cleanup", s)
state["mount_path"] = s.mountPath
state["mount_device_cleanup"] = s
return multistep.ActionContinue
}
func (s *StepMountDevice) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
func (s *StepMountDevice) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
func (s *StepMountDevice) CleanupFunc(state map[string]interface{}) error {
if s.mountPath == "" {
return nil
}
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
ui.Say("Unmounting the root device...")
unmountCommand := fmt.Sprintf("%s %s", config.UnmountCommand, s.mountPath)
+11 -11
View File
@@ -17,10 +17,10 @@ type StepMountExtra struct {
mounts []string
}
func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
func (s *StepMountExtra) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
mountPath := state["mount_path"].(string)
ui := state["ui"].(packer.Ui)
s.mounts = make([]string, 0, len(config.ChrootMounts))
@@ -30,7 +30,7 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
if err := os.MkdirAll(innerPath, 0755); err != nil {
err := fmt.Errorf("Error creating mount directory: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -53,7 +53,7 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"Error mounting: %s\nStderr: %s", err, stderr.String())
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -61,12 +61,12 @@ func (s *StepMountExtra) Run(state multistep.StateBag) multistep.StepAction {
s.mounts = append(s.mounts, innerPath)
}
state.Put("mount_extra_cleanup", s)
state["mount_extra_cleanup"] = s
return multistep.ActionContinue
}
func (s *StepMountExtra) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
func (s *StepMountExtra) Cleanup(state map[string]interface{}) {
ui := state["ui"].(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
@@ -74,12 +74,12 @@ func (s *StepMountExtra) Cleanup(state multistep.StateBag) {
}
}
func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error {
func (s *StepMountExtra) CleanupFunc(state map[string]interface{}) error {
if s.mounts == nil {
return nil
}
config := state.Get("config").(*Config)
config := state["config"].(*Config)
for len(s.mounts) > 0 {
var path string
lastIndex := len(s.mounts) - 1
+7 -7
View File
@@ -13,9 +13,9 @@ type StepPrepareDevice struct {
mounts []string
}
func (s *StepPrepareDevice) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
func (s *StepPrepareDevice) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
device := config.DevicePath
if device == "" {
@@ -24,7 +24,7 @@ func (s *StepPrepareDevice) Run(state multistep.StateBag) multistep.StepAction {
device, err = AvailableDevice()
if err != nil {
err := fmt.Errorf("Error finding available device: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -32,14 +32,14 @@ func (s *StepPrepareDevice) Run(state multistep.StateBag) multistep.StepAction {
if _, err := os.Stat(device); err == nil {
err := fmt.Errorf("Device is in use: %s", device)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Device: %s", device)
state.Put("device", device)
state["device"] = device
return multistep.ActionContinue
}
func (s *StepPrepareDevice) Cleanup(state multistep.StateBag) {}
func (s *StepPrepareDevice) Cleanup(state map[string]interface{}) {}
+11 -11
View File
@@ -11,12 +11,12 @@ import (
// StepRegisterAMI creates the AMI.
type StepRegisterAMI struct{}
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
image := state.Get("source_image").(*ec2.Image)
snapshotId := state.Get("snapshot_id").(string)
ui := state.Get("ui").(packer.Ui)
func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ec2conn := state["ec2"].(*ec2.EC2)
image := state["source_image"].(*ec2.Image)
snapshotId := state["snapshot_id"].(string)
ui := state["ui"].(packer.Ui)
ui.Say("Registering the AMI...")
blockDevices := make([]ec2.BlockDeviceMapping, len(image.BlockDevices))
@@ -40,8 +40,8 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
registerResp, err := ec2conn.RegisterImage(registerOpts)
if err != nil {
state.Put("error", fmt.Errorf("Error registering AMI: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error registering AMI: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
@@ -49,13 +49,13 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
ui.Say(fmt.Sprintf("AMI: %s", registerResp.ImageId))
amis := make(map[string]string)
amis[ec2conn.Region.Name] = registerResp.ImageId
state.Put("amis", amis)
state["amis"] = amis
// Wait for the image to become ready
ui.Say("Waiting for AMI to become ready...")
if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -63,4 +63,4 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}
func (s *StepRegisterAMI) Cleanup(state map[string]interface{}) {}
+12 -12
View File
@@ -17,16 +17,16 @@ type StepSnapshot struct {
snapshotId string
}
func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
volumeId := state.Get("volume_id").(string)
func (s *StepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
volumeId := state["volume_id"].(string)
ui.Say("Creating snapshot...")
createSnapResp, err := ec2conn.CreateSnapshot(volumeId, "")
if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -58,26 +58,26 @@ func (s *StepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for snapshot: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("snapshot_id", s.snapshotId)
state["snapshot_id"] = s.snapshotId
return multistep.ActionContinue
}
func (s *StepSnapshot) Cleanup(state multistep.StateBag) {
func (s *StepSnapshot) Cleanup(state map[string]interface{}) {
if s.snapshotId == "" {
return
}
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
_, cancelled := state[multistep.StateCancelled]
_, halted := state[multistep.StateHalted]
if cancelled || halted {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Removing snapshot since we cancelled or halted...")
_, err := ec2conn.DeleteSnapshots([]string{s.snapshotId})
if err != nil {
@@ -14,23 +14,23 @@ import (
// source_image *ec2.Image - the source AMI info
type StepSourceAMIInfo struct{}
func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
func (s *StepSourceAMIInfo) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Inspecting the source AMI...")
imageResp, err := ec2conn.Images([]string{config.SourceAmi}, ec2.NewFilter())
if err != nil {
err := fmt.Errorf("Error querying AMI: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(imageResp.Images) == 0 {
err := fmt.Errorf("Source AMI '%s' was not found!", config.SourceAmi)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -40,13 +40,13 @@ func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction {
// It must be EBS-backed otherwise the build won't work
if image.RootDeviceType != "ebs" {
err := fmt.Errorf("The root device of the source AMI must be EBS-backed.")
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("source_image", image)
state["source_image"] = image
return multistep.ActionContinue
}
func (s *StepSourceAMIInfo) Cleanup(multistep.StateBag) {}
func (s *StepSourceAMIInfo) Cleanup(map[string]interface{}) {}
+5 -55
View File
@@ -2,19 +2,16 @@ package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/packer/packer"
)
// AMIConfig is for common configuration related to creating AMIs.
type AMIConfig struct {
AMIName string `mapstructure:"ami_name"`
AMIDescription string `mapstructure:"ami_description"`
AMIUsers []string `mapstructure:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes"`
AMIRegions []string `mapstructure:"ami_regions"`
AMITags map[string]string `mapstructure:"tags"`
AMIName string `mapstructure:"ami_name"`
AMIDescription string `mapstructure:"ami_description"`
AMIUsers []string `mapstructure:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes"`
}
func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
@@ -45,7 +42,6 @@ func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
"ami_users": c.AMIUsers,
"ami_groups": c.AMIGroups,
"ami_product_codes": c.AMIProductCodes,
"ami_regions": c.AMIRegions,
}
for n, slice := range sliceTemplates {
@@ -63,52 +59,6 @@ func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
errs = append(errs, fmt.Errorf("ami_name must be specified"))
}
if len(c.AMIRegions) > 0 {
regionSet := make(map[string]struct{})
regions := make([]string, 0, len(c.AMIRegions))
for _, region := range c.AMIRegions {
// If we already saw the region, then don't look again
if _, ok := regionSet[region]; ok {
continue
}
// Mark that we saw the region
regionSet[region] = struct{}{}
// Verify the region is real
if _, ok := aws.Regions[region]; !ok {
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
continue
}
regions = append(regions, region)
}
c.AMIRegions = regions
}
newTags := make(map[string]string)
for k, v := range c.AMITags {
k, err := t.Process(k, nil)
if err != nil {
errs = append(errs,
fmt.Errorf("Error processing tag key %s: %s", k, err))
continue
}
v, err := t.Process(v, nil)
if err != nil {
errs = append(errs,
fmt.Errorf("Error processing tag value '%s': %s", v, err))
continue
}
newTags[k] = v
}
c.AMITags = newTags
if len(errs) > 0 {
return errs
}
+1 -25
View File
@@ -1,7 +1,6 @@
package common
import (
"reflect"
"testing"
)
@@ -11,7 +10,7 @@ func testAMIConfig() *AMIConfig {
}
}
func TestAMIConfigPrepare_name(t *testing.T) {
func TestAMIConfigPrepare_Region(t *testing.T) {
c := testAMIConfig()
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
@@ -22,26 +21,3 @@ func TestAMIConfigPrepare_name(t *testing.T) {
t.Fatal("should have error")
}
}
func TestAMIConfigPrepare_regions(t *testing.T) {
c := testAMIConfig()
c.AMIRegions = nil
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AMIRegions = []string{"foo"}
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
if err := c.Prepare(nil); err != nil {
t.Fatalf("bad: %s", err)
}
expected := []string{"us-east-1", "us-west-1"}
if !reflect.DeepEqual(c.AMIRegions, expected) {
t.Fatalf("bad: %#v", c.AMIRegions)
}
}
+3 -5
View File
@@ -2,7 +2,6 @@ package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/packer/packer"
"log"
@@ -52,10 +51,9 @@ func (a *Artifact) String() string {
func (a *Artifact) Destroy() error {
errors := make([]error, 0)
for region, imageId := range a.Amis {
log.Printf("Deregistering image ID (%s) from region (%s)", imageId, region)
regionconn := ec2.New(a.Conn.Auth, aws.Regions[region])
if _, err := regionconn.DeregisterImage(imageId); err != nil {
for _, imageId := range a.Amis {
log.Printf("Deregistering image ID: %s", imageId)
if _, err := a.Conn.DeregisterImage(imageId); err != nil {
errors = append(errors, err)
}
+2 -4
View File
@@ -13,12 +13,11 @@ type BlockDevice struct {
VolumeSize int64 `mapstructure:"volume_size"`
DeleteOnTermination bool `mapstructure:"delete_on_termination"`
IOPS int64 `mapstructure:"iops"`
NoDevice bool `mapstructure:"no_device"`
}
type BlockDevices struct {
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"`
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
AMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings,squash"`
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings,squash"`
}
func buildBlockDevices(b []BlockDevice) []ec2.BlockDeviceMapping {
@@ -33,7 +32,6 @@ func buildBlockDevices(b []BlockDevice) []ec2.BlockDeviceMapping {
VolumeSize: blockDevice.VolumeSize,
DeleteOnTermination: blockDevice.DeleteOnTermination,
IOPS: blockDevice.IOPS,
NoDevice: blockDevice.NoDevice,
})
}
return blockDevices
+17 -27
View File
@@ -26,7 +26,7 @@ type StateChangeConf struct {
Conn *ec2.EC2
Pending []string
Refresh StateRefreshFunc
StepState multistep.StateBag
StepState map[string]interface{}
Target string
}
@@ -40,12 +40,6 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
return nil, "", err
}
if len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
// Sometimes AWS just has consistency issues and doesn't see
// our instance yet. Return an empty state.
return nil, "", nil
}
i = &resp.Reservations[0].Instances[0]
return i, i.State.Name, nil
}
@@ -63,31 +57,27 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
return
}
// Check states only if we were able to refresh to an instance
// that exists.
if i != nil {
if currentState == conf.Target {
return
}
if currentState == conf.Target {
return
}
if conf.StepState != nil {
if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("interrupted")
}
if conf.StepState != nil {
if _, ok := conf.StepState[multistep.StateCancelled]; ok {
return nil, errors.New("interrupted")
}
}
found := false
for _, allowed := range conf.Pending {
if currentState == allowed {
found = true
break
}
found := false
for _, allowed := range conf.Pending {
if currentState == allowed {
found = true
break
}
}
if !found {
fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
return
}
if !found {
fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
return
}
time.Sleep(2 * time.Second)
+19 -25
View File
@@ -11,18 +11,17 @@ import (
// RunConfig contains configuration for running an instance from a source
// AMI and details on how to access that launched image.
type RunConfig struct {
SourceAmi string `mapstructure:"source_ami"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
SecurityGroupId string `mapstructure:"security_group_id"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
VpcId string `mapstructure:"vpc_id"`
SourceAmi string `mapstructure:"source_ami"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceType string `mapstructure:"instance_type"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
SecurityGroupId string `mapstructure:"security_group_id"`
SubnetId string `mapstructure:"subnet_id"`
VpcId string `mapstructure:"vpc_id"`
// Unexported fields that are calculated from others
sshTimeout time.Duration
@@ -46,10 +45,6 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
c.RawSSHTimeout = "1m"
}
if c.TemporaryKeyPairName == "" {
c.TemporaryKeyPairName = "packer {{uuid}}"
}
// Validation
var err error
errs := make([]error, 0)
@@ -74,15 +69,14 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
}
templates := map[string]*string{
"iam_instance_profile": &c.IamInstanceProfile,
"instance_type": &c.InstanceType,
"ssh_timeout": &c.RawSSHTimeout,
"security_group_id": &c.SecurityGroupId,
"ssh_username": &c.SSHUsername,
"source_ami": &c.SourceAmi,
"subnet_id": &c.SubnetId,
"temporary_key_pair_name": &c.TemporaryKeyPairName,
"vpc_id": &c.VpcId,
"iam_instance_profile": &c.IamInstanceProfile,
"instance_type": &c.InstanceType,
"ssh_timeout": &c.RawSSHTimeout,
"security_group_id": &c.SecurityGroupId,
"ssh_username": &c.SSHUsername,
"source_ami": &c.SourceAmi,
"subnet_id": &c.SubnetId,
"vpc_id": &c.VpcId,
}
for n, ptr := range templates {
-12
View File
@@ -126,15 +126,3 @@ func TestRunConfigPrepare_UserDataFile(t *testing.T) {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
c := testConfig()
c.TemporaryKeyPairName = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.TemporaryKeyPairName == "" {
t.Fatal("keypair empty")
}
}
+7 -8
View File
@@ -5,18 +5,17 @@ import (
"errors"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
"time"
)
// SSHAddress returns a function that can be given to the SSH communicator
// for determining the SSH address based on the instance DNS name.
func SSHAddress(e *ec2.EC2, port int) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
func SSHAddress(e *ec2.EC2, port int) func(map[string]interface{}) (string, error) {
return func(state map[string]interface{}) (string, error) {
for j := 0; j < 2; j++ {
var host string
i := state.Get("instance").(*ec2.Instance)
i := state["instance"].(*ec2.Instance)
if i.DNSName != "" {
host = i.DNSName
} else if i.VpcId != "" {
@@ -36,7 +35,7 @@ func SSHAddress(e *ec2.EC2, port int) func(multistep.StateBag) (string, error) {
return "", fmt.Errorf("instance not found: %s", i.InstanceId)
}
state.Put("instance", &r.Reservations[0].Instances[0])
state["instance"] = &r.Reservations[0].Instances[0]
time.Sleep(1 * time.Second)
}
@@ -47,9 +46,9 @@ func SSHAddress(e *ec2.EC2, port int) func(multistep.StateBag) (string, error) {
// SSHConfig returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the generated
// private key.
func SSHConfig(username string) func(multistep.StateBag) (*gossh.ClientConfig, error) {
return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
privateKey := state.Get("privateKey").(string)
func SSHConfig(username string) func(map[string]interface{}) (*gossh.ClientConfig, error) {
return func(state map[string]interface{}) (*gossh.ClientConfig, error) {
privateKey := state["privateKey"].(string)
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(privateKey); err != nil {
@@ -1,60 +0,0 @@
package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepAMIRegionCopy struct {
Regions []string
}
func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
ami := amis[ec2conn.Region.Name]
if len(s.Regions) == 0 {
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Copying AMI (%s) to other regions...", ami))
for _, region := range s.Regions {
ui.Message(fmt.Sprintf("Copying to: %s", region))
// Connect to the region where the AMI will be copied to
regionconn := ec2.New(ec2conn.Auth, aws.Regions[region])
resp, err := regionconn.CopyImage(&ec2.CopyImage{
SourceRegion: ec2conn.Region.Name,
SourceImageId: ami,
})
if err != nil {
err := fmt.Errorf("Error Copying AMI (%s) to region (%s): %s", ami, region, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Waiting for AMI (%s) in region (%s) to become ready...", resp.ImageId, region))
if err := WaitForAMI(regionconn, resp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s", resp.ImageId, region, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
amis[region] = resp.ImageId
}
state.Put("amis", amis)
return multistep.ActionContinue
}
func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) {
// No cleanup...
}
+18 -21
View File
@@ -2,7 +2,6 @@ package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
@@ -12,35 +11,33 @@ type StepCreateTags struct {
Tags map[string]string
}
func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
func (s *StepCreateTags) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
amis := state["amis"].(map[string]string)
ami := amis[ec2conn.Region.Name]
if len(s.Tags) > 0 {
for region, ami := range amis {
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", ami))
var ec2Tags []ec2.Tag
for key, value := range s.Tags {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
ec2Tags = append(ec2Tags, ec2.Tag{key, value})
}
var ec2Tags []ec2.Tag
for key, value := range s.Tags {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
ec2Tags = append(ec2Tags, ec2.Tag{key, value})
}
regionconn := ec2.New(ec2conn.Auth, aws.Regions[region])
_, err := regionconn.CreateTags([]string{ami}, ec2Tags)
if err != nil {
err := fmt.Errorf("Error adding tags to AMI (%s): %s", ami, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
_, err := ec2conn.CreateTags([]string{ami}, ec2Tags)
if err != nil {
err := fmt.Errorf("Error adding tags to AMI (%s): %s", ami, err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
func (s *StepCreateTags) Cleanup(state map[string]interface{}) {
// No cleanup...
}
+17 -44
View File
@@ -1,77 +1,50 @@
package common
import (
"cgl.tideland.biz/identifier"
"encoding/hex"
"fmt"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"os"
"runtime"
"log"
)
type StepKeyPair struct {
Debug bool
DebugKeyPath string
KeyPairName string
keyName string
}
func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
func (s *StepKeyPair) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.KeyPairName))
keyResp, err := ec2conn.CreateKeyPair(s.KeyPairName)
ui.Say("Creating temporary keypair for this instance...")
keyName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw()))
log.Printf("temporary keypair name: %s", keyName)
keyResp, err := ec2conn.CreateKeyPair(keyName)
if err != nil {
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
state["error"] = fmt.Errorf("Error creating temporary keypair: %s", err)
return multistep.ActionHalt
}
// Set the keyname so we know to delete it later
s.keyName = s.KeyPairName
s.keyName = keyName
// Set some state data for use in future steps
state.Put("keyPair", s.keyName)
state.Put("privateKey", keyResp.KeyMaterial)
// If we're in debug mode, output the private key to the working
// directory.
if s.Debug {
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
f, err := os.Create(s.DebugKeyPath)
if err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
defer f.Close()
// Write the key out
if _, err := f.Write([]byte(keyResp.KeyMaterial)); err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
// Chmod it so that it is SSH ready
if runtime.GOOS != "windows" {
if err := f.Chmod(0600); err != nil {
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
return multistep.ActionHalt
}
}
}
state["keyPair"] = keyName
state["privateKey"] = keyResp.KeyMaterial
return multistep.ActionContinue
}
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
func (s *StepKeyPair) Cleanup(state map[string]interface{}) {
// If no key name is set, then we never created it, so just return
if s.keyName == "" {
return
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Deleting temporary keypair...")
_, err := ec2conn.DeleteKeyPair(s.keyName)
@@ -2,7 +2,6 @@ package common
import (
"fmt"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
@@ -15,10 +14,11 @@ type StepModifyAMIAttributes struct {
Description string
}
func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
func (s *StepModifyAMIAttributes) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
amis := state["amis"].(map[string]string)
ami := amis[ec2conn.Region.Name]
// Determine if there is any work to do.
valid := false
@@ -59,24 +59,21 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc
}
}
for region, ami := range amis {
ui.Say(fmt.Sprintf("Modifying attributes on AMI (%s)...", ami))
regionconn := ec2.New(ec2conn.Auth, aws.Regions[region])
for name, opts := range options {
ui.Message(fmt.Sprintf("Modifying: %s", name))
_, err := regionconn.ModifyImageAttribute(ami, opts)
if err != nil {
err := fmt.Errorf("Error modify AMI attributes: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Modifying AMI attributes...")
for name, opts := range options {
ui.Message(fmt.Sprintf("Modifying: %s", name))
_, err := ec2conn.ModifyImageAttribute(ami, opts)
if err != nil {
err := fmt.Errorf("Error modify AMI attributes: %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepModifyAMIAttributes) Cleanup(state multistep.StateBag) {
func (s *StepModifyAMIAttributes) Cleanup(state map[string]interface{}) {
// No cleanup...
}
@@ -10,7 +10,6 @@ import (
)
type StepRunSourceInstance struct {
Debug bool
ExpectedRootDevice string
InstanceType string
UserData string
@@ -23,17 +22,17 @@ type StepRunSourceInstance struct {
instance *ec2.Instance
}
func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
keyName := state.Get("keyPair").(string)
securityGroupId := state.Get("securityGroupId").(string)
ui := state.Get("ui").(packer.Ui)
func (s *StepRunSourceInstance) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
keyName := state["keyPair"].(string)
securityGroupId := state["securityGroupId"].(string)
ui := state["ui"].(packer.Ui)
userData := s.UserData
if s.UserDataFile != "" {
contents, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
state["error"] = fmt.Errorf("Problem reading user data file: %s", err)
return multistep.ActionHalt
}
@@ -56,27 +55,27 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
ui.Say("Launching a source AWS instance...")
imageResp, err := ec2conn.Images([]string{s.SourceAMI}, ec2.NewFilter())
if err != nil {
state.Put("error", fmt.Errorf("There was a problem with the source AMI: %s", err))
state["error"] = fmt.Errorf("There was a problem with the source AMI: %s", err)
return multistep.ActionHalt
}
if len(imageResp.Images) != 1 {
state.Put("error", fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI))
state["error"] = fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI)
return multistep.ActionHalt
}
if s.ExpectedRootDevice != "" && imageResp.Images[0].RootDeviceType != s.ExpectedRootDevice {
state.Put("error", fmt.Errorf(
state["error"] = fmt.Errorf(
"The provided source AMI has an invalid root device type.\n"+
"Expected '%s', got '%s'.",
s.ExpectedRootDevice, imageResp.Images[0].RootDeviceType))
s.ExpectedRootDevice, imageResp.Images[0].RootDeviceType)
return multistep.ActionHalt
}
runResp, err := ec2conn.RunInstances(runOpts)
if err != nil {
err := fmt.Errorf("Error launching source instance: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -95,35 +94,24 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
latestInstance, err := WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", s.instance.InstanceId, err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
s.instance = latestInstance.(*ec2.Instance)
if s.Debug {
if s.instance.DNSName != "" {
ui.Message(fmt.Sprintf("Public DNS: %s", s.instance.DNSName))
}
if s.instance.PrivateIpAddress != "" {
ui.Message(fmt.Sprintf("Private IP: %s", s.instance.PrivateIpAddress))
}
}
state.Put("instance", s.instance)
state["instance"] = s.instance
return multistep.ActionContinue
}
func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) {
func (s *StepRunSourceInstance) Cleanup(state map[string]interface{}) {
if s.instance == nil {
return
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Terminating the source AWS instance...")
if _, err := ec2conn.TerminateInstances([]string{s.instance.InstanceId}); err != nil {
+9 -9
View File
@@ -19,13 +19,13 @@ type StepSecurityGroup struct {
createdGroupId string
}
func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
func (s *StepSecurityGroup) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
if s.SecurityGroupId != "" {
log.Printf("Using specified security group: %s", s.SecurityGroupId)
state.Put("securityGroupId", s.SecurityGroupId)
state["securityGroupId"] = s.SecurityGroupId
return multistep.ActionContinue
}
@@ -64,24 +64,24 @@ func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
ui.Say("Authorizing SSH access on the temporary security group...")
if _, err := ec2conn.AuthorizeSecurityGroup(groupResp.SecurityGroup, perms); err != nil {
err := fmt.Errorf("Error creating temporary security group: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set some state data for use in future steps
state.Put("securityGroupId", s.createdGroupId)
state["securityGroupId"] = s.createdGroupId
return multistep.ActionContinue
}
func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) {
func (s *StepSecurityGroup) Cleanup(state map[string]interface{}) {
if s.createdGroupId == "" {
return
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ec2conn := state["ec2"].(*ec2.EC2)
ui := state["ui"].(packer.Ui)
ui.Say("Deleting temporary security group...")
-38
View File
@@ -1,38 +0,0 @@
package common
import (
"bytes"
"text/template"
)
func isalphanumeric(b byte) bool {
if '0' <= b && b <= '9' {
return true
}
if 'a' <= b && b <= 'z' {
return true
}
if 'A' <= b && b <= 'Z' {
return true
}
return false
}
// Clean up AMI name by replacing invalid characters with "-"
func templateCleanAMIName(s string) string {
allowed := []byte{'(', ')', ',', '/', '-', '_'}
b := []byte(s)
newb := make([]byte, len(b))
for i, c := range b {
if isalphanumeric(c) || bytes.IndexByte(allowed, c) != -1 {
newb[i] = c
} else {
newb[i] = '-'
}
}
return string(newb[:])
}
var TemplateFuncs = template.FuncMap{
"clean_ami_name": templateCleanAMIName,
}
@@ -1,16 +0,0 @@
package common
import (
"testing"
)
func TestAMITemplatePrepare_clean(t *testing.T) {
origName := "AMZamz09(),/-_:&^$%"
expected := "AMZamz09(),/-_-----"
name := templateCleanAMIName(origName)
if name != expected {
t.Fatalf("template names do not match: expected %s got %s\n", expected, name)
}
}
+35 -21
View File
@@ -25,6 +25,9 @@ type config struct {
awscommon.BlockDevices `mapstructure:",squash"`
awscommon.RunConfig `mapstructure:",squash"`
// Tags for the AMI
Tags map[string]string
tpl *packer.ConfigTemplate
}
@@ -44,7 +47,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
b.config.tpl.Funcs(awscommon.TemplateFuncs)
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
@@ -52,6 +54,28 @@ func (b *Builder) Prepare(raws ...interface{}) error {
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
// Accumulate any errors
newTags := make(map[string]string)
for k, v := range b.config.Tags {
k, err = b.config.tpl.Process(k, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing tag key %s: %s", k, err))
continue
}
v, err = b.config.tpl.Process(v, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing tag value '%s': %s", v, err))
continue
}
newTags[k] = v
}
b.config.Tags = newTags
if errs != nil && len(errs.Errors) > 0 {
return errs
}
@@ -74,26 +98,21 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn := ec2.New(auth, region)
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
state := make(map[string]interface{})
state["config"] = b.config
state["ec2"] = ec2conn
state["hook"] = hook
state["ui"] = ui
// Build the steps
steps := []multistep.Step{
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
KeyPairName: b.config.TemporaryKeyPairName,
},
&awscommon.StepKeyPair{},
&awscommon.StepSecurityGroup{
SecurityGroupId: b.config.SecurityGroupId,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
@@ -111,17 +130,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&common.StepProvision{},
&stepStopInstance{},
&stepCreateAMI{},
&awscommon.StepAMIRegionCopy{
Regions: b.config.AMIRegions,
},
&awscommon.StepCreateTags{Tags: b.config.Tags},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
},
}
// Run!
@@ -137,18 +151,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
if rawErr, ok := state["error"]; ok {
return nil, rawErr.(error)
}
// If there are no AMIs, then just return
if _, ok := state.GetOk("amis"); !ok {
if _, ok := state["amis"]; !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &awscommon.Artifact{
Amis: state.Get("amis").(map[string]string),
Amis: state["amis"].(map[string]string),
BuilderIdValue: BuilderId,
Conn: ec2conn,
}
+9 -9
View File
@@ -10,11 +10,11 @@ import (
type stepCreateAMI struct{}
func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config)
ec2conn := state.Get("ec2").(*ec2.EC2)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
func (s *stepCreateAMI) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(config)
ec2conn := state["ec2"].(*ec2.EC2)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
// Create the image
ui.Say(fmt.Sprintf("Creating the AMI: %s", config.AMIName))
@@ -27,7 +27,7 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
createResp, err := ec2conn.CreateImage(createOpts)
if err != nil {
err := fmt.Errorf("Error creating AMI: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -36,13 +36,13 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
ui.Say(fmt.Sprintf("AMI: %s", createResp.ImageId))
amis := make(map[string]string)
amis[ec2conn.Region.Name] = createResp.ImageId
state.Put("amis", amis)
state["amis"] = amis
// Wait for the image to become ready
ui.Say("Waiting for AMI to become ready...")
if err := awscommon.WaitForAMI(ec2conn, createResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -50,6 +50,6 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepCreateAMI) Cleanup(multistep.StateBag) {
func (s *stepCreateAMI) Cleanup(map[string]interface{}) {
// No cleanup...
}
+7 -7
View File
@@ -10,17 +10,17 @@ import (
type stepStopInstance struct{}
func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
func (s *stepStopInstance) Run(state map[string]interface{}) multistep.StepAction {
ec2conn := state["ec2"].(*ec2.EC2)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
// Stop the instance so we can create an AMI from it
ui.Say("Stopping the source instance...")
_, err := ec2conn.StopInstances(instance.InstanceId)
if err != nil {
err := fmt.Errorf("Error stopping instance: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -37,7 +37,7 @@ func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction {
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for instance to stop: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -45,6 +45,6 @@ func (s *stepStopInstance) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepStopInstance) Cleanup(multistep.StateBag) {
func (s *stepStopInstance) Cleanup(map[string]interface{}) {
// No cleanup...
}
+11 -21
View File
@@ -33,6 +33,7 @@ type Config struct {
BundleUploadCommand string `mapstructure:"bundle_upload_command"`
BundleVolCommand string `mapstructure:"bundle_vol_command"`
S3Bucket string `mapstructure:"s3_bucket"`
Tags map[string]string
X509CertPath string `mapstructure:"x509_cert_path"`
X509KeyPath string `mapstructure:"x509_key_path"`
X509UploadPath string `mapstructure:"x509_upload_path"`
@@ -56,7 +57,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
b.config.tpl.Funcs(awscommon.TemplateFuncs)
if b.config.BundleDestination == "" {
b.config.BundleDestination = "/tmp"
@@ -177,26 +177,21 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ec2conn := ec2.New(auth, region)
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
state := make(map[string]interface{})
state["config"] = &b.config
state["ec2"] = ec2conn
state["hook"] = hook
state["ui"] = ui
// Build the steps
steps := []multistep.Step{
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
KeyPairName: b.config.TemporaryKeyPairName,
},
&awscommon.StepKeyPair{},
&awscommon.StepSecurityGroup{
SecurityGroupId: b.config.SecurityGroupId,
SSHPort: b.config.SSHPort,
VpcId: b.config.VpcId,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "instance-store",
InstanceType: b.config.InstanceType,
IamInstanceProfile: b.config.IamInstanceProfile,
@@ -216,18 +211,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepBundleVolume{},
&StepUploadBundle{},
&StepRegisterAMI{},
&awscommon.StepAMIRegionCopy{
Regions: b.config.AMIRegions,
},
&awscommon.StepCreateTags{Tags: b.config.Tags},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
},
}
// Run!
@@ -243,18 +233,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
if rawErr, ok := state["error"]; ok {
return nil, rawErr.(error)
}
// If there are no AMIs, then just return
if _, ok := state.GetOk("amis"); !ok {
if _, ok := state["amis"]; !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &awscommon.Artifact{
Amis: state.Get("amis").(map[string]string),
Amis: state["amis"].(map[string]string),
BuilderIdValue: BuilderId,
Conn: ec2conn,
}
+38 -19
View File
@@ -19,13 +19,32 @@ type bundleCmdData struct {
type StepBundleVolume struct{}
func (s *StepBundleVolume) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*Config)
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
x509RemoteCertPath := state.Get("x509RemoteCertPath").(string)
x509RemoteKeyPath := state.Get("x509RemoteKeyPath").(string)
func (s *StepBundleVolume) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*Config)
instance := state["instance"].(*ec2.Instance)
ui := state["ui"].(packer.Ui)
x509RemoteCertPath := state["x509RemoteCertPath"].(string)
x509RemoteKeyPath := state["x509RemoteKeyPath"].(string)
// Verify the AMI tools are available
ui.Say("Checking for EC2 AMI tools...")
cmd := &packer.RemoteCmd{Command: "ec2-ami-tools-version"}
if err := comm.Start(cmd); err != nil {
state["error"] = fmt.Errorf("Error checking for AMI tools: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
cmd.Wait()
if cmd.ExitStatus != 0 {
state["error"] = fmt.Errorf(
"The EC2 AMI tools could not be detected. These must be manually\n" +
"via a provisioner or some other means and are required for Packer\n" +
"to create an instance-store AMI.")
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
// Bundle the volume
var err error
@@ -40,35 +59,35 @@ func (s *StepBundleVolume) Run(state multistep.StateBag) multistep.StepAction {
})
if err != nil {
err := fmt.Errorf("Error processing bundle volume command: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Bundling the volume...")
cmd := new(packer.RemoteCmd)
cmd = new(packer.RemoteCmd)
cmd.Command = config.BundleVolCommand
if err := cmd.StartWithUi(comm, ui); err != nil {
state.Put("error", fmt.Errorf("Error bundling volume: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error bundling volume: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
if cmd.ExitStatus != 0 {
state.Put("error", fmt.Errorf(
"Volume bundling failed. Please see the output above for more\n"+
"details on what went wrong."))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf(
"Volume bundling failed. Please see the output above for more\n" +
"details on what went wrong.")
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
// Store the manifest path
manifestName := config.BundlePrefix + ".manifest.xml"
state.Put("manifest_name", manifestName)
state.Put("manifest_path", fmt.Sprintf(
"%s/%s", config.BundleDestination, manifestName))
state["manifest_name"] = manifestName
state["manifest_path"] = fmt.Sprintf(
"%s/%s", config.BundleDestination, manifestName)
return multistep.ActionContinue
}
func (s *StepBundleVolume) Cleanup(multistep.StateBag) {}
func (s *StepBundleVolume) Cleanup(map[string]interface{}) {}
+10 -10
View File
@@ -10,11 +10,11 @@ import (
type StepRegisterAMI struct{}
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
manifestPath := state.Get("remote_manifest_path").(string)
ui := state.Get("ui").(packer.Ui)
func (s *StepRegisterAMI) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*Config)
ec2conn := state["ec2"].(*ec2.EC2)
manifestPath := state["remote_manifest_path"].(string)
ui := state["ui"].(packer.Ui)
ui.Say("Registering the AMI...")
registerOpts := &ec2.RegisterImage{
@@ -25,8 +25,8 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
registerResp, err := ec2conn.RegisterImage(registerOpts)
if err != nil {
state.Put("error", fmt.Errorf("Error registering AMI: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error registering AMI: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
@@ -34,13 +34,13 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
ui.Say(fmt.Sprintf("AMI: %s", registerResp.ImageId))
amis := make(map[string]string)
amis[ec2conn.Region.Name] = registerResp.ImageId
state.Put("amis", amis)
state["amis"] = amis
// Wait for the image to become ready
ui.Say("Waiting for AMI to become ready...")
if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -48,4 +48,4 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *StepRegisterAMI) Cleanup(multistep.StateBag) {}
func (s *StepRegisterAMI) Cleanup(map[string]interface{}) {}
+16 -16
View File
@@ -16,12 +16,12 @@ type uploadCmdData struct {
type StepUploadBundle struct{}
func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*Config)
manifestName := state.Get("manifest_name").(string)
manifestPath := state.Get("manifest_path").(string)
ui := state.Get("ui").(packer.Ui)
func (s *StepUploadBundle) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*Config)
manifestName := state["manifest_name"].(string)
manifestPath := state["manifest_path"].(string)
ui := state["ui"].(packer.Ui)
var err error
config.BundleUploadCommand, err = config.tpl.Process(config.BundleUploadCommand, uploadCmdData{
@@ -33,7 +33,7 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
})
if err != nil {
err := fmt.Errorf("Error processing bundle upload command: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -41,23 +41,23 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
ui.Say("Uploading the bundle...")
cmd := &packer.RemoteCmd{Command: config.BundleUploadCommand}
if err := cmd.StartWithUi(comm, ui); err != nil {
state.Put("error", fmt.Errorf("Error uploading volume: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error uploading volume: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
if cmd.ExitStatus != 0 {
state.Put("error", fmt.Errorf(
"Bundle upload failed. Please see the output above for more\n"+
"details on what went wrong."))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf(
"Bundle upload failed. Please see the output above for more\n" +
"details on what went wrong.")
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
state.Put("remote_manifest_path", fmt.Sprintf(
"%s/%s", config.S3Bucket, manifestName))
state["remote_manifest_path"] = fmt.Sprintf(
"%s/%s", config.S3Bucket, manifestName)
return multistep.ActionContinue
}
func (s *StepUploadBundle) Cleanup(state multistep.StateBag) {}
func (s *StepUploadBundle) Cleanup(state map[string]interface{}) {}
@@ -9,34 +9,34 @@ import (
type StepUploadX509Cert struct{}
func (s *StepUploadX509Cert) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
func (s *StepUploadX509Cert) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*Config)
ui := state["ui"].(packer.Ui)
x509RemoteCertPath := config.X509UploadPath + "/cert.pem"
x509RemoteKeyPath := config.X509UploadPath + "/key.pem"
ui.Say("Uploading X509 Certificate...")
if err := s.uploadSingle(comm, x509RemoteCertPath, config.X509CertPath); err != nil {
state.Put("error", fmt.Errorf("Error uploading X509 cert: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error uploading X509 cert: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
if err := s.uploadSingle(comm, x509RemoteKeyPath, config.X509KeyPath); err != nil {
state.Put("error", fmt.Errorf("Error uploading X509 cert: %s", err))
ui.Error(state.Get("error").(error).Error())
state["error"] = fmt.Errorf("Error uploading X509 cert: %s", err)
ui.Error(state["error"].(error).Error())
return multistep.ActionHalt
}
state.Put("x509RemoteCertPath", x509RemoteCertPath)
state.Put("x509RemoteKeyPath", x509RemoteKeyPath)
state["x509RemoteCertPath"] = x509RemoteCertPath
state["x509RemoteKeyPath"] = x509RemoteKeyPath
return multistep.ActionContinue
}
func (s *StepUploadX509Cert) Cleanup(multistep.StateBag) {}
func (s *StepUploadX509Cert) Cleanup(map[string]interface{}) {}
func (s *StepUploadX509Cert) uploadSingle(comm packer.Communicator, dst, src string) error {
f, err := os.Open(src)
+36 -61
View File
@@ -14,7 +14,6 @@ import (
"net/http"
"net/url"
"strings"
"time"
)
const DIGITALOCEAN_API_URL = "https://api.digitalocean.com"
@@ -44,11 +43,7 @@ type DigitalOceanClient struct {
// Creates a new client for communicating with DO
func (d DigitalOceanClient) New(client string, key string) *DigitalOceanClient {
c := &DigitalOceanClient{
client: &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
},
client: http.DefaultClient,
BaseURL: DIGITALOCEAN_API_URL,
ClientID: client,
APIKey: key,
@@ -116,15 +111,6 @@ func (d DigitalOceanClient) PowerOffDroplet(id uint) error {
return err
}
// Shutsdown a droplet. This is a "soft" shutdown.
func (d DigitalOceanClient) ShutdownDroplet(id uint) error {
path := fmt.Sprintf("droplets/%v/shutdown", id)
_, err := NewRequest(d, path, url.Values{})
return err
}
// Creates a snaphot of a droplet by it's ID
func (d DigitalOceanClient) CreateSnapshot(id uint, name string) error {
path := fmt.Sprintf("droplets/%v/snapshot", id)
@@ -192,57 +178,46 @@ func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[strin
url := fmt.Sprintf("%s/%s?%s", DIGITALOCEAN_API_URL, path, params.Encode())
var decodedResponse map[string]interface{}
// Do some basic scrubbing so sensitive information doesn't appear in logs
scrubbedUrl := strings.Replace(url, d.ClientID, "CLIENT_ID", -1)
scrubbedUrl = strings.Replace(scrubbedUrl, d.APIKey, "API_KEY", -1)
log.Printf("sending new request to digitalocean: %s", scrubbedUrl)
var lastErr error
for attempts := 1; attempts < 10; attempts++ {
resp, err := client.Get(url)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
log.Printf("response from digitalocean: %s", body)
var decodedResponse map[string]interface{}
err = json.Unmarshal(body, &decodedResponse)
if err != nil {
err = errors.New(fmt.Sprintf("Failed to decode JSON response (HTTP %v) from DigitalOcean: %s",
resp.StatusCode, body))
return decodedResponse, err
}
// Check for errors sent by digitalocean
status := decodedResponse["status"].(string)
if status == "OK" {
return decodedResponse, nil
}
if status == "ERROR" {
status = decodedResponse["error_message"].(string)
}
lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s",
resp.StatusCode, status))
log.Println(lastErr)
if strings.Contains(status, "a pending event") {
// Retry, DigitalOcean sends these dumb "pending event"
// errors all the time.
time.Sleep(5 * time.Second)
continue
}
// Some other kind of error. Just return.
return decodedResponse, lastErr
resp, err := client.Get(url)
if err != nil {
return decodedResponse, err
}
return nil, lastErr
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return decodedResponse, err
}
log.Printf("response from digitalocean: %s", body)
err = json.Unmarshal(body, &decodedResponse)
// 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
}
// 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 response (HTTP %v) from DigitalOcean: %s", resp.StatusCode, status))
return decodedResponse, err
}
return decodedResponse, nil
}
+25 -10
View File
@@ -34,11 +34,13 @@ type config struct {
SSHPort uint `mapstructure:"ssh_port"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
RawEventDelay string `mapstructure:"event_delay"`
RawStateTimeout string `mapstructure:"state_timeout"`
// These are unexported since they're set by other fields
// being set.
sshTimeout time.Duration
eventDelay time.Duration
stateTimeout time.Duration
tpl *packer.ConfigTemplate
@@ -111,6 +113,12 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.RawSSHTimeout = "1m"
}
if b.config.RawEventDelay == "" {
// Default to 5 second delays after creating events
// to allow DO to process
b.config.RawEventDelay = "5s"
}
if b.config.RawStateTimeout == "" {
// Default to 6 minute timeouts waiting for
// desired state. i.e waiting for droplet to become active
@@ -123,6 +131,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
"snapshot_name": &b.config.SnapshotName,
"ssh_username": &b.config.SSHUsername,
"ssh_timeout": &b.config.RawSSHTimeout,
"event_delay": &b.config.RawEventDelay,
"state_timeout": &b.config.RawStateTimeout,
}
@@ -153,6 +162,13 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
b.config.sshTimeout = sshTimeout
eventDelay, err := time.ParseDuration(b.config.RawEventDelay)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing event_delay: %s", err))
}
b.config.eventDelay = eventDelay
stateTimeout, err := time.ParseDuration(b.config.RawStateTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
@@ -173,11 +189,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
client := DigitalOceanClient{}.New(b.config.ClientID, b.config.APIKey)
// Set up the state
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("client", client)
state.Put("hook", hook)
state.Put("ui", ui)
state := make(map[string]interface{})
state["config"] = b.config
state["client"] = client
state["hook"] = hook
state["ui"] = ui
// Build the steps
steps := []multistep.Step{
@@ -190,7 +206,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SSHWaitTimeout: 5 * time.Minute,
},
new(common.StepProvision),
new(stepShutdown),
new(stepPowerOff),
new(stepSnapshot),
}
@@ -208,18 +223,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
if rawErr, ok := state["error"]; ok {
return nil, rawErr.(error)
}
if _, ok := state.GetOk("snapshot_name"); !ok {
if _, ok := state["snapshot_name"]; !ok {
log.Println("Failed to find snapshot_name in state. Bug?")
return nil, nil
}
artifact := &Artifact{
snapshotName: state.Get("snapshot_name").(string),
snapshotId: state.Get("snapshot_image_id").(uint),
snapshotName: state["snapshot_name"].(string),
snapshotId: state["snapshot_image_id"].(uint),
client: client,
}
+32
View File
@@ -258,6 +258,38 @@ func TestBuilderPrepare_SSHTimeout(t *testing.T) {
}
func TestBuilderPrepare_EventDelay(t *testing.T) {
var b Builder
config := testConfig()
// Test default
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.RawEventDelay != "5s" {
t.Errorf("invalid: %d", b.config.RawEventDelay)
}
// Test set
config["event_delay"] = "10s"
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["event_delay"] = "tubes"
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_StateTimeout(t *testing.T) {
var b Builder
config := testConfig()
+6 -7
View File
@@ -3,19 +3,18 @@ package digitalocean
import (
gossh "code.google.com/p/go.crypto/ssh"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
)
func sshAddress(state multistep.StateBag) (string, error) {
config := state.Get("config").(config)
ipAddress := state.Get("droplet_ip").(string)
func sshAddress(state map[string]interface{}) (string, error) {
config := state["config"].(config)
ipAddress := state["droplet_ip"].(string)
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil
}
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(config)
privateKey := state.Get("privateKey").(string)
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
config := state["config"].(config)
privateKey := state["privateKey"].(string)
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(privateKey); err != nil {
+24 -15
View File
@@ -6,17 +6,19 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
type stepCreateDroplet struct {
dropletId uint
}
func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
sshKeyId := state.Get("ssh_key_id").(uint)
func (s *stepCreateDroplet) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
sshKeyId := state["ssh_key_id"].(uint)
ui.Say("Creating droplet...")
@@ -27,7 +29,7 @@ func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
dropletId, err := client.CreateDroplet(name, c.SizeID, c.ImageID, c.RegionID, sshKeyId)
if err != nil {
err := fmt.Errorf("Error creating droplet: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -36,29 +38,36 @@ func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
s.dropletId = dropletId
// Store the droplet id for later
state.Put("droplet_id", dropletId)
state["droplet_id"] = dropletId
return multistep.ActionContinue
}
func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) {
func (s *stepCreateDroplet) Cleanup(state map[string]interface{}) {
// If the dropletid isn't there, we probably never created it
if s.dropletId == 0 {
return
}
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
// Destroy the droplet we just created
ui.Say("Destroying droplet...")
err := client.DestroyDroplet(s.dropletId)
if err != nil {
curlstr := fmt.Sprintf("curl '%v/droplets/%v/destroy?client_id=%v&api_key=%v'",
DIGITALOCEAN_API_URL, s.dropletId, c.ClientID, c.APIKey)
// Sleep arbitrarily before sending destroy request
// Otherwise we get "pending event" errors, even though there isn't
// one.
log.Printf("Sleeping for %v, event_delay", c.RawEventDelay)
time.Sleep(c.eventDelay)
err := client.DestroyDroplet(s.dropletId)
curlstr := fmt.Sprintf("curl '%v/droplets/%v/destroy?client_id=%v&api_key=%v'",
DIGITALOCEAN_API_URL, s.dropletId, c.ClientID, c.APIKey)
if err != nil {
ui.Error(fmt.Sprintf(
"Error destroying droplet. Please destroy it manually: %v", curlstr))
}
+10 -10
View File
@@ -18,9 +18,9 @@ type stepCreateSSHKey struct {
keyId uint
}
func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
func (s *stepCreateSSHKey) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
ui.Say("Creating temporary ssh key for droplet...")
@@ -35,7 +35,7 @@ func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
}
// Set the private key in the statebag for later
state.Put("privateKey", string(pem.EncodeToMemory(&priv_blk)))
state["privateKey"] = string(pem.EncodeToMemory(&priv_blk))
// Marshal the public key into SSH compatible format
pub := priv.PublicKey
@@ -48,7 +48,7 @@ func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
keyId, err := client.CreateKey(name, pub_sshformat)
if err != nil {
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -59,20 +59,20 @@ func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
log.Printf("temporary ssh key name: %s", name)
// Remember some state for the future
state.Put("ssh_key_id", keyId)
state["ssh_key_id"] = keyId
return multistep.ActionContinue
}
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
func (s *stepCreateSSHKey) Cleanup(state map[string]interface{}) {
// If no key name is set, then we never created it, so just return
if s.keyId == 0 {
return
}
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
ui.Say("Deleting temporary ssh key...")
err := client.DestroyKey(s.keyId)
+10 -10
View File
@@ -8,18 +8,18 @@ import (
type stepDropletInfo struct{}
func (s *stepDropletInfo) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
dropletId := state.Get("droplet_id").(uint)
func (s *stepDropletInfo) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
dropletId := state["droplet_id"].(uint)
ui.Say("Waiting for droplet to become active...")
err := waitForDropletState("active", dropletId, client, c.stateTimeout)
err := waitForDropletState("active", dropletId, client, c)
if err != nil {
err := fmt.Errorf("Error waiting for droplet to become active: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -28,16 +28,16 @@ func (s *stepDropletInfo) Run(state multistep.StateBag) multistep.StepAction {
ip, _, err := client.DropletStatus(dropletId)
if err != nil {
err := fmt.Errorf("Error retrieving droplet ID: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("droplet_ip", ip)
state["droplet_ip"] = ip
return multistep.ActionContinue
}
func (s *stepDropletInfo) Cleanup(state multistep.StateBag) {
func (s *stepDropletInfo) Cleanup(state map[string]interface{}) {
// no cleanup
}
+20 -24
View File
@@ -5,43 +5,39 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
type stepPowerOff struct{}
func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
c := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui)
dropletId := state.Get("droplet_id").(uint)
func (s *stepPowerOff) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
c := state["config"].(config)
ui := state["ui"].(packer.Ui)
dropletId := state["droplet_id"].(uint)
_, status, err := client.DropletStatus(dropletId)
if err != nil {
err := fmt.Errorf("Error checking droplet state: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Sleep arbitrarily before sending power off request
// Otherwise we get "pending event" errors, even though there isn't
// one.
log.Printf("Sleeping for %v, event_delay", c.RawEventDelay)
time.Sleep(c.eventDelay)
if status == "off" {
// Droplet is already off, don't do anything
return multistep.ActionContinue
}
// Poweroff the droplet so it can be snapshot
err := client.PowerOffDroplet(dropletId)
// Pull the plug on the Droplet
ui.Say("Forcefully shutting down Droplet...")
err = client.PowerOffDroplet(dropletId)
if err != nil {
err := fmt.Errorf("Error powering off droplet: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Println("Waiting for poweroff event to complete...")
err = waitForDropletState("off", dropletId, client, c.stateTimeout)
ui.Say("Waiting for droplet to power off...")
err = waitForDropletState("off", dropletId, client, c)
if err != nil {
state.Put("error", err)
err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -49,6 +45,6 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepPowerOff) Cleanup(state multistep.StateBag) {
func (s *stepPowerOff) Cleanup(state map[string]interface{}) {
// no cleanup
}
-74
View File
@@ -1,74 +0,0 @@
package digitalocean
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
type stepShutdown struct{}
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
dropletId := state.Get("droplet_id").(uint)
// Gracefully power off the droplet. We have to retry this a number
// of times because sometimes it says it completed when it actually
// did absolutely nothing (*ALAKAZAM!* magic!). We give up after
// a pretty arbitrary amount of time.
ui.Say("Gracefully shutting down droplet...")
err := client.ShutdownDroplet(dropletId)
if err != nil {
// If we get an error the first time, actually report it
err := fmt.Errorf("Error shutting down droplet: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// A channel we use as a flag to end our goroutines
done := make(chan struct{})
shutdownRetryDone := make(chan struct{})
// Make sure we wait for the shutdown retry goroutine to end
// before moving on.
defer func() {
close(done)
<-shutdownRetryDone
}()
// Start a goroutine that just keeps trying to shut down the
// droplet.
go func() {
defer close(shutdownRetryDone)
for attempts := 2; attempts > 0; attempts++ {
log.Printf("ShutdownDroplet attempt #%d...", attempts)
err := client.ShutdownDroplet(dropletId)
if err != nil {
log.Printf("Shutdown retry error: %s", err)
}
select {
case <-done:
return
case <-time.After(20 * time.Second):
// Retry!
}
}
}()
err = waitForDropletState("off", dropletId, client, 2*time.Minute)
if err != nil {
log.Printf("Error waiting for graceful off: %s", err)
}
return multistep.ActionContinue
}
func (s *stepShutdown) Cleanup(state multistep.StateBag) {
// no cleanup
}
+13 -13
View File
@@ -10,26 +10,26 @@ import (
type stepSnapshot struct{}
func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
dropletId := state.Get("droplet_id").(uint)
func (s *stepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
client := state["client"].(*DigitalOceanClient)
ui := state["ui"].(packer.Ui)
c := state["config"].(config)
dropletId := state["droplet_id"].(uint)
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
err := client.CreateSnapshot(dropletId, c.SnapshotName)
if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Waiting for snapshot to complete...")
err = waitForDropletState("active", dropletId, client, c.stateTimeout)
err = waitForDropletState("active", dropletId, client, c)
if err != nil {
err := fmt.Errorf("Error waiting for snapshot to complete: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -38,7 +38,7 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
images, err := client.Images()
if err != nil {
err := fmt.Errorf("Error looking up snapshot ID: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -53,19 +53,19 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
if imageId == 0 {
err := errors.New("Couldn't find snapshot to get the image ID. Bug?")
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Snapshot image ID: %d", imageId)
state.Put("snapshot_image_id", imageId)
state.Put("snapshot_name", c.SnapshotName)
state["snapshot_image_id"] = imageId
state["snapshot_name"] = c.SnapshotName
return multistep.ActionContinue
}
func (s *stepSnapshot) Cleanup(state multistep.StateBag) {
func (s *stepSnapshot) Cleanup(state map[string]interface{}) {
// no cleanup
}
+24 -25
View File
@@ -1,18 +1,16 @@
package digitalocean
import (
"fmt"
"errors"
"log"
"time"
)
// waitForState simply blocks until the droplet is in
// a state we expect, while eventually timing out.
func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, timeout time.Duration) error {
done := make(chan struct{})
defer close(done)
func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, c config) error {
active := make(chan bool, 1)
result := make(chan error, 1)
go func() {
attempts := 0
for {
@@ -21,35 +19,36 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce
log.Printf("Checking droplet status... (attempt: %d)", attempts)
_, status, err := client.DropletStatus(dropletId)
if err != nil {
result <- err
return
log.Println(err)
break
}
if status == desiredState {
result <- nil
return
break
}
// Wait 3 seconds in between
time.Sleep(3 * time.Second)
// Verify we shouldn't exit
select {
case <-done:
// We finished, so just exit the goroutine
return
default:
// Keep going
}
}
active <- true
}()
log.Printf("Waiting for up to %d seconds for droplet to become %s", timeout, desiredState)
select {
case err := <-result:
return err
case <-time.After(timeout):
err := fmt.Errorf("Timeout while waiting to for droplet to become '%s'", desiredState)
return err
log.Printf("Waiting for up to %s for droplet to become %s", c.RawStateTimeout, desiredState)
timeout := time.After(c.stateTimeout)
ActiveWaitLoop:
for {
select {
case <-active:
// We connected. Just break the loop.
break ActiveWaitLoop
case <-timeout:
err := errors.New("Timeout while waiting to for droplet to become active")
return err
}
}
// If we got this far, there were no errors
return nil
}
-91
View File
@@ -1,91 +0,0 @@
package openstack
import (
"fmt"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"os"
)
// AccessConfig is for common configuration related to openstack access
type AccessConfig struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Project string `mapstructure:"project"`
Provider string `mapstructure:"provider"`
RawRegion string `mapstructure:"region"`
}
// Auth returns a valid Auth object for access to openstack services, or
// an error if the authentication couldn't be resolved.
func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) {
username := c.Username
password := c.Password
project := c.Project
provider := c.Provider
if username == "" {
username = os.Getenv("SDK_USERNAME")
}
if password == "" {
password = os.Getenv("SDK_PASSWORD")
}
if project == "" {
project = os.Getenv("SDK_PROJECT")
}
if provider == "" {
provider = os.Getenv("SDK_PROVIDER")
}
authoptions := gophercloud.AuthOptions{
Username: username,
Password: password,
AllowReauth: true,
}
if project != "" {
authoptions.TenantName = project
}
return gophercloud.Authenticate(provider, authoptions)
}
func (c *AccessConfig) Region() string {
return c.RawRegion
}
func (c *AccessConfig) Prepare(t *packer.ConfigTemplate) []error {
if t == nil {
var err error
t, err = packer.NewConfigTemplate()
if err != nil {
return []error{err}
}
}
templates := map[string]*string{
"username": &c.Username,
"password": &c.Password,
"provider": &c.Provider,
}
errs := make([]error, 0)
for n, ptr := range templates {
var err error
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if c.RawRegion == "" {
errs = append(errs, fmt.Errorf("region must be specified"))
}
if len(errs) > 0 {
return errs
}
return nil
}
-28
View File
@@ -1,28 +0,0 @@
package openstack
import (
"testing"
)
func testAccessConfig() *AccessConfig {
return &AccessConfig{}
}
func TestAccessConfigPrepare_NoRegion(t *testing.T) {
c := testAccessConfig()
if err := c.Prepare(nil); err == nil {
t.Fatalf("shouldn't have err: %s", err)
}
}
func TestAccessConfigPrepare_Region(t *testing.T) {
dfw := "DFW"
c := testAccessConfig()
c.RawRegion = dfw
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
if dfw != c.Region() {
t.Fatalf("Regions do not match: %s %s", dfw, c.Region())
}
}
-41
View File
@@ -1,41 +0,0 @@
package openstack
import (
"fmt"
"github.com/rackspace/gophercloud"
"log"
)
// Artifact is an artifact implementation that contains built images.
type Artifact struct {
// ImageId of built image
ImageId string
// BuilderId is the unique ID for the builder that created this image
BuilderIdValue string
// OpenStack connection for performing API stuff.
Conn gophercloud.CloudServersProvider
}
func (a *Artifact) BuilderId() string {
return a.BuilderIdValue
}
func (*Artifact) Files() []string {
// We have no files
return nil
}
func (a *Artifact) Id() string {
return a.ImageId
}
func (a *Artifact) String() string {
return fmt.Sprintf("An image was created: %v", a.ImageId)
}
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d", a.ImageId)
return a.Conn.DeleteImageById(a.ImageId)
}
-39
View File
@@ -1,39 +0,0 @@
package openstack
import (
"cgl.tideland.biz/asserts"
"github.com/mitchellh/packer/packer"
"testing"
)
func TestArtifact_Impl(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
var actual packer.Artifact
assert.Implementor(&Artifact{}, &actual, "should be an Artifact")
}
func TestArtifactId(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
expected := `b8cdf55b-c916-40bd-b190-389ec144c4ed`
a := &Artifact{
ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed",
}
result := a.Id()
assert.Equal(result, expected, "should match output")
}
func TestArtifactString(t *testing.T) {
assert := asserts.NewTestingAsserts(t, true)
expected := "An image was created: b8cdf55b-c916-40bd-b190-389ec144c4ed"
a := &Artifact{
ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed",
}
result := a.String()
assert.Equal(result, expected, "should match output")
}
-136
View File
@@ -1,136 +0,0 @@
// The openstack package contains a packer.Builder implementation that
// builds Images for openstack.
package openstack
import (
//"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
)
// The unique ID for this builder
const BuilderId = "mitchellh.openstack"
type config struct {
common.PackerConfig `mapstructure:",squash"`
AccessConfig `mapstructure:",squash"`
ImageConfig `mapstructure:",squash"`
RunConfig `mapstructure:",squash"`
tpl *packer.ConfigTemplate
}
type Builder struct {
config config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) error {
md, err := common.DecodeConfig(&b.config, raws...)
if err != nil {
return err
}
b.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return err
}
b.config.tpl.UserVars = b.config.PackerUserVars
// Accumulate any errors
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.ImageConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
if errs != nil && len(errs.Errors) > 0 {
return errs
}
log.Printf("Config: %+v", b.config)
return nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
auth, err := b.config.AccessConfig.Auth()
if err != nil {
return nil, err
}
api := &gophercloud.ApiCriteria{
Name: "cloudServersOpenStack",
Region: b.config.AccessConfig.Region(),
VersionId: "2",
UrlChoice: gophercloud.PublicURL,
}
csp, err := gophercloud.ServersApi(auth, *api)
if err != nil {
log.Printf("Region: %s", b.config.AccessConfig.Region())
return nil, err
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("csp", csp)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
&StepKeyPair{},
&StepRunSourceServer{
Name: b.config.ImageName,
Flavor: b.config.Flavor,
SourceImage: b.config.SourceImage,
},
&common.StepConnectSSH{
SSHAddress: SSHAddress(csp, b.config.SSHPort),
SSHConfig: SSHConfig(b.config.SSHUsername),
SSHWaitTimeout: b.config.SSHTimeout(),
},
&common.StepProvision{},
&stepCreateImage{},
}
// Run!
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If there are no images, then just return
if _, ok := state.GetOk("image"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &Artifact{
ImageId: state.Get("image").(string),
BuilderIdValue: BuilderId,
Conn: csp,
}
return artifact, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
-79
View File
@@ -1,79 +0,0 @@
package openstack
import (
"github.com/mitchellh/packer/packer"
"testing"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"username": "foo",
"password": "bar",
"provider": "foo",
"region": "DFW",
"image_name": "foo",
"source_image": "foo",
"flavor": "foo",
"ssh_username": "root",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"password": []string{},
}
err := b.Prepare(c)
if err == nil {
t.Fatalf("prepare should fail")
}
}
func TestBuilderPrepare_ImageName(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["image_name"] = "foo"
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["image_name"] = "foo {{"
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test bad
delete(config, "image_name")
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
}
-45
View File
@@ -1,45 +0,0 @@
package openstack
import (
"fmt"
"github.com/mitchellh/packer/packer"
)
// ImageConfig is for common configuration related to creating Images.
type ImageConfig struct {
ImageName string `mapstructure:"image_name"`
}
func (c *ImageConfig) Prepare(t *packer.ConfigTemplate) []error {
if t == nil {
var err error
t, err = packer.NewConfigTemplate()
if err != nil {
return []error{err}
}
}
templates := map[string]*string{
"image_name": &c.ImageName,
}
errs := make([]error, 0)
for n, ptr := range templates {
var err error
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
if c.ImageName == "" {
errs = append(errs, fmt.Errorf("An image_name must be specified"))
}
if len(errs) > 0 {
return errs
}
return nil
}
-23
View File
@@ -1,23 +0,0 @@
package openstack
import (
"testing"
)
func testImageConfig() *ImageConfig {
return &ImageConfig{
ImageName: "foo",
}
}
func TestImageConfigPrepare_Region(t *testing.T) {
c := testImageConfig()
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.ImageName = ""
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
}
-86
View File
@@ -1,86 +0,0 @@
package openstack
import (
"errors"
"fmt"
"github.com/mitchellh/packer/packer"
"time"
)
// RunConfig contains configuration for running an instance from a source
// image and details on how to access that launched image.
type RunConfig struct {
SourceImage string `mapstructure:"source_image"`
Flavor string `mapstructure:"flavor"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
// Unexported fields that are calculated from others
sshTimeout time.Duration
}
func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
if t == nil {
var err error
t, err = packer.NewConfigTemplate()
if err != nil {
return []error{err}
}
}
// Defaults
if c.SSHUsername == "" {
c.SSHUsername = "root"
}
if c.SSHPort == 0 {
c.SSHPort = 22
}
if c.RawSSHTimeout == "" {
c.RawSSHTimeout = "5m"
}
// Validation
var err error
errs := make([]error, 0)
if c.SourceImage == "" {
errs = append(errs, errors.New("A source_image must be specified"))
}
if c.Flavor == "" {
errs = append(errs, errors.New("A flavor must be specified"))
}
if c.SSHUsername == "" {
errs = append(errs, errors.New("An ssh_username must be specified"))
}
templates := map[string]*string{
"flavlor": &c.Flavor,
"ssh_timeout": &c.RawSSHTimeout,
"ssh_username": &c.SSHUsername,
"source_image": &c.SourceImage,
}
for n, ptr := range templates {
var err error
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(
errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
}
return errs
}
func (c *RunConfig) SSHTimeout() time.Duration {
return c.sshTimeout
}
-88
View File
@@ -1,88 +0,0 @@
package openstack
import (
"os"
"testing"
)
func init() {
// Clear out the openstack env vars so they don't
// affect our tests.
os.Setenv("SDK_USERNAME", "")
os.Setenv("SDK_PASSWORD", "")
os.Setenv("SDK_PROVIDER", "")
}
func testRunConfig() *RunConfig {
return &RunConfig{
SourceImage: "abcd",
Flavor: "m1.small",
SSHUsername: "root",
}
}
func TestRunConfigPrepare(t *testing.T) {
c := testRunConfig()
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_InstanceType(t *testing.T) {
c := testRunConfig()
c.Flavor = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SourceImage(t *testing.T) {
c := testRunConfig()
c.SourceImage = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SSHPort(t *testing.T) {
c := testRunConfig()
c.SSHPort = 0
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.SSHPort != 22 {
t.Fatalf("invalid value: %d", c.SSHPort)
}
c.SSHPort = 44
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.SSHPort != 44 {
t.Fatalf("invalid value: %d", c.SSHPort)
}
}
func TestRunConfigPrepare_SSHTimeout(t *testing.T) {
c := testRunConfig()
c.RawSSHTimeout = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
c.RawSSHTimeout = "bad"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SSHUsername(t *testing.T) {
c := testRunConfig()
c.SSHUsername = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
-87
View File
@@ -1,87 +0,0 @@
package openstack
import (
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/rackspace/gophercloud"
"log"
"time"
)
// StateRefreshFunc is a function type used for StateChangeConf that is
// responsible for refreshing the item being watched for a state change.
//
// It returns three results. `result` is any object that will be returned
// as the final object after waiting for state change. This allows you to
// return the final updated object, for example an openstack instance after
// refreshing it.
//
// `state` is the latest state of that object. And `err` is any error that
// may have happened while refreshing the state.
type StateRefreshFunc func() (result interface{}, state string, progress int, err error)
// StateChangeConf is the configuration struct used for `WaitForState`.
type StateChangeConf struct {
Pending []string
Refresh StateRefreshFunc
StepState multistep.StateBag
Target string
}
// ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an openstacn server.
func ServerStateRefreshFunc(csp gophercloud.CloudServersProvider, s *gophercloud.Server) StateRefreshFunc {
return func() (interface{}, string, int, error) {
resp, err := csp.ServerById(s.Id)
if err != nil {
log.Printf("Error on ServerStateRefresh: %s", err)
return nil, "", 0, err
}
return resp, resp.Status, resp.Progress, nil
}
}
// WaitForState watches an object and waits for it to achieve a certain
// state.
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
log.Printf("Waiting for state to become: %s", conf.Target)
for {
var currentProgress int
var currentState string
i, currentState, currentProgress, err = conf.Refresh()
if err != nil {
return
}
if currentState == conf.Target {
return
}
if conf.StepState != nil {
if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("interrupted")
}
}
found := false
for _, allowed := range conf.Pending {
if currentState == allowed {
found = true
break
}
}
if !found {
fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
return
}
log.Printf("Waiting for state to become: %s currently %s (%d%%)", conf.Target, currentState, currentProgress)
time.Sleep(2 * time.Second)
}
return
}
-58
View File
@@ -1,58 +0,0 @@
package openstack
import (
gossh "code.google.com/p/go.crypto/ssh"
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
"github.com/rackspace/gophercloud"
"time"
)
// SSHAddress returns a function that can be given to the SSH communicator
// for determining the SSH address based on the server AccessIPv4 setting..
func SSHAddress(csp gophercloud.CloudServersProvider, port int) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
for j := 0; j < 2; j++ {
s := state.Get("server").(*gophercloud.Server)
if s.AccessIPv4 != "" {
return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil
}
if s.AccessIPv6 != "" {
return fmt.Sprintf("[%s]:%d", s.AccessIPv6, port), nil
}
serverState, err := csp.ServerById(s.Id)
if err != nil {
return "", err
}
state.Put("server", serverState)
time.Sleep(1 * time.Second)
}
return "", errors.New("couldn't determine IP address for server")
}
}
// SSHConfig returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the generated
// private key.
func SSHConfig(username string) func(multistep.StateBag) (*gossh.ClientConfig, error) {
return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
privateKey := state.Get("privateKey").(string)
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(privateKey); err != nil {
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
}
return &gossh.ClientConfig{
User: username,
Auth: []gossh.ClientAuth{
gossh.ClientAuthKeyring(keyring),
},
}, nil
}
}
-68
View File
@@ -1,68 +0,0 @@
package openstack
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
"time"
)
type stepCreateImage struct{}
func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
csp := state.Get("csp").(gophercloud.CloudServersProvider)
config := state.Get("config").(config)
server := state.Get("server").(*gophercloud.Server)
ui := state.Get("ui").(packer.Ui)
// Create the image
ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName))
createOpts := gophercloud.CreateImage{
Name: config.ImageName,
}
imageId, err := csp.CreateImage(server.Id, createOpts)
if err != nil {
err := fmt.Errorf("Error creating image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the Image ID in the state
ui.Say(fmt.Sprintf("Image: %s", imageId))
state.Put("image", imageId)
// Wait for the image to become ready
ui.Say("Waiting for image to become ready...")
if err := WaitForImage(csp, imageId); err != nil {
err := fmt.Errorf("Error waiting for image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepCreateImage) Cleanup(multistep.StateBag) {
// No cleanup...
}
// WaitForImage waits for the given Image ID to become ready.
func WaitForImage(csp gophercloud.CloudServersProvider, imageId string) error {
for {
image, err := csp.ImageById(imageId)
if err != nil {
return err
}
if image.Status == "ACTIVE" {
return nil
}
log.Printf("Waiting for image creation status: %s (%d%%)", image.Status, image.Progress)
time.Sleep(2 * time.Second)
}
}
-55
View File
@@ -1,55 +0,0 @@
package openstack
import (
"cgl.tideland.biz/identifier"
"encoding/hex"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
)
type StepKeyPair struct {
keyName string
}
func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
csp := state.Get("csp").(gophercloud.CloudServersProvider)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating temporary keypair for this instance...")
keyName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw()))
log.Printf("temporary keypair name: %s", keyName)
keyResp, err := csp.CreateKeyPair(gophercloud.NewKeyPair{Name: keyName})
if err != nil {
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
return multistep.ActionHalt
}
// Set the keyname so we know to delete it later
s.keyName = keyName
// Set some state data for use in future steps
state.Put("keyPair", keyName)
state.Put("privateKey", keyResp.PrivateKey)
return multistep.ActionContinue
}
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
// If no key name is set, then we never created it, so just return
if s.keyName == "" {
return
}
csp := state.Get("csp").(gophercloud.CloudServersProvider)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting temporary keypair...")
err := csp.DeleteKeyPair(s.keyName)
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up keypair. Please delete the key manually: %s", s.keyName))
}
}
@@ -1,86 +0,0 @@
package openstack
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
)
type StepRunSourceServer struct {
Flavor string
Name string
SourceImage string
server *gophercloud.Server
}
func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction {
csp := state.Get("csp").(gophercloud.CloudServersProvider)
keyName := state.Get("keyPair").(string)
ui := state.Get("ui").(packer.Ui)
// XXX - validate image and flavor is available
server := gophercloud.NewServer{
Name: s.Name,
ImageRef: s.SourceImage,
FlavorRef: s.Flavor,
KeyPairName: keyName,
}
serverResp, err := csp.CreateServer(server)
if err != nil {
err := fmt.Errorf("Error launching source server: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.server, err = csp.ServerById(serverResp.Id)
log.Printf("server id: %s", s.server.Id)
ui.Say(fmt.Sprintf("Waiting for server (%s) to become ready...", s.server.Id))
stateChange := StateChangeConf{
Pending: []string{"BUILD"},
Target: "ACTIVE",
Refresh: ServerStateRefreshFunc(csp, s.server),
StepState: state,
}
latestServer, err := WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for server (%s) to become ready: %s", s.server.Id, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.server = latestServer.(*gophercloud.Server)
state.Put("server", s.server)
return multistep.ActionContinue
}
func (s *StepRunSourceServer) Cleanup(state multistep.StateBag) {
if s.server == nil {
return
}
csp := state.Get("csp").(gophercloud.CloudServersProvider)
ui := state.Get("ui").(packer.Ui)
ui.Say("Terminating the source server...")
if err := csp.DeleteServerById(s.server.Id); err != nil {
ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err))
return
}
stateChange := StateChangeConf{
Pending: []string{"ACTIVE", "BUILD", "REBUILD", "SUSPENDED"},
Refresh: ServerStateRefreshFunc(csp, s.server),
Target: "DELETED",
}
WaitForState(&stateChange)
}
+9 -42
View File
@@ -27,12 +27,10 @@ type config struct {
BootCommand []string `mapstructure:"boot_command"`
DiskSize uint `mapstructure:"disk_size"`
FloppyFiles []string `mapstructure:"floppy_files"`
Format string `mapstructure:"format"`
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
GuestOSType string `mapstructure:"guest_os_type"`
HardDriveInterface string `mapstructure:"hard_drive_interface"`
Headless bool `mapstructure:"headless"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
@@ -44,7 +42,6 @@ type config struct {
ShutdownCommand string `mapstructure:"shutdown_command"`
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
SSHKeyPath string `mapstructure:"ssh_key_path"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPort uint `mapstructure:"ssh_port"`
SSHUser string `mapstructure:"ssh_username"`
@@ -90,10 +87,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.GuestAdditionsPath = "VBoxGuestAdditions.iso"
}
if b.config.HardDriveInterface == "" {
b.config.HardDriveInterface = "ide"
}
if b.config.GuestOSType == "" {
b.config.GuestOSType = "Other"
}
@@ -138,15 +131,10 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
}
if b.config.Format == "" {
b.config.Format = "ovf"
}
// Errors
templates := map[string]*string{
"guest_additions_sha256": &b.config.GuestAdditionsSHA256,
"guest_os_type": &b.config.GuestOSType,
"hard_drive_interface": &b.config.HardDriveInterface,
"http_directory": &b.config.HTTPDir,
"iso_checksum": &b.config.ISOChecksum,
"iso_checksum_type": &b.config.ISOChecksumType,
@@ -157,7 +145,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
"ssh_username": &b.config.SSHUser,
"virtualbox_version_file": &b.config.VBoxVersionFile,
"vm_name": &b.config.VMName,
"format": &b.config.Format,
"boot_wait": &b.config.RawBootWait,
"shutdown_timeout": &b.config.RawShutdownTimeout,
"ssh_wait_timeout": &b.config.RawSSHWaitTimeout,
@@ -210,16 +197,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
}
if !(b.config.Format == "ovf" || b.config.Format == "ova") {
errs = packer.MultiErrorAppend(
errs, errors.New("invalid format, only 'ovf' or 'ova' are allowed"))
}
if b.config.HardDriveInterface != "ide" && b.config.HardDriveInterface != "sata" {
errs = packer.MultiErrorAppend(
errs, errors.New("hard_drive_interface can only be ide or sata"))
}
if b.config.HTTPPortMin > b.config.HTTPPortMax {
errs = packer.MultiErrorAppend(
errs, errors.New("http_port_min must be less than http_port_max"))
@@ -294,16 +271,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
}
if b.config.SSHKeyPath != "" {
if _, err := os.Stat(b.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
} else if _, err := sshKeyToKeyring(b.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
}
}
if b.config.SSHHostPortMin > b.config.SSHHostPortMax {
errs = packer.MultiErrorAppend(
errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
@@ -379,12 +346,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Setup the state bag
state := new(multistep.BasicStateBag)
state.Put("cache", cache)
state.Put("config", &b.config)
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
state := make(map[string]interface{})
state["cache"] = cache
state["config"] = &b.config
state["driver"] = driver
state["hook"] = hook
state["ui"] = ui
// Run
if b.config.PackerDebug {
@@ -399,16 +366,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
if rawErr, ok := state["error"]; ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
if _, ok := state[multistep.StateCancelled]; ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
if _, ok := state[multistep.StateHalted]; ok {
return nil, errors.New("Build was halted.")
}
-143
View File
@@ -8,36 +8,6 @@ import (
"testing"
)
var testPem = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW
LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN
AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD
2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH
uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3
5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV
BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG
E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko
9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF
K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3
/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+
2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa
nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn
kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6
hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC
v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl
b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR
v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3
uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1
9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR
lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc
eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa
1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG
3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4=
-----END RSA PRIVATE KEY-----
`
func testConfig() map[string]interface{} {
return map[string]interface{}{
"iso_checksum": "foo",
@@ -88,10 +58,6 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
if b.config.VMName != "packer-foo" {
t.Errorf("bad vm name: %s", b.config.VMName)
}
if b.config.Format != "ovf" {
t.Errorf("bad format: %s", b.config.Format)
}
}
func TestBuilderPrepare_BootWait(t *testing.T) {
@@ -252,38 +218,6 @@ func TestBuilderPrepare_GuestAdditionsURL(t *testing.T) {
}
}
func TestBuilderPrepare_HardDriveInterface(t *testing.T) {
var b Builder
config := testConfig()
// Test a default boot_wait
delete(config, "hard_drive_interface")
err := b.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
if b.config.HardDriveInterface != "ide" {
t.Fatalf("bad: %s", b.config.HardDriveInterface)
}
// Test with a bad
config["hard_drive_interface"] = "fake"
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test with a good
config["hard_drive_interface"] = "sata"
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_HTTPPort(t *testing.T) {
var b Builder
config := testConfig()
@@ -314,34 +248,6 @@ func TestBuilderPrepare_HTTPPort(t *testing.T) {
}
}
func TestBuilderPrepare_Format(t *testing.T) {
var b Builder
config := testConfig()
// Bad
config["format"] = "illegal value"
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Good
config["format"] = "ova"
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Good
config["format"] = "ovf"
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
@@ -546,55 +452,6 @@ func TestBuilderPrepare_SSHHostPort(t *testing.T) {
}
}
func TestBuilderPrepare_sshKeyPath(t *testing.T) {
var b Builder
config := testConfig()
config["ssh_key_path"] = ""
b = Builder{}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
config["ssh_key_path"] = "/i/dont/exist"
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test bad contents
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
if _, err := tf.Write([]byte("HELLO!")); err != nil {
t.Fatalf("err: %s", err)
}
config["ssh_key_path"] = tf.Name()
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test good contents
tf.Seek(0, 0)
tf.Truncate(0)
tf.Write([]byte(testPem))
config["ssh_key_path"] = tf.Name()
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestBuilderPrepare_SSHUser(t *testing.T) {
var b Builder
config := testConfig()
+9 -43
View File
@@ -3,57 +3,23 @@ package virtualbox
import (
gossh "code.google.com/p/go.crypto/ssh"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
"io/ioutil"
"os"
)
func sshAddress(state multistep.StateBag) (string, error) {
sshHostPort := state.Get("sshHostPort").(uint)
func sshAddress(state map[string]interface{}) (string, error) {
sshHostPort := state["sshHostPort"].(uint)
return fmt.Sprintf("127.0.0.1:%d", sshHostPort), nil
}
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(*config)
auth := []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
}
if config.SSHKeyPath != "" {
keyring, err := sshKeyToKeyring(config.SSHKeyPath)
if err != nil {
return nil, err
}
auth = append(auth, gossh.ClientAuthKeyring(keyring))
}
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
config := state["config"].(*config)
return &gossh.ClientConfig{
User: config.SSHUser,
Auth: auth,
Auth: []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
},
}, nil
}
func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
keyBytes, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(string(keyBytes)); err != nil {
return nil, err
}
return keyring, nil
}
+11 -11
View File
@@ -20,10 +20,10 @@ type stepAttachFloppy struct {
floppyPath string
}
func (s *stepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction {
func (s *stepAttachFloppy) Run(state map[string]interface{}) multistep.StepAction {
// Determine if we even have a floppy disk to attach
var floppyPath string
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
if floppyPathRaw, ok := state["floppy_path"]; ok {
floppyPath = floppyPathRaw.(string)
} else {
log.Println("No floppy disk, not attaching.")
@@ -35,13 +35,13 @@ func (s *stepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction {
// floppy.
floppyPath, err := s.copyFloppy(floppyPath)
if err != nil {
state.Put("error", fmt.Errorf("Error preparing floppy: %s", err))
state["error"] = fmt.Errorf("Error preparing floppy: %s", err)
return multistep.ActionHalt
}
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
ui.Say("Attaching floppy disk...")
@@ -52,7 +52,7 @@ func (s *stepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction {
"--add", "floppy",
}
if err := driver.VBoxManage(command...); err != nil {
state.Put("error", fmt.Errorf("Error creating floppy controller: %s", err))
state["error"] = fmt.Errorf("Error creating floppy controller: %s", err)
return multistep.ActionHalt
}
@@ -66,7 +66,7 @@ func (s *stepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction {
"--medium", floppyPath,
}
if err := driver.VBoxManage(command...); err != nil {
state.Put("error", fmt.Errorf("Error attaching floppy: %s", err))
state["error"] = fmt.Errorf("Error attaching floppy: %s", err)
return multistep.ActionHalt
}
@@ -76,7 +76,7 @@ func (s *stepAttachFloppy) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepAttachFloppy) Cleanup(state multistep.StateBag) {
func (s *stepAttachFloppy) Cleanup(state map[string]interface{}) {
if s.floppyPath == "" {
return
}
@@ -84,8 +84,8 @@ func (s *stepAttachFloppy) Cleanup(state multistep.StateBag) {
// Delete the floppy disk
defer os.Remove(s.floppyPath)
driver := state.Get("driver").(Driver)
vmName := state.Get("vmName").(string)
driver := state["driver"].(Driver)
vmName := state["vmName"].(string)
command := []string{
"storageattach", vmName,
+10 -10
View File
@@ -15,11 +15,11 @@ type stepAttachISO struct {
diskPath string
}
func (s *stepAttachISO) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
func (s *stepAttachISO) Run(state map[string]interface{}) multistep.StepAction {
driver := state["driver"].(Driver)
isoPath := state["iso_path"].(string)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
// Attach the disk to the controller
command := []string{
@@ -32,7 +32,7 @@ func (s *stepAttachISO) Run(state multistep.StateBag) multistep.StepAction {
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error attaching ISO: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -43,14 +43,14 @@ func (s *stepAttachISO) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepAttachISO) Cleanup(state multistep.StateBag) {
func (s *stepAttachISO) Cleanup(state map[string]interface{}) {
if s.diskPath == "" {
return
}
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
command := []string{
"storageattach", vmName,
+10 -31
View File
@@ -13,11 +13,11 @@ import (
// hard drive for the virtual machine.
type stepCreateDisk struct{}
func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
func (s *stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
format := "VDI"
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName, strings.ToLower(format)))
@@ -34,42 +34,21 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
err := driver.VBoxManage(command...)
if err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// Add the IDE controller so we can later attach the disk.
// When the hard disk controller is not IDE, this device is still used
// by VirtualBox to deliver the guest extensions.
// Add the IDE controller so we can later attach the disk
controllerName := "IDE Controller"
err = driver.VBoxManage("storagectl", vmName, "--name", controllerName, "--add", "ide")
if err != nil {
err := fmt.Errorf("Error creating disk controller: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// Add a SATA controller if we were asked to use SATA. We still attach
// the IDE controller above because some other things (disks) require
// that.
if config.HardDriveInterface == "sata" {
controllerName = "SATA Controller"
command = []string{
"storagectl", vmName,
"--name", controllerName,
"--add", "sata",
"--sataportcount", "1",
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error creating disk controller: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
// Attach the disk to the controller
command = []string{
"storageattach", vmName,
@@ -81,7 +60,7 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error attaching hard drive: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -89,4 +68,4 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepCreateDisk) Cleanup(state multistep.StateBag) {}
func (s *stepCreateDisk) Cleanup(state map[string]interface{}) {}
+9 -9
View File
@@ -14,10 +14,10 @@ type stepCreateVM struct {
vmName string
}
func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
func (s *stepCreateVM) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
name := config.VMName
@@ -38,7 +38,7 @@ func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
err := driver.VBoxManage(command...)
if err != nil {
err := fmt.Errorf("Error creating VM: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -50,18 +50,18 @@ func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
}
// Set the final name in the state bag so others can use it
state.Put("vmName", s.vmName)
state["vmName"] = s.vmName
return multistep.ActionContinue
}
func (s *stepCreateVM) Cleanup(state multistep.StateBag) {
func (s *stepCreateVM) Cleanup(state map[string]interface{}) {
if s.vmName == "" {
return
}
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
ui.Say("Unregistering and deleting virtual machine...")
if err := driver.VBoxManage("unregistervm", s.vmName, "--delete"); err != nil {
@@ -29,16 +29,16 @@ type guestAdditionsUrlTemplate struct {
// guest_additions_path string - Path to the guest additions.
type stepDownloadGuestAdditions struct{}
func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep.StepAction {
var action multistep.StepAction
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
config := state["config"].(*config)
// Get VBox version
version, err := driver.Version()
if err != nil {
state.Put("error", fmt.Errorf("Error reading version for guest additions download: %s", err))
state["error"] = fmt.Errorf("Error reading version for guest additions download: %s", err)
return multistep.ActionHalt
}
@@ -71,7 +71,7 @@ func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.Ste
url, err = config.tpl.Process(url, tplData)
if err != nil {
err := fmt.Errorf("Error preparing guest additions url: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -85,7 +85,7 @@ func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.Ste
url, err = common.DownloadableURL(url)
if err != nil {
err := fmt.Errorf("Error preparing guest additions url: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -103,9 +103,9 @@ func (s *stepDownloadGuestAdditions) Run(state multistep.StateBag) multistep.Ste
return downStep.Run(state)
}
func (s *stepDownloadGuestAdditions) Cleanup(state multistep.StateBag) {}
func (s *stepDownloadGuestAdditions) Cleanup(state map[string]interface{}) {}
func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state multistep.StateBag, additionsVersion string, additionsName string) (string, multistep.StepAction) {
func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state map[string]interface{}, additionsVersion string, additionsName string) (string, multistep.StepAction) {
// First things first, we get the list of checksums for the files available
// for this version.
checksumsUrl := fmt.Sprintf(
@@ -114,9 +114,9 @@ func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state multistep.Sta
checksumsFile, err := ioutil.TempFile("", "packer")
if err != nil {
state.Put("error", fmt.Errorf(
state["error"] = fmt.Errorf(
"Failed creating temporary file to store guest addition checksums: %s",
err))
err)
return "", multistep.ActionHalt
}
defer os.Remove(checksumsFile.Name())
@@ -136,9 +136,9 @@ func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state multistep.Sta
// Next, we find the checksum for the file we're looking to download.
// It is an error if the checksum cannot be found.
checksumsF, err := os.Open(state.Get("guest_additions_checksums_path").(string))
checksumsF, err := os.Open(state["guest_additions_checksums_path"].(string))
if err != nil {
state.Put("error", fmt.Errorf("Error opening guest addition checksums: %s", err))
state["error"] = fmt.Errorf("Error opening guest addition checksums: %s", err)
return "", multistep.ActionHalt
}
defer checksumsF.Close()
@@ -166,8 +166,8 @@ func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256(state multistep.Sta
}
if checksum == "" {
state.Put("error", fmt.Errorf(
"The checksum for the file '%s' could not be found.", additionsName))
state["error"] = fmt.Errorf(
"The checksum for the file '%s' could not be found.", additionsName)
return "", multistep.ActionHalt
}
+13 -13
View File
@@ -15,25 +15,25 @@ import (
// exportPath string - The path to the resulting export.
type stepExport struct{}
func (s *stepExport) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
func (s *stepExport) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
// Clear out the Packer-created forwarding rule
ui.Say("Preparing to export machine...")
ui.Message(fmt.Sprintf("Deleting forwarded port mapping for SSH (host port %d)", state.Get("sshHostPort")))
ui.Message(fmt.Sprintf("Deleting forwarded port mapping for SSH (host port %d)", state["sshHostPort"]))
command := []string{"modifyvm", vmName, "--natpf1", "delete", "packerssh"}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error deleting port forwarding rule: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// Remove the attached floppy disk, if it exists
if _, ok := state.GetOk("floppy_path"); ok {
if _, ok := state["floppy_path"]; ok {
ui.Message("Removing floppy drive...")
command := []string{
"storageattach", vmName,
@@ -43,14 +43,14 @@ func (s *stepExport) Run(state multistep.StateBag) multistep.StepAction {
"--medium", "none",
}
if err := driver.VBoxManage(command...); err != nil {
state.Put("error", fmt.Errorf("Error removing floppy: %s", err))
state["error"] = fmt.Errorf("Error removing floppy: %s", err)
return multistep.ActionHalt
}
}
// Export the VM to an OVF
outputPath := filepath.Join(config.OutputDir, vmName+"."+config.Format)
outputPath := filepath.Join(config.OutputDir, "packer.ovf")
command = []string{
"export",
@@ -63,14 +63,14 @@ func (s *stepExport) Run(state multistep.StateBag) multistep.StepAction {
err := driver.VBoxManage(command...)
if err != nil {
err := fmt.Errorf("Error exporting virtual machine: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("exportPath", outputPath)
state["exportPath"] = outputPath
return multistep.ActionContinue
}
func (s *stepExport) Cleanup(state multistep.StateBag) {}
func (s *stepExport) Cleanup(state map[string]interface{}) {}
+8 -8
View File
@@ -17,11 +17,11 @@ import (
// Produces:
type stepForwardSSH struct{}
func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
func (s *stepForwardSSH) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
log.Printf("Looking for available SSH port between %d and %d", config.SSHHostPortMin, config.SSHHostPortMax)
var sshHostPort uint
@@ -45,15 +45,15 @@ func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error creating port forwarding rule: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
// Save the port we're using so that future steps can use it
state.Put("sshHostPort", sshHostPort)
state["sshHostPort"] = sshHostPort
return multistep.ActionContinue
}
func (s *stepForwardSSH) Cleanup(state multistep.StateBag) {}
func (s *stepForwardSSH) Cleanup(state map[string]interface{}) {}
+6 -6
View File
@@ -23,13 +23,13 @@ type stepHTTPServer struct {
l net.Listener
}
func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
func (s *stepHTTPServer) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
var httpPort uint = 0
if config.HTTPDir == "" {
state.Put("http_port", httpPort)
state["http_port"] = httpPort
return multistep.ActionContinue
}
@@ -62,12 +62,12 @@ func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
go server.Serve(s.l)
// Save the address into the state so it can be accessed in the future
state.Put("http_port", httpPort)
state["http_port"] = httpPort
return multistep.ActionContinue
}
func (s *stepHTTPServer) Cleanup(multistep.StateBag) {
func (s *stepHTTPServer) Cleanup(map[string]interface{}) {
if s.l != nil {
// Close the listener so that the HTTP server stops
s.l.Close()
@@ -10,9 +10,9 @@ import (
type stepPrepareOutputDir struct{}
func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
func (stepPrepareOutputDir) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
ui.Say("Deleting previous output directory...")
@@ -20,20 +20,20 @@ func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
}
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
state.Put("error", err)
state["error"] = err
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
func (stepPrepareOutputDir) Cleanup(state map[string]interface{}) {
_, cancelled := state[multistep.StateCancelled]
_, halted := state[multistep.StateHalted]
if cancelled || halted {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ {
+9 -9
View File
@@ -16,11 +16,11 @@ type stepRun struct {
vmName string
}
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
ui.Say("Starting the virtual machine...")
guiArgument := "gui"
@@ -33,7 +33,7 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
command := []string{"startvm", vmName, "--type", guiArgument}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error starting VM: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -48,13 +48,13 @@ func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepRun) Cleanup(state multistep.StateBag) {
func (s *stepRun) Cleanup(state map[string]interface{}) {
if s.vmName == "" {
return
}
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
if running, _ := driver.IsRunning(s.vmName); running {
if err := driver.VBoxManage("controlvm", s.vmName, "poweroff"); err != nil {
+10 -10
View File
@@ -23,12 +23,12 @@ import (
// <nothing>
type stepShutdown struct{}
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
if config.ShutdownCommand != "" {
ui.Say("Gracefully halting virtual machine...")
@@ -36,7 +36,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
cmd := &packer.RemoteCmd{Command: config.ShutdownCommand}
if err := cmd.StartWithUi(comm, ui); err != nil {
err := fmt.Errorf("Failed to send shutdown command: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -53,7 +53,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
select {
case <-shutdownTimer:
err := errors.New("Timeout while waiting for machine to shut down.")
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
default:
@@ -64,7 +64,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
ui.Say("Halting the virtual machine...")
if err := driver.Stop(vmName); err != nil {
err := fmt.Errorf("Error stopping VM: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -74,4 +74,4 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepShutdown) Cleanup(state multistep.StateBag) {}
func (s *stepShutdown) Cleanup(state map[string]interface{}) {}
+5 -5
View File
@@ -11,14 +11,14 @@ import (
// pop-up messages don't exist.
type stepSuppressMessages struct{}
func (stepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
func (stepSuppressMessages) Run(state map[string]interface{}) multistep.StepAction {
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
log.Println("Suppressing annoying messages in VirtualBox")
if err := driver.SuppressMessages(); err != nil {
err := fmt.Errorf("Error configuring VirtualBox to suppress messages: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -26,4 +26,4 @@ func (stepSuppressMessages) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (stepSuppressMessages) Cleanup(multistep.StateBag) {}
func (stepSuppressMessages) Cleanup(map[string]interface{}) {}
+10 -10
View File
@@ -32,12 +32,12 @@ type bootCommandTemplateData struct {
// <nothing>
type stepTypeBootCommand struct{}
func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
httpPort := state.Get("http_port").(uint)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
httpPort := state["http_port"].(uint)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
tplData := &bootCommandTemplateData{
"10.0.2.2",
@@ -50,7 +50,7 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
command, err := config.tpl.Process(command, tplData)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -73,13 +73,13 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
// Since typing is sometimes so slow, we check for an interrupt
// in between each character.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
if _, ok := state[multistep.StateCancelled]; ok {
return multistep.ActionHalt
}
if err := driver.VBoxManage("controlvm", vmName, "keyboardputscancode", code); err != nil {
err := fmt.Errorf("Error sending boot command: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -89,7 +89,7 @@ func (s *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
return multistep.ActionContinue
}
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
func (*stepTypeBootCommand) Cleanup(map[string]interface{}) {}
func scancodes(message string) []string {
special := make(map[string][]string)
@@ -14,22 +14,22 @@ type guestAdditionsPathTemplate struct {
// This step uploads the guest additions ISO to the VM.
type stepUploadGuestAdditions struct{}
func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
guestAdditionsPath := state.Get("guest_additions_path").(string)
ui := state.Get("ui").(packer.Ui)
func (s *stepUploadGuestAdditions) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*config)
driver := state["driver"].(Driver)
guestAdditionsPath := state["guest_additions_path"].(string)
ui := state["ui"].(packer.Ui)
version, err := driver.Version()
if err != nil {
state.Put("error", fmt.Errorf("Error reading version for guest additions upload: %s", err))
state["error"] = fmt.Errorf("Error reading version for guest additions upload: %s", err)
return multistep.ActionHalt
}
f, err := os.Open(guestAdditionsPath)
if err != nil {
state.Put("error", fmt.Errorf("Error opening guest additions ISO: %s", err))
state["error"] = fmt.Errorf("Error opening guest additions ISO: %s", err)
return multistep.ActionHalt
}
@@ -40,18 +40,18 @@ func (s *stepUploadGuestAdditions) Run(state multistep.StateBag) multistep.StepA
config.GuestAdditionsPath, err = config.tpl.Process(config.GuestAdditionsPath, tplData)
if err != nil {
err := fmt.Errorf("Error preparing guest additions path: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Uploading VirtualBox guest additions ISO...")
if err := comm.Upload(config.GuestAdditionsPath, f); err != nil {
state.Put("error", fmt.Errorf("Error uploading guest additions: %s", err))
state["error"] = fmt.Errorf("Error uploading guest additions: %s", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepUploadGuestAdditions) Cleanup(state multistep.StateBag) {}
func (s *stepUploadGuestAdditions) Cleanup(state map[string]interface{}) {}
+8 -8
View File
@@ -12,11 +12,11 @@ import (
// can be useful for various provisioning reasons.
type stepUploadVersion struct{}
func (s *stepUploadVersion) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
func (s *stepUploadVersion) Run(state map[string]interface{}) multistep.StepAction {
comm := state["communicator"].(packer.Communicator)
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
if config.VBoxVersionFile == "" {
log.Println("VBoxVersionFile is empty. Not uploading.")
@@ -25,7 +25,7 @@ func (s *stepUploadVersion) Run(state multistep.StateBag) multistep.StepAction {
version, err := driver.Version()
if err != nil {
state.Put("error", fmt.Errorf("Error reading version for metadata upload: %s", err))
state["error"] = fmt.Errorf("Error reading version for metadata upload: %s", err)
return multistep.ActionHalt
}
@@ -33,11 +33,11 @@ func (s *stepUploadVersion) Run(state multistep.StateBag) multistep.StepAction {
var data bytes.Buffer
data.WriteString(version)
if err := comm.Upload(config.VBoxVersionFile, &data); err != nil {
state.Put("error", fmt.Errorf("Error uploading VirtualBox version: %s", err))
state["error"] = fmt.Errorf("Error uploading VirtualBox version: %s", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepUploadVersion) Cleanup(state multistep.StateBag) {}
func (s *stepUploadVersion) Cleanup(state map[string]interface{}) {}
+8 -8
View File
@@ -19,11 +19,11 @@ type commandTemplate struct {
// Produces:
type stepVBoxManage struct{}
func (s *stepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
func (s *stepVBoxManage) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
driver := state["driver"].(Driver)
ui := state["ui"].(packer.Ui)
vmName := state["vmName"].(string)
if len(config.VBoxManage) > 0 {
ui.Say("Executing custom VBoxManage commands...")
@@ -42,7 +42,7 @@ func (s *stepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
command[i], err = config.tpl.Process(arg, tplData)
if err != nil {
err := fmt.Errorf("Error preparing vboxmanage command: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -51,7 +51,7 @@ func (s *stepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
ui.Message(fmt.Sprintf("Executing: %s", strings.Join(command, " ")))
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error executing command: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -60,4 +60,4 @@ func (s *stepVBoxManage) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepVBoxManage) Cleanup(state multistep.StateBag) {}
func (s *stepVBoxManage) Cleanup(state map[string]interface{}) {}
+9 -54
View File
@@ -6,7 +6,6 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"math/rand"
"os"
@@ -28,7 +27,6 @@ type config struct {
DiskName string `mapstructure:"vmdk_name"`
DiskSize uint `mapstructure:"disk_size"`
DiskTypeId string `mapstructure:"disk_type_id"`
FloppyFiles []string `mapstructure:"floppy_files"`
GuestOSType string `mapstructure:"guest_os_type"`
ISOChecksum string `mapstructure:"iso_checksum"`
@@ -44,14 +42,11 @@ type config struct {
SkipCompaction bool `mapstructure:"skip_compaction"`
ShutdownCommand string `mapstructure:"shutdown_command"`
SSHUser string `mapstructure:"ssh_username"`
SSHKeyPath string `mapstructure:"ssh_key_path"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPort uint `mapstructure:"ssh_port"`
SSHSkipRequestPty bool `mapstructure:"ssh_skip_request_pty"`
ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"`
ToolsUploadPath string `mapstructure:"tools_upload_path"`
VMXData map[string]string `mapstructure:"vmx_data"`
VMXTemplatePath string `mapstructure:"vmx_template_path"`
VNCPortMin uint `mapstructure:"vnc_port_min"`
VNCPortMax uint `mapstructure:"vnc_port_max"`
@@ -89,11 +84,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b.config.DiskSize = 40000
}
if b.config.DiskTypeId == "" {
// Default is growable virtual disk split in 2GB files.
b.config.DiskTypeId = "1"
}
if b.config.FloppyFiles == nil {
b.config.FloppyFiles = make([]string, 0)
}
@@ -155,7 +145,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
"boot_wait": &b.config.RawBootWait,
"shutdown_timeout": &b.config.RawShutdownTimeout,
"ssh_wait_timeout": &b.config.RawSSHWaitTimeout,
"vmx_template_path": &b.config.VMXTemplatePath,
}
for n, ptr := range templates {
@@ -264,16 +253,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
}
}
if b.config.SSHKeyPath != "" {
if _, err := os.Stat(b.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
} else if _, err := sshKeyToKeyring(b.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
}
}
if b.config.SSHUser == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("An ssh_username must be specified."))
@@ -312,14 +291,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
errs, fmt.Errorf("tools_upload_path invalid: %s", err))
}
if b.config.VMXTemplatePath != "" {
if err := b.validateVMXTemplatePath(); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("vmx_template_path is invalid: %s", err))
}
}
if b.config.VNCPortMin > b.config.VNCPortMax {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
@@ -365,7 +336,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SSHAddress: sshAddress,
SSHConfig: sshConfig,
SSHWaitTimeout: b.config.sshWaitTimeout,
NoPty: b.config.SSHSkipRequestPty,
},
&stepUploadTools{},
&common.StepProvision{},
@@ -376,12 +346,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Setup the state bag
state := new(multistep.BasicStateBag)
state.Put("cache", cache)
state.Put("config", &b.config)
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
state := make(map[string]interface{})
state["cache"] = cache
state["config"] = &b.config
state["driver"] = driver
state["hook"] = hook
state["ui"] = ui
// Run!
if b.config.PackerDebug {
@@ -396,16 +366,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
if rawErr, ok := state["error"]; ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
if _, ok := state[multistep.StateCancelled]; ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
if _, ok := state[multistep.StateHalted]; ok {
return nil, errors.New("Build was halted.")
}
@@ -436,18 +406,3 @@ func (b *Builder) Cancel() {
b.runner.Cancel()
}
}
func (b *Builder) validateVMXTemplatePath() error {
f, err := os.Open(b.config.VMXTemplatePath)
if err != nil {
return err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return err
}
return b.config.tpl.Validate(string(data))
}
-129
View File
@@ -9,36 +9,6 @@ import (
"time"
)
var testPem = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW
LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN
AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD
2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH
uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3
5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV
BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG
E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko
9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF
K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3
/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+
2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa
nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn
kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6
hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC
v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl
b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR
v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3
uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1
9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR
lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc
eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa
1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG
3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4=
-----END RSA PRIVATE KEY-----
`
func testConfig() map[string]interface{} {
return map[string]interface{}{
"iso_checksum": "foo",
@@ -369,55 +339,6 @@ func TestBuilderPrepare_ShutdownTimeout(t *testing.T) {
}
}
func TestBuilderPrepare_sshKeyPath(t *testing.T) {
var b Builder
config := testConfig()
config["ssh_key_path"] = ""
b = Builder{}
err := b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
config["ssh_key_path"] = "/i/dont/exist"
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test bad contents
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
if _, err := tf.Write([]byte("HELLO!")); err != nil {
t.Fatalf("err: %s", err)
}
config["ssh_key_path"] = tf.Name()
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test good contents
tf.Seek(0, 0)
tf.Truncate(0)
tf.Write([]byte(testPem))
config["ssh_key_path"] = tf.Name()
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestBuilderPrepare_SSHUser(t *testing.T) {
var b Builder
config := testConfig()
@@ -516,56 +437,6 @@ func TestBuilderPrepare_ToolsUploadPath(t *testing.T) {
}
}
func TestBuilderPrepare_VMXTemplatePath(t *testing.T) {
var b Builder
config := testConfig()
// Test bad
config["vmx_template_path"] = "/i/dont/exist/forreal"
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test good
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
if _, err := tf.Write([]byte("HELLO!")); err != nil {
t.Fatalf("err: %s", err)
}
config["vmx_template_path"] = tf.Name()
b = Builder{}
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Bad template
tf2, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf2.Name())
defer tf2.Close()
if _, err := tf2.Write([]byte("{{foo}")); err != nil {
t.Fatalf("err: %s", err)
}
config["vmx_template_path"] = tf2.Name()
b = Builder{}
err = b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_VNCPort(t *testing.T) {
var b Builder
config := testConfig()
+1 -1
View File
@@ -15,7 +15,7 @@ type Driver interface {
CompactDisk(string) error
// CreateDisk creates a virtual disk with the given size.
CreateDisk(string, string, string) error
CreateDisk(string, string) error
// Checks if the VMX file at the given path is running.
IsRunning(string) (bool, error)
+2 -2
View File
@@ -28,8 +28,8 @@ func (d *Fusion5Driver) CompactDisk(diskPath string) error {
return nil
}
func (d *Fusion5Driver) CreateDisk(output string, size string, type_id string) error {
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
func (d *Fusion5Driver) CreateDisk(output string, size string) error {
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
if _, _, err := runAndLog(cmd); err != nil {
return err
}
+2 -2
View File
@@ -51,12 +51,12 @@ func (d *Player5LinuxDriver) qemuCompactDisk(diskPath string) error {
return nil
}
func (d *Player5LinuxDriver) CreateDisk(output string, size string, type_id string) error {
func (d *Player5LinuxDriver) CreateDisk(output string, size string) error {
var cmd *exec.Cmd
if d.QemuImgPath != "" {
cmd = exec.Command(d.QemuImgPath, "create", "-f", "vmdk", "-o", "compat6", output, size)
} else {
cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
}
if _, _, err := runAndLog(cmd); err != nil {
return err
+2 -2
View File
@@ -31,8 +31,8 @@ func (d *Workstation9Driver) CompactDisk(diskPath string) error {
return nil
}
func (d *Workstation9Driver) CreateDisk(output string, size string, type_id string) error {
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
func (d *Workstation9Driver) CreateDisk(output string, size string) error {
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
if _, _, err := runAndLog(cmd); err != nil {
return err
}
+11 -43
View File
@@ -4,17 +4,16 @@ import (
gossh "code.google.com/p/go.crypto/ssh"
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh"
"io/ioutil"
"log"
"os"
)
func sshAddress(state multistep.StateBag) (string, error) {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
vmxPath := state.Get("vmx_path").(string)
func sshAddress(state map[string]interface{}) (string, error) {
config := state["config"].(*config)
driver := state["driver"].(Driver)
vmxPath := state["vmx_path"].(string)
log.Println("Lookup up IP information...")
f, err := os.Open(vmxPath)
@@ -59,46 +58,15 @@ func sshAddress(state multistep.StateBag) (string, error) {
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil
}
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(*config)
auth := []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
}
if config.SSHKeyPath != "" {
keyring, err := sshKeyToKeyring(config.SSHKeyPath)
if err != nil {
return nil, err
}
auth = append(auth, gossh.ClientAuthKeyring(keyring))
}
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
config := state["config"].(*config)
return &gossh.ClientConfig{
User: config.SSHUser,
Auth: auth,
Auth: []gossh.ClientAuth{
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
gossh.ClientAuthKeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
},
}, nil
}
func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
keyBytes, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(string(keyBytes)); err != nil {
return nil, err
}
return keyring, nil
}
+5 -5
View File
@@ -23,9 +23,9 @@ var KeepFileExtensions = []string{".nvram", ".vmdk", ".vmsd", ".vmx", ".vmxf"}
// <nothing>
type stepCleanFiles struct{}
func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
func (stepCleanFiles) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
ui.Say("Deleting unnecessary VMware files...")
visit := func(path string, info os.FileInfo, err error) error {
@@ -55,11 +55,11 @@ func (stepCleanFiles) Run(state multistep.StateBag) multistep.StepAction {
}
if err := filepath.Walk(config.OutputDir, visit); err != nil {
state.Put("error", err)
state["error"] = err
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepCleanFiles) Cleanup(multistep.StateBag) {}
func (stepCleanFiles) Cleanup(map[string]interface{}) {}
+9 -9
View File
@@ -22,20 +22,20 @@ import (
// <nothing>
type stepCleanVMX struct{}
func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
func (s stepCleanVMX) Run(state map[string]interface{}) multistep.StepAction {
isoPath := state["iso_path"].(string)
ui := state["ui"].(packer.Ui)
vmxPath := state["vmx_path"].(string)
ui.Say("Cleaning VMX prior to finishing up...")
vmxData, err := s.readVMX(vmxPath)
if err != nil {
state.Put("error", fmt.Errorf("Error reading VMX: %s", err))
state["error"] = fmt.Errorf("Error reading VMX: %s", err)
return multistep.ActionHalt
}
if _, ok := state.GetOk("floppy_path"); ok {
if _, ok := state["floppy_path"]; ok {
// Delete the floppy0 entries so the floppy is no longer mounted
ui.Message("Unmounting floppy from VMX...")
for k, _ := range vmxData {
@@ -47,7 +47,7 @@ func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
vmxData["floppy0.present"] = "FALSE"
}
ui.Message("Detaching ISO from CD-ROM device...")
ui.Message("Detatching ISO from CD-ROM device...")
devRe := regexp.MustCompile(`^ide\d:\d\.`)
for k, _ := range vmxData {
match := devRe.FindString(k)
@@ -67,14 +67,14 @@ func (s stepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
// Rewrite the VMX
if err := WriteVMX(vmxPath, vmxData); err != nil {
state.Put("error", fmt.Errorf("Error writing VMX: %s", err))
state["error"] = fmt.Errorf("Error writing VMX: %s", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepCleanVMX) Cleanup(multistep.StateBag) {}
func (stepCleanVMX) Cleanup(map[string]interface{}) {}
func (stepCleanVMX) readVMX(vmxPath string) (map[string]string, error) {
vmxF, err := os.Open(vmxPath)
+7 -7
View File
@@ -20,11 +20,11 @@ import (
// <nothing>
type stepCompactDisk struct{}
func (stepCompactDisk) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
full_disk_path := state.Get("full_disk_path").(string)
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...")
@@ -33,11 +33,11 @@ func (stepCompactDisk) Run(state multistep.StateBag) multistep.StepAction {
ui.Say("Compacting the disk image")
if err := driver.CompactDisk(full_disk_path); err != nil {
state.Put("error", fmt.Errorf("Error compacting disk: %s", err))
state["error"] = fmt.Errorf("Error compacting disk: %s", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepCompactDisk) Cleanup(multistep.StateBag) {}
func (stepCompactDisk) Cleanup(map[string]interface{}) {}
+9 -9
View File
@@ -22,15 +22,15 @@ import (
// vnc_port uint - The port that VNC is configured to listen on.
type stepConfigureVNC struct{}
func (stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)
func (stepConfigureVNC) Run(state map[string]interface{}) multistep.StepAction {
config := state["config"].(*config)
ui := state["ui"].(packer.Ui)
vmxPath := state["vmx_path"].(string)
f, err := os.Open(vmxPath)
if err != nil {
err := fmt.Errorf("Error reading VMX data: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -38,7 +38,7 @@ func (stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
vmxBytes, err := ioutil.ReadAll(f)
if err != nil {
err := fmt.Errorf("Error reading VMX data: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
@@ -67,15 +67,15 @@ func (stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
if err := WriteVMX(vmxPath, vmxData); err != nil {
err := fmt.Errorf("Error writing VMX data: %s", err)
state.Put("error", err)
state["error"] = err
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("vnc_port", vncPort)
state["vnc_port"] = vncPort
return multistep.ActionContinue
}
func (stepConfigureVNC) Cleanup(multistep.StateBag) {
func (stepConfigureVNC) Cleanup(map[string]interface{}) {
}

Some files were not shown because too many files have changed in this diff Show More