Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d1cc53d45 |
@@ -1,6 +1,5 @@
|
||||
/bin
|
||||
/local
|
||||
/pkg
|
||||
/src
|
||||
/website/.sass-cache
|
||||
/website/build
|
||||
|
||||
-158
@@ -1,161 +1,3 @@
|
||||
## 0.2.1 (July 26, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* New builder: `amazon-instance` can create instance-storage backed
|
||||
AMIs.
|
||||
* VMware builder now works with Workstation 9 on Linux.
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* builder/amazon/all: Ctrl-C while waiting for state change works
|
||||
* builder/amazon/ebs: Can now launch instances into a VPC for added protection [GH-210]
|
||||
* builder/virtualbox,vmware: Add backspace, delete, and F1-F12 keys to the boot
|
||||
command.
|
||||
* builder/virtualbox: massive performance improvements with big ISO files because
|
||||
an expensive copy is avoided. [GH-202]
|
||||
* builder/vmware: CD is removed prior to exporting final machine. [GH-198]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* builder/amazon/all: Gracefully handle when AMI appears to not exist
|
||||
while AWS state is propogating. [GH-207]
|
||||
* builder/virtualbox: Trim carriage returns for Windows to properly
|
||||
detect VM state on Windows. [GH-218]
|
||||
* core: build names no longer cause invalid config errors. [GH-197]
|
||||
* command/build: If any builds fail, exit with non-zero exit status.
|
||||
* communicator/ssh: SCP exit codes are tested and errors are reported. [GH-195]
|
||||
* communicator/ssh: Properly change slash direction for Windows hosts. [GH-218]
|
||||
|
||||
## 0.2.0 (July 16, 2013)
|
||||
|
||||
BACKWARDS INCOMPATIBILITIES:
|
||||
|
||||
* "iso_md5" in the virtualbox and vmware builders is replaced with
|
||||
"iso_checksum" and "iso_checksum_type" (with the latter set to "md5").
|
||||
See the announce below on `packer fix` to automatically fix your templates.
|
||||
|
||||
FEATURES:
|
||||
|
||||
* **NEW COMMAND:** `packer fix` will attempt to fix templates from older
|
||||
versions of Packer that are now broken due to backwards incompatibilities.
|
||||
This command will fix the backwards incompatibilities introduced in this
|
||||
version.
|
||||
* Amazon EBS builder can now optionally use a pre-made security group
|
||||
instead of randomly generating one.
|
||||
* DigitalOcean API key and client IDs can now be passed in as
|
||||
environmental variables. See the documentatin for more details.
|
||||
* VirtualBox and VMware can now have `floppy_files` specified to attach
|
||||
floppy disks when booting. This allows for unattended Windows installs.
|
||||
* `packer build` has a new `-force` flag that forces the removal of
|
||||
existing artifacts if they exist. [GH-173]
|
||||
* You can now log to a file (instead of just stderr) by setting the
|
||||
`PACKER_LOG_FILE` environmental variable. [GH-168]
|
||||
* Checksums other than MD5 can now be used. SHA1 and SHA256 can also
|
||||
be used. See the documentation on `iso_checksum_type` for more info. [GH-175]
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* core: invalid keys in configuration are now considered validation
|
||||
errors. [GH-104]
|
||||
* core: all builders now share a common SSH connection core, improving
|
||||
SSH reliability over all the builders.
|
||||
* amazon-ebs: Credentials will come from IAM role if available. [GH-160]
|
||||
* amazon-ebs: Verify the source AMI is EBS-backed before launching. [GH-169]
|
||||
* shell provisioner: the build name and builder type are available in
|
||||
the `PACKER_BUILD_NAME` and `PACKER_BUILDER_TYPE` env vars by default,
|
||||
respectively. [GH-154]
|
||||
* vmware: error if shutdown command has non-zero exit status.
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: UI messages are now properly prefixed with spaces again.
|
||||
* core: If SSH connection ends, re-connection attempts will take
|
||||
place. [GH-152]
|
||||
* virtualbox: "paused" doesn't mean the VM is stopped, improving
|
||||
shutdown detection.
|
||||
* vmware: error if guest IP could not be detected. [GH-189]
|
||||
|
||||
## 0.1.5 (July 7, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* "file" uploader will upload files from the machine running Packer to the
|
||||
remote machine.
|
||||
* VirtualBox guest additions URL and checksum can now be specified, allowing
|
||||
the VirtualBox builder to have the ability to be used completely offline.
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* core: If SCP is not available, a more descriptive error message
|
||||
is shown telling the user. [GH-127]
|
||||
* shell: Scripts are now executed by default according to their shebang,
|
||||
not with `/bin/sh`. [GH-105]
|
||||
* shell: You can specify what interpreter you want inline scripts to
|
||||
run with `inline_shebang`.
|
||||
* virtualbox: Delete the packer-made SSH port forwarding prior to
|
||||
exporting the VM.
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: Non-200 response codes on downloads now show proper errors.
|
||||
[GH-141]
|
||||
* amazon-ebs: SSH handshake is retried. [GH-130]
|
||||
* vagrant: The `BuildName` template propery works properly in
|
||||
the output path.
|
||||
* vagrant: Properly configure the provider-specific post-processors so
|
||||
things like `vagrantfile_template` work. [GH-129]
|
||||
* vagrant: Close filehandles when copying files so Windows can
|
||||
rename files. [GH-100]
|
||||
|
||||
## 0.1.4 (July 2, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* virtualbox: Can now be built headless with the "Headless" option. [GH-99]
|
||||
* virtualbox: <wait5> and <wait10> codes for waiting 5 and 10 seconds
|
||||
during the boot sequence, respectively. [GH-97]
|
||||
* vmware: Can now be built headless with the "Headless" option. [GH-99]
|
||||
* vmware: <wait5> and <wait10> codes for waiting 5 and 10 seconds
|
||||
during the boot sequence, respectively. [GH-97]
|
||||
* vmware: Disks are defragmented and compacted at the end of the build.
|
||||
This can be disabled using "skip_compaction"
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* core: Template syntax errors now show line and character number. [GH-56]
|
||||
* amazon-ebs: Access key and secret access key default to
|
||||
environmental variables. [GH-40]
|
||||
* virtualbox: Send password for keyboard-interactive auth [GH-121]
|
||||
* vmware: Send password for keyboard-interactive auth [GH-121]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* vmware: Wait until shut down cleans up properly to avoid corrupt
|
||||
disk files [GH-111]
|
||||
|
||||
## 0.1.3 (July 1, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* The VMware builder can now upload the VMware tools for you into
|
||||
the VM. This is opt-in, you must specify the `tools_upload_flavor`
|
||||
option. See the website for more documentation.
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* digitalocean: Errors contain human-friendly error messages. [GH-85]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: More plugin server fixes that avoid hangs on OS X 10.7 [GH-87]
|
||||
* vagrant: AWS boxes will keep the AMI artifact around [GH-55]
|
||||
* virtualbox: More robust version parsing for uploading guest additions. [GH-69]
|
||||
* virtualbox: Output dir and VM name defaults depend on build name,
|
||||
avoiding collisions. [GH-91]
|
||||
* vmware: Output dir and VM name defaults depend on build name,
|
||||
avoiding collisions. [GH-91]
|
||||
|
||||
## 0.1.2 (June 29, 2013)
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
# Contributing to Packer
|
||||
|
||||
**First:** if you're unsure or afraid of _anything_, just ask
|
||||
or submit the issue or pull request anyways. You won't be yelled at for
|
||||
giving your best effort. The worst that can happen is that you'll be
|
||||
politely asked to change something. We appreciate any sort of contributions,
|
||||
and don't want a wall of rules to get in the way of that.
|
||||
|
||||
However, for those individuals who want a bit more guidance on the
|
||||
best way to contribute to the project, read on. This document will cover
|
||||
what we're looking for. By addressing all the points we're looking for,
|
||||
it raises the chances we can quickly merge or address your contributions.
|
||||
|
||||
## Issues
|
||||
|
||||
### Reporting an Issue
|
||||
|
||||
* Make sure you test against the latest released version. It is possible
|
||||
we already fixed the bug you're experiencing.
|
||||
|
||||
* Provide a reproducible test case. If a contributor can't reproduce an
|
||||
issue, then it dramatically lowers the chances it'll get fixed. And in
|
||||
some cases, the issue will eventually be closed.
|
||||
|
||||
* Respond promptly to any questions made by the Packer team to your issue.
|
||||
Stale issues will be closed.
|
||||
|
||||
### Issue Lifecycle
|
||||
|
||||
1. The issue is reported.
|
||||
|
||||
2. The issue is verified and categorized by a Packer collaborator.
|
||||
Categorization is done via tags. For example, bugs are marked as "bugs"
|
||||
and easy fixes are marked as "easy".
|
||||
|
||||
3. Unless it is critical, the issue is left for a period of time (sometimes
|
||||
many weeks), giving outside contributors a chance to address the issue.
|
||||
|
||||
4. The issue is addressed in a pull request or commit. The issue will be
|
||||
referenced in the commit message so that the code that fixes it is clearly
|
||||
linked.
|
||||
|
||||
5. The issue is closed.
|
||||
|
||||
## Setting up Go to work on Packer
|
||||
|
||||
If you have never worked with Go before, you will have to complete the
|
||||
following steps in order to be able to compile and test Packer.
|
||||
|
||||
1. Install Go. On a Mac, you can `brew install go`.
|
||||
|
||||
2. Set and export the `GOPATH` environment variable. For example, you can
|
||||
add `export GOPATH=$HOME/Documents/golang` to your `.bash_profile`.
|
||||
|
||||
3. Download the Packer source (and its dependencies) by running
|
||||
`go get github.com/mitchellh/packer`. This will download the Packer
|
||||
source to `$GOPATH/src/github.com/mitchellh/packer`.
|
||||
|
||||
4. Make your changes to the Packer source. You can run `make` from the main
|
||||
source directory to recompile all the binaries. Any compilation errors
|
||||
will be shown when the binaries are rebuilding.
|
||||
|
||||
5. Test your changes by running `make test` and then running
|
||||
`$GOPATH/src/github.com/mitchellh/packer/bin/packer` to build a machine.
|
||||
|
||||
6. If everything works well and the tests pass, run `go fmt` on your code
|
||||
before submitting a pull request.
|
||||
@@ -1,7 +1,7 @@
|
||||
NO_COLOR=\033[0m
|
||||
OK_COLOR=\033[32;01m
|
||||
ERROR_COLOR=\033[31;01m
|
||||
WARN_COLOR=\033[33;01m
|
||||
NO_COLOR=\x1b[0m
|
||||
OK_COLOR=\x1b[32;01m
|
||||
ERROR_COLOR=\x1b[31;01m
|
||||
WARN_COLOR=\x1b[33;01m
|
||||
|
||||
all:
|
||||
@mkdir -p bin/
|
||||
@@ -10,15 +10,13 @@ all:
|
||||
@echo "$(OK_COLOR)==> Building$(NO_COLOR)"
|
||||
@./scripts/build.sh
|
||||
|
||||
clean:
|
||||
@rm -rf bin/ local/ pkg/ src/ website/.sass-cache website/build
|
||||
|
||||
format:
|
||||
go fmt ./...
|
||||
|
||||
test:
|
||||
@echo "$(OK_COLOR)==> Testing Packer...$(NO_COLOR)"
|
||||
@go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs -n1 go get -d
|
||||
@go list -f '{{range .TestImports}}{{.}}\
|
||||
{{end}}' ./... | xargs -n1 go get -d
|
||||
go test ./...
|
||||
|
||||
.PHONY: all format test
|
||||
|
||||
@@ -11,7 +11,7 @@ Packer is lightweight, runs on every major operating system, and is highly
|
||||
performant, creating machine images for multiple platforms in parallel.
|
||||
Packer comes out of the box with support for creating AMIs (EC2), VMware
|
||||
images, and VirtualBox images. Support for more platforms can be added via
|
||||
plugins.
|
||||
plugins.
|
||||
|
||||
The images that Packer creates can easily be turned into
|
||||
[Vagrant](http://www.vagrantup.com) boxes.
|
||||
@@ -70,15 +70,8 @@ http://www.packer.io/docs
|
||||
## Developing Packer
|
||||
|
||||
If you wish to work on Packer itself, you'll first need [Go](http://golang.org)
|
||||
installed (version 1.1+ is _required_). Make sure you have Go properly installed,
|
||||
including setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH).
|
||||
|
||||
For some additional dependencies, Go needs [Mercurial](http://mercurial.selenic.com/)
|
||||
to be installed. Packer itself doesn't require this but a dependency of a
|
||||
dependency does.
|
||||
|
||||
Next, clone this repository into `$GOPATH/src/github.com/mitchellh/packer` and
|
||||
then just type `make`. In a few moments, you'll have a working `packer` executable:
|
||||
installed (version 1.1+ is _required_). Next, clone this repository then just type `make`.
|
||||
In a few moments, you'll have a working `packer` executable:
|
||||
|
||||
```
|
||||
$ make
|
||||
@@ -87,6 +80,6 @@ $ bin/packer
|
||||
...
|
||||
```
|
||||
|
||||
You can run tests by typing `make test`.
|
||||
You can run tests by typing `make test`.
|
||||
|
||||
This will run tests for Packer core along with all the core builders and commands and such that come with Packer.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
* builder/amazonebs: Copy AMI to multiple regions
|
||||
* builder/vmware: VMX templates
|
||||
* communicator/ssh: Ability to re-establish connection
|
||||
* communicator/ssh: Download()
|
||||
* packer: Communicator should have Close() method
|
||||
* packer/plugin: Better error messages/detection if plugin crashes
|
||||
* provisioner/shell: Arguments
|
||||
@@ -1,21 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
)
|
||||
|
||||
// AccessConfig is for common configuration related to AWS access
|
||||
type AccessConfig struct {
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
}
|
||||
|
||||
// Auth returns a valid aws.Auth object for access to AWS services, or
|
||||
// an error if the authentication couldn't be resolved.
|
||||
func (c *AccessConfig) Auth() (aws.Auth, error) {
|
||||
return aws.GetAuth(c.AccessKey, c.SecretKey)
|
||||
}
|
||||
|
||||
func (c *AccessConfig) Prepare() []error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// WaitForAMI waits for the given AMI ID to become ready.
|
||||
func WaitForAMI(c *ec2.EC2, imageId string) error {
|
||||
for {
|
||||
imageResp, err := c.Images([]string{imageId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
|
||||
log.Println("AMI not found, probably state issues on AWS side. Trying again.")
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if imageResp.Images[0].State == "available" {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Image in state %s, sleeping 2s before checking again",
|
||||
imageResp.Images[0].State)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StateChangeConf struct {
|
||||
Conn *ec2.EC2
|
||||
Instance *ec2.Instance
|
||||
Pending []string
|
||||
StepState map[string]interface{}
|
||||
Target string
|
||||
}
|
||||
|
||||
func WaitForState(conf *StateChangeConf) (i *ec2.Instance, err error) {
|
||||
log.Printf("Waiting for instance state to become: %s", conf.Target)
|
||||
|
||||
i = conf.Instance
|
||||
for i.State.Name != conf.Target {
|
||||
if conf.StepState != nil {
|
||||
if _, ok := conf.StepState[multistep.StateCancelled]; ok {
|
||||
return nil, errors.New("interrupted")
|
||||
}
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, allowed := range conf.Pending {
|
||||
if i.State.Name == allowed {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
fmt.Errorf("unexpected state '%s', wanted target '%s'", i.State.Name, conf.Target)
|
||||
return
|
||||
}
|
||||
|
||||
var resp *ec2.InstancesResp
|
||||
resp, err = conf.Conn.Instances([]string{i.InstanceId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
i = &resp.Reservations[0].Instances[0]
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RunConfig contains configuration for running an instance from a source
|
||||
// AMI and details on how to access that launched image.
|
||||
type RunConfig struct {
|
||||
Region string
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
RawSSHTimeout string `mapstructure:"ssh_timeout"`
|
||||
SSHUsername string `mapstructure:"ssh_username"`
|
||||
SSHPort int `mapstructure:"ssh_port"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SubnetId string `mapstructure:"subnet_id"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
|
||||
// Unexported fields that are calculated from others
|
||||
sshTimeout time.Duration
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare() []error {
|
||||
// Defaults
|
||||
if c.SSHPort == 0 {
|
||||
c.SSHPort = 22
|
||||
}
|
||||
|
||||
if c.RawSSHTimeout == "" {
|
||||
c.RawSSHTimeout = "1m"
|
||||
}
|
||||
|
||||
// Validation
|
||||
var err error
|
||||
errs := make([]error, 0)
|
||||
if c.SourceAmi == "" {
|
||||
errs = append(errs, errors.New("A source_ami must be specified"))
|
||||
}
|
||||
|
||||
if c.InstanceType == "" {
|
||||
errs = append(errs, errors.New("An instance_type must be specified"))
|
||||
}
|
||||
|
||||
if c.Region == "" {
|
||||
errs = append(errs, errors.New("A region must be specified"))
|
||||
} else if _, ok := aws.Regions[c.Region]; !ok {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", c.Region))
|
||||
}
|
||||
|
||||
if c.SSHUsername == "" {
|
||||
errs = append(errs, errors.New("An ssh_username must be specified"))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Clear out the AWS access key env vars so they don't
|
||||
// affect our tests.
|
||||
os.Setenv("AWS_ACCESS_KEY_ID", "")
|
||||
os.Setenv("AWS_ACCESS_KEY", "")
|
||||
os.Setenv("AWS_SECRET_ACCESS_KEY", "")
|
||||
os.Setenv("AWS_SECRET_KEY", "")
|
||||
}
|
||||
|
||||
func testConfig() *RunConfig {
|
||||
return &RunConfig{
|
||||
Region: "us-east-1",
|
||||
SourceAmi: "abcd",
|
||||
InstanceType: "m1.small",
|
||||
SSHUsername: "root",
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare(t *testing.T) {
|
||||
c := testConfig()
|
||||
err := c.Prepare()
|
||||
if len(err) > 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_InstanceType(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.InstanceType = ""
|
||||
if err := c.Prepare(); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_Region(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.Region = ""
|
||||
if err := c.Prepare(); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c.Region = "us-east-12"
|
||||
if err := c.Prepare(); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c.Region = "us-east-1"
|
||||
if err := c.Prepare(); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceAmi(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.SourceAmi = ""
|
||||
if err := c.Prepare(); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHPort(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.SSHPort = 0
|
||||
if err := c.Prepare(); 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(); 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 := testConfig()
|
||||
c.RawSSHTimeout = ""
|
||||
if err := c.Prepare(); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c.RawSSHTimeout = "bad"
|
||||
if err := c.Prepare(); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHUsername(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.SSHUsername = ""
|
||||
if err := c.Prepare(); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
)
|
||||
|
||||
// SSHAddress returns a function that can be given to the SSH communicator
|
||||
// for determining the SSH address based on the instance DNS name.
|
||||
func SSHAddress(port int) func(map[string]interface{}) (string, error) {
|
||||
return func(state map[string]interface{}) (string, error) {
|
||||
var host string
|
||||
instance := state["instance"].(*ec2.Instance)
|
||||
if instance.VpcId != "" {
|
||||
host = instance.PrivateIpAddress
|
||||
} else {
|
||||
host = instance.DNSName
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", host, port), nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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(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 {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthKeyring(keyring),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
type StepRunSourceInstance struct {
|
||||
ExpectedRootDevice string
|
||||
InstanceType string
|
||||
SourceAMI string
|
||||
SubnetId string
|
||||
|
||||
instance *ec2.Instance
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
runOpts := &ec2.RunInstances{
|
||||
KeyName: keyName,
|
||||
ImageId: s.SourceAMI,
|
||||
InstanceType: s.InstanceType,
|
||||
MinCount: 0,
|
||||
MaxCount: 0,
|
||||
SecurityGroups: []ec2.SecurityGroup{ec2.SecurityGroup{Id: securityGroupId}},
|
||||
SubnetId: s.SubnetId,
|
||||
}
|
||||
|
||||
ui.Say("Launching a source AWS instance...")
|
||||
imageResp, err := ec2conn.Images([]string{s.SourceAMI}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("There was a problem with the source AMI: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if s.ExpectedRootDevice != "" && imageResp.Images[0].RootDeviceType != s.ExpectedRootDevice {
|
||||
state["error"] = fmt.Errorf(
|
||||
"The provided source AMI has an invalid root device type.\n"+
|
||||
"Expected '%s', got '%s'.",
|
||||
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["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.instance = &runResp.Instances[0]
|
||||
log.Printf("instance id: %s", s.instance.InstanceId)
|
||||
|
||||
ui.Say(fmt.Sprintf("Waiting for instance (%s) to become ready...", s.instance.InstanceId))
|
||||
stateChange := StateChangeConf{
|
||||
Conn: ec2conn,
|
||||
Instance: s.instance,
|
||||
Pending: []string{"pending"},
|
||||
Target: "running",
|
||||
StepState: state,
|
||||
}
|
||||
s.instance, err = WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", s.instance.InstanceId, err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["instance"] = s.instance
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepRunSourceInstance) Cleanup(state map[string]interface{}) {
|
||||
if s.instance == nil {
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
stateChange := StateChangeConf{
|
||||
Conn: ec2conn,
|
||||
Instance: s.instance,
|
||||
Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
|
||||
Target: "running",
|
||||
}
|
||||
|
||||
WaitForState(&stateChange)
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
// The amazonebs package contains a packer.Builder implementation that
|
||||
// builds AMIs for Amazon EC2.
|
||||
//
|
||||
// In general, there are two types of AMIs that can be created: ebs-backed or
|
||||
// instance-store. This builder _only_ builds ebs-backed images.
|
||||
package ebs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
const BuilderId = "mitchellh.amazonebs"
|
||||
|
||||
type config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
|
||||
// Configuration of the resulting AMI
|
||||
AMIName string `mapstructure:"ami_name"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare()...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare()...)
|
||||
|
||||
// Accumulate any errors
|
||||
if b.config.AMIName == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("ami_name must be specified"))
|
||||
} else {
|
||||
_, err = template.New("ami").Parse(b.config.AMIName)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing ami_name: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
region, ok := aws.Regions[b.config.Region]
|
||||
if !ok {
|
||||
panic("region not found")
|
||||
}
|
||||
|
||||
auth, err := b.config.AccessConfig.Auth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ec2conn := ec2.New(auth, region)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := make(map[string]interface{})
|
||||
state["config"] = b.config
|
||||
state["ec2"] = ec2conn
|
||||
state["hook"] = hook
|
||||
state["ui"] = ui
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&awscommon.StepKeyPair{},
|
||||
&awscommon.StepSecurityGroup{
|
||||
SecurityGroupId: b.config.SecurityGroupId,
|
||||
SSHPort: b.config.SSHPort,
|
||||
VpcId: b.config.VpcId,
|
||||
},
|
||||
&awscommon.StepRunSourceInstance{
|
||||
ExpectedRootDevice: "ebs",
|
||||
InstanceType: b.config.InstanceType,
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SubnetId: b.config.SubnetId,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: awscommon.SSHAddress(b.config.SSHPort),
|
||||
SSHConfig: awscommon.SSHConfig(b.config.SSHUsername),
|
||||
SSHWaitTimeout: b.config.SSHTimeout(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&stepStopInstance{},
|
||||
&stepCreateAMI{},
|
||||
}
|
||||
|
||||
// Run!
|
||||
if b.config.PackerDebug {
|
||||
b.runner = &multistep.DebugRunner{
|
||||
Steps: steps,
|
||||
PauseFn: common.MultistepDebugFn(ui),
|
||||
}
|
||||
} else {
|
||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||
}
|
||||
|
||||
b.runner.Run(state)
|
||||
|
||||
// If there was an error, return that
|
||||
if rawErr, ok := state["error"]; ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// If there are no AMIs, then just return
|
||||
if _, ok := state["amis"]; !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Build the artifact and return it
|
||||
artifact := &awscommon.Artifact{
|
||||
Amis: state["amis"].(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Conn: ec2conn,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package ebs
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"access_key": "foo",
|
||||
"secret_key": "bar",
|
||||
"source_ami": "foo",
|
||||
"instance_type": "foo",
|
||||
"region": "us-east-1",
|
||||
"ssh_username": "root",
|
||||
"ami_name": "foo",
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packer.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_Prepare_BadType(t *testing.T) {
|
||||
b := &Builder{}
|
||||
c := map[string]interface{}{
|
||||
"access_key": []string{},
|
||||
}
|
||||
|
||||
err := b.Prepare(c)
|
||||
if err == nil {
|
||||
t.Fatalf("prepare should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
config["ami_name"] = "foo {{"
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "ami_name")
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_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")
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
// The instance package contains a packer.Builder implementation that builds
|
||||
// AMIs for Amazon EC2 backed by instance storage, as opposed to EBS storage.
|
||||
package instance
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
const BuilderId = "mitchellh.amazon.instance"
|
||||
|
||||
// Config is the configuration that is chained through the steps and
|
||||
// settable from the template.
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
awscommon.AccessConfig `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
|
||||
AccountId string `mapstructure:"account_id"`
|
||||
AMIName string `mapstructure:"ami_name"`
|
||||
BundleDestination string `mapstructure:"bundle_destination"`
|
||||
BundlePrefix string `mapstructure:"bundle_prefix"`
|
||||
BundleUploadCommand string `mapstructure:"bundle_upload_command"`
|
||||
BundleVolCommand string `mapstructure:"bundle_vol_command"`
|
||||
S3Bucket string `mapstructure:"s3_bucket"`
|
||||
X509CertPath string `mapstructure:"x509_cert_path"`
|
||||
X509KeyPath string `mapstructure:"x509_key_path"`
|
||||
X509UploadPath string `mapstructure:"x509_upload_path"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if b.config.BundleDestination == "" {
|
||||
b.config.BundleDestination = "/tmp"
|
||||
}
|
||||
|
||||
if b.config.BundlePrefix == "" {
|
||||
b.config.BundlePrefix = "image-{{.CreateTime}}"
|
||||
}
|
||||
|
||||
if b.config.BundleUploadCommand == "" {
|
||||
b.config.BundleUploadCommand = "sudo -n ec2-upload-bundle " +
|
||||
"-b {{.BucketName}} " +
|
||||
"-m {{.ManifestPath}} " +
|
||||
"-a {{.AccessKey}} " +
|
||||
"-s {{.SecretKey}} " +
|
||||
"-d {{.BundleDirectory}} " +
|
||||
"--batch " +
|
||||
"--retry"
|
||||
}
|
||||
|
||||
if b.config.BundleVolCommand == "" {
|
||||
b.config.BundleVolCommand = "sudo -n ec2-bundle-vol " +
|
||||
"-k {{.KeyPath}} " +
|
||||
"-u {{.AccountId}} " +
|
||||
"-c {{.CertPath}} " +
|
||||
"-r {{.Architecture}} " +
|
||||
"-e {{.PrivatePath}} " +
|
||||
"-d {{.Destination}} " +
|
||||
"-p {{.Prefix}} " +
|
||||
"--batch"
|
||||
}
|
||||
|
||||
if b.config.X509UploadPath == "" {
|
||||
b.config.X509UploadPath = "/tmp"
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare()...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare()...)
|
||||
|
||||
if b.config.AccountId == "" {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New("account_id is required"))
|
||||
} else {
|
||||
b.config.AccountId = strings.Replace(b.config.AccountId, "-", "", -1)
|
||||
}
|
||||
|
||||
if b.config.AMIName == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("ami_name must be specified"))
|
||||
} else {
|
||||
_, err = template.New("ami").Parse(b.config.AMIName)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing ami_name: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.S3Bucket == "" {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New("s3_bucket is required"))
|
||||
}
|
||||
|
||||
if b.config.X509CertPath == "" {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New("x509_cert_path is required"))
|
||||
} else if _, err := os.Stat(b.config.X509CertPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("x509_cert_path points to bad file: %s", err))
|
||||
}
|
||||
|
||||
if b.config.X509KeyPath == "" {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New("x509_key_path is required"))
|
||||
} else if _, err := os.Stat(b.config.X509KeyPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("x509_key_path points to bad file: %s", err))
|
||||
}
|
||||
|
||||
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) {
|
||||
region, ok := aws.Regions[b.config.Region]
|
||||
if !ok {
|
||||
panic("region not found")
|
||||
}
|
||||
|
||||
auth, err := b.config.AccessConfig.Auth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ec2conn := ec2.New(auth, region)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := make(map[string]interface{})
|
||||
state["config"] = &b.config
|
||||
state["ec2"] = ec2conn
|
||||
state["hook"] = hook
|
||||
state["ui"] = ui
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&awscommon.StepKeyPair{},
|
||||
&awscommon.StepSecurityGroup{
|
||||
SecurityGroupId: b.config.SecurityGroupId,
|
||||
SSHPort: b.config.SSHPort,
|
||||
VpcId: b.config.VpcId,
|
||||
},
|
||||
&awscommon.StepRunSourceInstance{
|
||||
ExpectedRootDevice: "instance-store",
|
||||
InstanceType: b.config.InstanceType,
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SubnetId: b.config.SubnetId,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: awscommon.SSHAddress(b.config.SSHPort),
|
||||
SSHConfig: awscommon.SSHConfig(b.config.SSHUsername),
|
||||
SSHWaitTimeout: b.config.SSHTimeout(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&StepUploadX509Cert{},
|
||||
&StepBundleVolume{},
|
||||
&StepUploadBundle{},
|
||||
&StepRegisterAMI{},
|
||||
}
|
||||
|
||||
// Run!
|
||||
if b.config.PackerDebug {
|
||||
b.runner = &multistep.DebugRunner{
|
||||
Steps: steps,
|
||||
PauseFn: common.MultistepDebugFn(ui),
|
||||
}
|
||||
} else {
|
||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||
}
|
||||
|
||||
b.runner.Run(state)
|
||||
|
||||
// If there was an error, return that
|
||||
if rawErr, ok := state["error"]; ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// If there are no AMIs, then just return
|
||||
if _, ok := state["amis"]; !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Build the artifact and return it
|
||||
artifact := &awscommon.Artifact{
|
||||
Amis: state["amis"].(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Conn: ec2conn,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"account_id": "foo",
|
||||
"ami_name": "foo",
|
||||
"instance_type": "m1.small",
|
||||
"region": "us-east-1",
|
||||
"s3_bucket": "foo",
|
||||
"source_ami": "foo",
|
||||
"ssh_username": "bob",
|
||||
"x509_cert_path": tf.Name(),
|
||||
"x509_key_path": tf.Name(),
|
||||
"x509_upload_path": "/foo",
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packer.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_AccountId(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["account_id"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["account_id"] = "foo"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
|
||||
config["account_id"] = "0123-0456-7890"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AccountId != "012304567890" {
|
||||
t.Errorf("should strip hyphens: %s", b.config.AccountId)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
config["ami_name"] = "foo {{"
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "ami_name")
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_BundleDestination(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["bundle_destination"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if b.config.BundleDestination != "/tmp" {
|
||||
t.Fatalf("bad: %s", b.config.BundleDestination)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_BundlePrefix(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["bundle_prefix"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if b.config.BundlePrefix != "image-{{.CreateTime}}" {
|
||||
t.Fatalf("bad: %s", b.config.BundlePrefix)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_S3Bucket(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["s3_bucket"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["s3_bucket"] = "foo"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_X509CertPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["x509_cert_path"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["x509_cert_path"] = "i/am/a/file/that/doesnt/exist"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["x509_cert_path"] = tf.Name()
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_X509KeyPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["x509_key_path"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["x509_key_path"] = "i/am/a/file/that/doesnt/exist"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["x509_key_path"] = tf.Name()
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_X509UploadPath(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["x509_upload_path"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"strconv"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
type bundleCmdData struct {
|
||||
AccountId string
|
||||
Architecture string
|
||||
CertPath string
|
||||
Destination string
|
||||
KeyPath string
|
||||
Prefix string
|
||||
PrivatePath string
|
||||
}
|
||||
|
||||
type bundlePrefixData struct {
|
||||
CreateTime string
|
||||
}
|
||||
|
||||
type StepBundleVolume struct{}
|
||||
|
||||
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 bundlePrefix bytes.Buffer
|
||||
prefixTData := bundlePrefixData{
|
||||
CreateTime: strconv.FormatInt(time.Now().UTC().Unix(), 10),
|
||||
}
|
||||
t := template.Must(template.New("bundlePrefix").Parse(config.BundlePrefix))
|
||||
t.Execute(&bundlePrefix, prefixTData)
|
||||
|
||||
var bundleCmd bytes.Buffer
|
||||
tData := bundleCmdData{
|
||||
AccountId: config.AccountId,
|
||||
Architecture: instance.Architecture,
|
||||
CertPath: x509RemoteCertPath,
|
||||
Destination: config.BundleDestination,
|
||||
KeyPath: x509RemoteKeyPath,
|
||||
Prefix: bundlePrefix.String(),
|
||||
PrivatePath: config.X509UploadPath,
|
||||
}
|
||||
t = template.Must(template.New("bundleCmd").Parse(config.BundleVolCommand))
|
||||
t.Execute(&bundleCmd, tData)
|
||||
|
||||
ui.Say("Bundling the volume...")
|
||||
cmd = new(packer.RemoteCmd)
|
||||
cmd.Command = bundleCmd.String()
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
state["error"] = fmt.Errorf("Error bundling volume: %s", err)
|
||||
ui.Error(state["error"].(error).Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if cmd.ExitStatus != 0 {
|
||||
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 := bundlePrefix.String() + ".manifest.xml"
|
||||
state["manifest_name"] = manifestName
|
||||
state["manifest_path"] = fmt.Sprintf(
|
||||
"%s/%s", config.BundleDestination, manifestName)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepBundleVolume) Cleanup(map[string]interface{}) {}
|
||||
@@ -1,68 +0,0 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"strconv"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
type amiNameData struct {
|
||||
CreateTime string
|
||||
}
|
||||
|
||||
type StepRegisterAMI struct{}
|
||||
|
||||
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)
|
||||
|
||||
// Parse the name of the AMI
|
||||
amiNameBuf := new(bytes.Buffer)
|
||||
tData := amiNameData{
|
||||
strconv.FormatInt(time.Now().UTC().Unix(), 10),
|
||||
}
|
||||
|
||||
t := template.Must(template.New("ami").Parse(config.AMIName))
|
||||
t.Execute(amiNameBuf, tData)
|
||||
amiName := amiNameBuf.String()
|
||||
|
||||
ui.Say("Registering the AMI...")
|
||||
registerOpts := &ec2.RegisterImage{
|
||||
ImageLocation: manifestPath,
|
||||
Name: amiName,
|
||||
}
|
||||
|
||||
registerResp, err := ec2conn.RegisterImage(registerOpts)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error registering AMI: %s", err)
|
||||
ui.Error(state["error"].(error).Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the AMI ID in the state
|
||||
ui.Say(fmt.Sprintf("AMI: %s", registerResp.ImageId))
|
||||
amis := make(map[string]string)
|
||||
amis[config.Region] = registerResp.ImageId
|
||||
state["amis"] = amis
|
||||
|
||||
// Wait for the image to become ready
|
||||
ui.Say("Waiting for AMI to become ready...")
|
||||
if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil {
|
||||
err := fmt.Errorf("Error waiting for AMI: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepRegisterAMI) Cleanup(map[string]interface{}) {}
|
||||
@@ -1,61 +0,0 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type uploadCmdData struct {
|
||||
AccessKey string
|
||||
BucketName string
|
||||
BundleDirectory string
|
||||
ManifestPath string
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
type StepUploadBundle struct{}
|
||||
|
||||
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 uploadCmd bytes.Buffer
|
||||
tData := uploadCmdData{
|
||||
AccessKey: config.AccessKey,
|
||||
BucketName: config.S3Bucket,
|
||||
BundleDirectory: config.BundleDestination,
|
||||
ManifestPath: manifestPath,
|
||||
SecretKey: config.SecretKey,
|
||||
}
|
||||
t := template.Must(template.New("uploadCmd").Parse(config.BundleUploadCommand))
|
||||
t.Execute(&uploadCmd, tData)
|
||||
|
||||
ui.Say("Uploading the bundle...")
|
||||
cmd := &packer.RemoteCmd{Command: uploadCmd.String()}
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
state["error"] = fmt.Errorf("Error uploading volume: %s", err)
|
||||
ui.Error(state["error"].(error).Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if cmd.ExitStatus != 0 {
|
||||
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["remote_manifest_path"] = fmt.Sprintf(
|
||||
"%s/%s", config.S3Bucket, manifestName)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepUploadBundle) Cleanup(state map[string]interface{}) {}
|
||||
@@ -1,49 +0,0 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
)
|
||||
|
||||
type StepUploadX509Cert struct{}
|
||||
|
||||
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["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["error"] = fmt.Errorf("Error uploading X509 cert: %s", err)
|
||||
ui.Error(state["error"].(error).Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["x509RemoteCertPath"] = x509RemoteCertPath
|
||||
state["x509RemoteKeyPath"] = x509RemoteKeyPath
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepUploadX509Cert) Cleanup(map[string]interface{}) {}
|
||||
|
||||
func (s *StepUploadX509Cert) uploadSingle(comm packer.Communicator, dst, src string) error {
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return comm.Upload(dst, f)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -8,39 +8,35 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Artifact is an artifact implementation that contains built AMIs.
|
||||
type Artifact struct {
|
||||
type artifact struct {
|
||||
// A map of regions to AMI IDs.
|
||||
Amis map[string]string
|
||||
|
||||
// BuilderId is the unique ID for the builder that created this AMI
|
||||
BuilderIdValue string
|
||||
amis map[string]string
|
||||
|
||||
// EC2 connection for performing API stuff.
|
||||
Conn *ec2.EC2
|
||||
conn *ec2.EC2
|
||||
}
|
||||
|
||||
func (a *Artifact) BuilderId() string {
|
||||
return a.BuilderIdValue
|
||||
func (*artifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
func (*Artifact) Files() []string {
|
||||
func (*artifact) Files() []string {
|
||||
// We have no files
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
parts := make([]string, 0, len(a.Amis))
|
||||
for region, amiId := range a.Amis {
|
||||
func (a *artifact) Id() string {
|
||||
parts := make([]string, 0, len(a.amis))
|
||||
for region, amiId := range a.amis {
|
||||
parts = append(parts, fmt.Sprintf("%s:%s", region, amiId))
|
||||
}
|
||||
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
amiStrings := make([]string, 0, len(a.Amis))
|
||||
for region, id := range a.Amis {
|
||||
func (a *artifact) String() string {
|
||||
amiStrings := make([]string, 0, len(a.amis))
|
||||
for region, id := range a.amis {
|
||||
single := fmt.Sprintf("%s: %s", region, id)
|
||||
amiStrings = append(amiStrings, single)
|
||||
}
|
||||
@@ -48,12 +44,12 @@ func (a *Artifact) String() string {
|
||||
return fmt.Sprintf("AMIs were created:\n\n%s", strings.Join(amiStrings, "\n"))
|
||||
}
|
||||
|
||||
func (a *Artifact) Destroy() error {
|
||||
func (a *artifact) Destroy() error {
|
||||
errors := make([]error, 0)
|
||||
|
||||
for _, imageId := range a.Amis {
|
||||
for _, imageId := range a.amis {
|
||||
log.Printf("Deregistering image ID: %s", imageId)
|
||||
if _, err := a.Conn.DeregisterImage(imageId); err != nil {
|
||||
if _, err := a.conn.DeregisterImage(imageId); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
"cgl.tideland.biz/asserts"
|
||||
@@ -10,7 +10,7 @@ func TestArtifact_Impl(t *testing.T) {
|
||||
assert := asserts.NewTestingAsserts(t, true)
|
||||
|
||||
var actual packer.Artifact
|
||||
assert.Implementor(&Artifact{}, &actual, "should be an Artifact")
|
||||
assert.Implementor(&artifact{}, &actual, "should be an Artifact")
|
||||
}
|
||||
|
||||
func TestArtifactId(t *testing.T) {
|
||||
@@ -22,10 +22,7 @@ func TestArtifactId(t *testing.T) {
|
||||
amis["east"] = "foo"
|
||||
amis["west"] = "bar"
|
||||
|
||||
a := &Artifact{
|
||||
Amis: amis,
|
||||
}
|
||||
|
||||
a := &artifact{amis, nil}
|
||||
result := a.Id()
|
||||
assert.Equal(result, expected, "should match output")
|
||||
}
|
||||
@@ -42,7 +39,7 @@ west: bar`
|
||||
amis["east"] = "foo"
|
||||
amis["west"] = "bar"
|
||||
|
||||
a := &Artifact{Amis: amis}
|
||||
a := &artifact{amis, nil}
|
||||
result := a.String()
|
||||
assert.Equal(result, expected, "should match output")
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// The amazonebs package contains a packer.Builder implementation that
|
||||
// builds AMIs for Amazon EC2.
|
||||
//
|
||||
// In general, there are two types of AMIs that can be created: ebs-backed or
|
||||
// instance-store. This builder _only_ builds ebs-backed images.
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
const BuilderId = "mitchellh.amazonebs"
|
||||
|
||||
type config struct {
|
||||
// Access information
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
|
||||
// Information for the source instance
|
||||
Region string
|
||||
SourceAmi string `mapstructure:"source_ami"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
SSHUsername string `mapstructure:"ssh_username"`
|
||||
SSHPort int `mapstructure:"ssh_port"`
|
||||
SSHTimeout time.Duration
|
||||
|
||||
// Configuration of the resulting AMI
|
||||
AMIName string `mapstructure:"ami_name"`
|
||||
|
||||
PackerDebug bool `mapstructure:"packer_debug"`
|
||||
RawSSHTimeout string `mapstructure:"ssh_timeout"`
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
config config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
var err error
|
||||
|
||||
for _, raw := range raws {
|
||||
err := mapstructure.Decode(raw, &b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.SSHPort == 0 {
|
||||
b.config.SSHPort = 22
|
||||
}
|
||||
|
||||
if b.config.RawSSHTimeout == "" {
|
||||
b.config.RawSSHTimeout = "1m"
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := make([]error, 0)
|
||||
|
||||
if b.config.AccessKey == "" {
|
||||
errs = append(errs, errors.New("An access_key must be specified"))
|
||||
}
|
||||
|
||||
if b.config.SecretKey == "" {
|
||||
errs = append(errs, errors.New("A secret_key must be specified"))
|
||||
}
|
||||
|
||||
if b.config.SourceAmi == "" {
|
||||
errs = append(errs, errors.New("A source_ami must be specified"))
|
||||
}
|
||||
|
||||
if b.config.InstanceType == "" {
|
||||
errs = append(errs, errors.New("An instance_type must be specified"))
|
||||
}
|
||||
|
||||
if b.config.Region == "" {
|
||||
errs = append(errs, errors.New("A region must be specified"))
|
||||
} else if _, ok := aws.Regions[b.config.Region]; !ok {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", b.config.Region))
|
||||
}
|
||||
|
||||
if b.config.SSHUsername == "" {
|
||||
errs = append(errs, errors.New("An ssh_username must be specified"))
|
||||
}
|
||||
|
||||
b.config.SSHTimeout, err = time.ParseDuration(b.config.RawSSHTimeout)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
|
||||
}
|
||||
|
||||
if b.config.AMIName == "" {
|
||||
errs = append(errs, errors.New("ami_name must be specified"))
|
||||
} else {
|
||||
_, err = template.New("ami").Parse(b.config.AMIName)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("Failed parsing ami_name: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &packer.MultiError{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) {
|
||||
region, ok := aws.Regions[b.config.Region]
|
||||
if !ok {
|
||||
panic("region not found")
|
||||
}
|
||||
|
||||
auth := aws.Auth{b.config.AccessKey, b.config.SecretKey}
|
||||
ec2conn := ec2.New(auth, region)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := make(map[string]interface{})
|
||||
state["config"] = b.config
|
||||
state["ec2"] = ec2conn
|
||||
state["hook"] = hook
|
||||
state["ui"] = ui
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&stepKeyPair{},
|
||||
&stepSecurityGroup{},
|
||||
&stepRunSourceInstance{},
|
||||
&stepConnectSSH{},
|
||||
&stepProvision{},
|
||||
&stepStopInstance{},
|
||||
&stepCreateAMI{},
|
||||
}
|
||||
|
||||
// Run!
|
||||
if b.config.PackerDebug {
|
||||
b.runner = &multistep.DebugRunner{
|
||||
Steps: steps,
|
||||
PauseFn: common.MultistepDebugFn(ui),
|
||||
}
|
||||
} else {
|
||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||
}
|
||||
|
||||
b.runner.Run(state)
|
||||
|
||||
// If there was an error, return that
|
||||
if rawErr, ok := state["error"]; ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// If there are no AMIs, then just return
|
||||
if _, ok := state["amis"]; !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Build the artifact and return it
|
||||
artifact := &artifact{
|
||||
amis: state["amis"].(map[string]string),
|
||||
conn: ec2conn,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"access_key": "foo",
|
||||
"secret_key": "bar",
|
||||
"source_ami": "foo",
|
||||
"instance_type": "foo",
|
||||
"region": "us-east-1",
|
||||
"ssh_username": "root",
|
||||
"ami_name": "foo",
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packer.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_Prepare_BadType(t *testing.T) {
|
||||
b := &Builder{}
|
||||
c := map[string]interface{}{
|
||||
"access_key": []string{},
|
||||
}
|
||||
|
||||
err := b.Prepare(c)
|
||||
if err == nil {
|
||||
t.Fatalf("prepare should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_AccessKey(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["access_key"] = "foo"
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.AccessKey != "foo" {
|
||||
t.Errorf("access key invalid: %s", b.config.AccessKey)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "access_key")
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_AMIName(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["ami_name"] = "foo"
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
config["ami_name"] = "foo {{"
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "ami_name")
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InstanceType(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["instance_type"] = "foo"
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.InstanceType != "foo" {
|
||||
t.Errorf("invalid: %s", b.config.InstanceType)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "instance_type")
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_Region(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["region"] = "us-east-1"
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.Region != "us-east-1" {
|
||||
t.Errorf("invalid: %s", b.config.Region)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "region")
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test invalid
|
||||
config["region"] = "i-am-not-real"
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SecretKey(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["secret_key"] = "foo"
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.SecretKey != "foo" {
|
||||
t.Errorf("secret key invalid: %s", b.config.SecretKey)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "secret_key")
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SourceAmi(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["source_ami"] = "foo"
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.SourceAmi != "foo" {
|
||||
t.Errorf("invalid: %s", b.config.SourceAmi)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "source_ami")
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SSHPort(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.SSHPort != 22 {
|
||||
t.Errorf("invalid: %d", b.config.SSHPort)
|
||||
}
|
||||
|
||||
// Test set
|
||||
config["ssh_port"] = 35
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.SSHPort != 35 {
|
||||
t.Errorf("invalid: %d", b.config.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SSHTimeout(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test with a bad value
|
||||
config["ssh_timeout"] = "this is not good"
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
config["ssh_timeout"] = "5s"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SSHUsername(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["ssh_username"] = "foo"
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.SSHUsername != "foo" {
|
||||
t.Errorf("invalid: %s", b.config.SSHUsername)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "ssh_username")
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package ebs
|
||||
package amazonebs
|
||||
|
||||
// This hook is fired prior to launching the EC2 instance.
|
||||
const HookPreLaunch = "amazonebs_pre_launch"
|
||||
@@ -0,0 +1,39 @@
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
func waitForState(ec2conn *ec2.EC2, originalInstance *ec2.Instance, pending []string, target string) (i *ec2.Instance, err error) {
|
||||
log.Printf("Waiting for instance state to become: %s", target)
|
||||
|
||||
i = originalInstance
|
||||
for i.State.Name != target {
|
||||
found := false
|
||||
for _, allowed := range pending {
|
||||
if i.State.Name == allowed {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
fmt.Errorf("unexpected state '%s', wanted target '%s'", i.State.Name, target)
|
||||
return
|
||||
}
|
||||
|
||||
var resp *ec2.InstancesResp
|
||||
resp, err = ec2conn.Instances([]string{i.InstanceId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
i = &resp.Reservations[0].Instances[0]
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stepConnectSSH struct {
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(config)
|
||||
instance := state["instance"].(*ec2.Instance)
|
||||
privateKey := state["privateKey"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
// Build the keyring for authentication. This stores the private key
|
||||
// we'll use to authenticate.
|
||||
keyring := &ssh.SimpleKeychain{}
|
||||
err := keyring.AddPEMKey(privateKey)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Build the actual SSH client configuration
|
||||
sshConfig := &gossh.ClientConfig{
|
||||
User: config.SSHUsername,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthKeyring(keyring),
|
||||
},
|
||||
}
|
||||
|
||||
// Start trying to connect to SSH
|
||||
connected := make(chan bool, 1)
|
||||
connectQuit := make(chan bool, 1)
|
||||
defer func() {
|
||||
connectQuit <- true
|
||||
}()
|
||||
|
||||
go func() {
|
||||
var err error
|
||||
|
||||
ui.Say("Connecting to the instance via SSH...")
|
||||
attempts := 0
|
||||
for {
|
||||
select {
|
||||
case <-connectQuit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
attempts += 1
|
||||
log.Printf(
|
||||
"Opening TCP conn for SSH to %s:%d (attempt %d)",
|
||||
instance.DNSName, config.SSHPort, attempts)
|
||||
s.conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", instance.DNSName, config.SSHPort))
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// A brief sleep so we're not being overly zealous attempting
|
||||
// to connect to the instance.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
connected <- true
|
||||
}()
|
||||
|
||||
log.Printf("Waiting up to %s for SSH connection", config.SSHTimeout)
|
||||
timeout := time.After(config.SSHTimeout)
|
||||
|
||||
ConnectWaitLoop:
|
||||
for {
|
||||
select {
|
||||
case <-connected:
|
||||
// We connected. Just break the loop.
|
||||
break ConnectWaitLoop
|
||||
case <-timeout:
|
||||
err := errors.New("Timeout waiting for SSH to become available.")
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var comm packer.Communicator
|
||||
if err == nil {
|
||||
comm, err = ssh.New(s.conn, sshConfig)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error connecting to SSH: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the communicator on the state bag so it can be used later
|
||||
state["communicator"] = comm
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConnectSSH) Cleanup(map[string]interface{}) {
|
||||
if s.conn != nil {
|
||||
s.conn.Close()
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
package ebs
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"strconv"
|
||||
"text/template"
|
||||
"time"
|
||||
@@ -57,11 +57,23 @@ func (s *stepCreateAMI) Run(state map[string]interface{}) multistep.StepAction {
|
||||
|
||||
// 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["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
for {
|
||||
imageResp, err := ec2conn.Images([]string{createResp.ImageId}, ec2.NewFilter())
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error querying images: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if imageResp.Images[0].State == "available" {
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Image in state %s, sleeping 2s before checking again",
|
||||
imageResp.Images[0].State)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
"cgl.tideland.biz/identifier"
|
||||
@@ -10,11 +10,11 @@ import (
|
||||
"log"
|
||||
)
|
||||
|
||||
type StepKeyPair struct {
|
||||
type stepKeyPair struct {
|
||||
keyName string
|
||||
}
|
||||
|
||||
func (s *StepKeyPair) Run(state map[string]interface{}) multistep.StepAction {
|
||||
func (s *stepKeyPair) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
@@ -23,7 +23,9 @@ func (s *StepKeyPair) Run(state map[string]interface{}) multistep.StepAction {
|
||||
log.Printf("temporary keypair name: %s", keyName)
|
||||
keyResp, err := ec2conn.CreateKeyPair(keyName)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error creating temporary keypair: %s", err)
|
||||
err := fmt.Errorf("Error creating temporary keypair: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
@@ -37,7 +39,7 @@ func (s *StepKeyPair) Run(state map[string]interface{}) multistep.StepAction {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepKeyPair) Cleanup(state map[string]interface{}) {
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
@@ -6,18 +6,9 @@ import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// StepProvision runs the provisioners.
|
||||
//
|
||||
// Uses:
|
||||
// communicator packer.Communicator
|
||||
// hook packer.Hook
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// <nothing>
|
||||
type StepProvision struct{}
|
||||
type stepProvision struct{}
|
||||
|
||||
func (*StepProvision) Run(state map[string]interface{}) multistep.StepAction {
|
||||
func (*stepProvision) Run(state map[string]interface{}) multistep.StepAction {
|
||||
comm := state["communicator"].(packer.Communicator)
|
||||
hook := state["hook"].(packer.Hook)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
@@ -31,4 +22,4 @@ func (*StepProvision) Run(state map[string]interface{}) multistep.StepAction {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepProvision) Cleanup(map[string]interface{}) {}
|
||||
func (*stepProvision) Cleanup(map[string]interface{}) {}
|
||||
@@ -0,0 +1,73 @@
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
type stepRunSourceInstance struct {
|
||||
instance *ec2.Instance
|
||||
}
|
||||
|
||||
func (s *stepRunSourceInstance) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(config)
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
keyName := state["keyPair"].(string)
|
||||
securityGroupId := state["securityGroupId"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
runOpts := &ec2.RunInstances{
|
||||
KeyName: keyName,
|
||||
ImageId: config.SourceAmi,
|
||||
InstanceType: config.InstanceType,
|
||||
MinCount: 0,
|
||||
MaxCount: 0,
|
||||
SecurityGroups: []ec2.SecurityGroup{ec2.SecurityGroup{Id: securityGroupId}},
|
||||
}
|
||||
|
||||
ui.Say("Launching a source AWS instance...")
|
||||
runResp, err := ec2conn.RunInstances(runOpts)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error launching source instance: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.instance = &runResp.Instances[0]
|
||||
log.Printf("instance id: %s", s.instance.InstanceId)
|
||||
|
||||
ui.Say("Waiting for instance to become ready...")
|
||||
s.instance, err = waitForState(ec2conn, s.instance, []string{"pending"}, "running")
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for instance to become ready: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["instance"] = s.instance
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepRunSourceInstance) Cleanup(state map[string]interface{}) {
|
||||
if s.instance == nil {
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
pending := []string{"pending", "running", "shutting-down", "stopped", "stopping"}
|
||||
waitForState(ec2conn, s.instance, pending, "terminated")
|
||||
}
|
||||
+14
-32
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
"cgl.tideland.biz/identifier"
|
||||
@@ -10,52 +10,34 @@ import (
|
||||
"log"
|
||||
)
|
||||
|
||||
type StepSecurityGroup struct {
|
||||
SecurityGroupId string
|
||||
SSHPort int
|
||||
VpcId string
|
||||
|
||||
createdGroupId string
|
||||
type stepSecurityGroup struct {
|
||||
groupId string
|
||||
}
|
||||
|
||||
func (s *StepSecurityGroup) Run(state map[string]interface{}) multistep.StepAction {
|
||||
func (s *stepSecurityGroup) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(config)
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
if s.SecurityGroupId != "" {
|
||||
log.Printf("Using specified security group: %s", s.SecurityGroupId)
|
||||
state["securityGroupId"] = s.SecurityGroupId
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.SSHPort == 0 {
|
||||
panic("SSHPort must be set to a non-zero value.")
|
||||
}
|
||||
|
||||
// Create the group
|
||||
ui.Say("Creating temporary security group for this instance...")
|
||||
groupName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw()))
|
||||
log.Printf("Temporary group name: %s", groupName)
|
||||
group := ec2.SecurityGroup{
|
||||
Name: groupName,
|
||||
Description: "Temporary group for Packer",
|
||||
VpcId: s.VpcId,
|
||||
}
|
||||
groupResp, err := ec2conn.CreateSecurityGroup(group)
|
||||
groupResp, err := ec2conn.CreateSecurityGroup(groupName, "Temporary group for Packer")
|
||||
if err != nil {
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the group ID so we can delete it later
|
||||
s.createdGroupId = groupResp.Id
|
||||
s.groupId = groupResp.Id
|
||||
|
||||
// Authorize the SSH access
|
||||
perms := []ec2.IPPerm{
|
||||
ec2.IPPerm{
|
||||
Protocol: "tcp",
|
||||
FromPort: s.SSHPort,
|
||||
ToPort: s.SSHPort,
|
||||
FromPort: config.SSHPort,
|
||||
ToPort: config.SSHPort,
|
||||
SourceIPs: []string{"0.0.0.0/0"},
|
||||
},
|
||||
}
|
||||
@@ -69,13 +51,13 @@ func (s *StepSecurityGroup) Run(state map[string]interface{}) multistep.StepActi
|
||||
}
|
||||
|
||||
// Set some state data for use in future steps
|
||||
state["securityGroupId"] = s.createdGroupId
|
||||
state["securityGroupId"] = s.groupId
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepSecurityGroup) Cleanup(state map[string]interface{}) {
|
||||
if s.createdGroupId == "" {
|
||||
func (s *stepSecurityGroup) Cleanup(state map[string]interface{}) {
|
||||
if s.groupId == "" {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,10 +65,10 @@ func (s *StepSecurityGroup) Cleanup(state map[string]interface{}) {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say("Deleting temporary security group...")
|
||||
_, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: s.createdGroupId})
|
||||
_, err := ec2conn.DeleteSecurityGroup(ec2.SecurityGroup{Id: s.groupId})
|
||||
if err != nil {
|
||||
log.Printf("Error deleting security group: %s", err)
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error cleaning up security group. Please delete the group manually: %s", s.createdGroupId))
|
||||
"Error cleaning up security group. Please delete the group manually: %s", s.groupId))
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
package ebs
|
||||
package amazonebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
@@ -27,14 +26,7 @@ func (s *stepStopInstance) Run(state map[string]interface{}) multistep.StepActio
|
||||
|
||||
// Wait for the instance to actual stop
|
||||
ui.Say("Waiting for the instance to stop...")
|
||||
stateChange := awscommon.StateChangeConf{
|
||||
Conn: ec2conn,
|
||||
Instance: instance,
|
||||
Pending: []string{"running", "stopping"},
|
||||
Target: "stopped",
|
||||
StepState: state,
|
||||
}
|
||||
instance, err = awscommon.WaitForState(&stateChange)
|
||||
instance, err = waitForState(ec2conn, instance, []string{"running", "stopping"}, "stopped")
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for instance to stop: %s", err)
|
||||
state["error"] = err
|
||||
@@ -1,57 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CheckUnusedConfig is a helper that makes sure that the there are no
|
||||
// unused configuration keys, properly ignoring keys that don't matter.
|
||||
func CheckUnusedConfig(md *mapstructure.Metadata) *packer.MultiError {
|
||||
errs := make([]error, 0)
|
||||
|
||||
if md.Unused != nil && len(md.Unused) > 0 {
|
||||
sort.Strings(md.Unused)
|
||||
for _, unused := range md.Unused {
|
||||
if unused != "type" && !strings.HasPrefix(unused, "packer_") {
|
||||
errs = append(
|
||||
errs, fmt.Errorf("Unknown configuration key: %s", unused))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &packer.MultiError{errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeConfig is a helper that handles decoding raw configuration using
|
||||
// mapstructure. It returns the metadata and any errors that may happen.
|
||||
// If you need extra configuration for mapstructure, you should configure
|
||||
// it manually and not use this helper function.
|
||||
func DecodeConfig(target interface{}, raws ...interface{}) (*mapstructure.Metadata, error) {
|
||||
var md mapstructure.Metadata
|
||||
decoderConfig := &mapstructure.DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: target,
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(decoderConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, raw := range raws {
|
||||
err := decoder.Decode(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &md, nil
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckUnusedConfig(t *testing.T) {
|
||||
md := &mapstructure.Metadata{
|
||||
Unused: make([]string, 0),
|
||||
}
|
||||
|
||||
err := CheckUnusedConfig(md)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
md.Unused = []string{"foo", "bar"}
|
||||
err = CheckUnusedConfig(md)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeConfig(t *testing.T) {
|
||||
type Local struct {
|
||||
Foo string
|
||||
Bar string
|
||||
}
|
||||
|
||||
raws := []interface{}{
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"bar": "baz",
|
||||
"baz": "what",
|
||||
},
|
||||
}
|
||||
|
||||
var result Local
|
||||
md, err := DecodeConfig(&result, raws...)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if result.Foo != "bar" {
|
||||
t.Fatalf("invalid: %#v", result.Foo)
|
||||
}
|
||||
|
||||
if result.Bar != "baz" {
|
||||
t.Fatalf("invalid: %#v", result.Bar)
|
||||
}
|
||||
|
||||
if md == nil {
|
||||
t.Fatal("metadata should not be nil")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(md.Unused, []string{"baz"}) {
|
||||
t.Fatalf("unused: %#v", md.Unused)
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,6 @@ package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -49,21 +46,6 @@ type DownloadClient struct {
|
||||
downloader Downloader
|
||||
}
|
||||
|
||||
// HashForType returns the Hash implementation for the given string
|
||||
// type, or nil if the type is not supported.
|
||||
func HashForType(t string) hash.Hash {
|
||||
switch t {
|
||||
case "md5":
|
||||
return md5.New()
|
||||
case "sha1":
|
||||
return sha1.New()
|
||||
case "sha256":
|
||||
return sha256.New()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewDownloadClient returns a new DownloadClient for the given
|
||||
// configuration.
|
||||
func NewDownloadClient(c *DownloadConfig) *DownloadClient {
|
||||
@@ -125,9 +107,6 @@ func (d *DownloadClient) Get() (string, error) {
|
||||
|
||||
log.Printf("Downloading: %s", url.String())
|
||||
err = d.downloader.Download(f, url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if d.config.Hash != nil {
|
||||
@@ -181,22 +160,11 @@ func (*HTTPDownloader) Cancel() {
|
||||
}
|
||||
|
||||
func (d *HTTPDownloader) Download(dst io.Writer, src *url.URL) error {
|
||||
log.Printf("Starting download: %s", src.String())
|
||||
resp, err := http.Get(src.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.Printf(
|
||||
"Non-200 status code: %d. Getting error body.", resp.StatusCode)
|
||||
|
||||
errorBody := new(bytes.Buffer)
|
||||
io.Copy(errorBody, resp.Body)
|
||||
return fmt.Errorf("HTTP error '%d'! Remote side responded:\n%s",
|
||||
resp.StatusCode, errorBody.String())
|
||||
}
|
||||
|
||||
d.progress = 0
|
||||
d.total = uint(resp.ContentLength)
|
||||
|
||||
|
||||
@@ -40,48 +40,3 @@ func TestDownloadClient_VerifyChecksum(t *testing.T) {
|
||||
t.Fatal("didn't verify")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashForType(t *testing.T) {
|
||||
if h := HashForType("md5"); h == nil {
|
||||
t.Fatalf("md5 hash is nil")
|
||||
} else {
|
||||
h.Write([]byte("foo"))
|
||||
result := h.Sum(nil)
|
||||
|
||||
expected := "acbd18db4cc2f85cedef654fccc4a4d8"
|
||||
actual := hex.EncodeToString(result)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad hash: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
if h := HashForType("sha1"); h == nil {
|
||||
t.Fatalf("sha1 hash is nil")
|
||||
} else {
|
||||
h.Write([]byte("foo"))
|
||||
result := h.Sum(nil)
|
||||
|
||||
expected := "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
|
||||
actual := hex.EncodeToString(result)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad hash: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
if h := HashForType("sha256"); h == nil {
|
||||
t.Fatalf("sha256 hash is nil")
|
||||
} else {
|
||||
h.Write([]byte("foo"))
|
||||
result := h.Sum(nil)
|
||||
|
||||
expected := "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
|
||||
actual := hex.EncodeToString(result)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad hash: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
if HashForType("fake") != nil {
|
||||
t.Fatalf("fake hash is not nil")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package common
|
||||
|
||||
// PackerConfig is a struct that contains the configuration keys that
|
||||
// are sent by packer, properly tagged already so mapstructure can load
|
||||
// them. Embed this structure into your configuration class to get it.
|
||||
type PackerConfig struct {
|
||||
PackerBuildName string `mapstructure:"packer_build_name"`
|
||||
PackerDebug bool `mapstructure:"packer_debug"`
|
||||
PackerForce bool `mapstructure:"packer_force"`
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StepConnectSSH is a multistep Step implementation that waits for SSH
|
||||
// to become available. It gets the connection information from a single
|
||||
// configuration when creating the step.
|
||||
//
|
||||
// Uses:
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// communicator packer.Communicator
|
||||
type StepConnectSSH struct {
|
||||
// SSHAddress is a function that returns the TCP address to connect to
|
||||
// for SSH. This is a function so that you can query information
|
||||
// if necessary for this address.
|
||||
SSHAddress func(map[string]interface{}) (string, error)
|
||||
|
||||
// SSHConfig is a function that returns the proper client configuration
|
||||
// for SSH access.
|
||||
SSHConfig func(map[string]interface{}) (*gossh.ClientConfig, error)
|
||||
|
||||
// SSHWaitTimeout is the total timeout to wait for SSH to become available.
|
||||
SSHWaitTimeout time.Duration
|
||||
|
||||
cancel bool
|
||||
comm packer.Communicator
|
||||
}
|
||||
|
||||
func (s *StepConnectSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
var comm packer.Communicator
|
||||
var err error
|
||||
|
||||
waitDone := make(chan bool, 1)
|
||||
go func() {
|
||||
ui.Say("Waiting for SSH to become available...")
|
||||
comm, err = s.waitForSSH(state)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
log.Printf("Waiting for SSH, up to timeout: %s", s.SSHWaitTimeout)
|
||||
timeout := time.After(s.SSHWaitTimeout)
|
||||
WaitLoop:
|
||||
for {
|
||||
// Wait for either SSH to become available, a timeout to occur,
|
||||
// or an interrupt to come through.
|
||||
select {
|
||||
case <-waitDone:
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error waiting for SSH: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Connected to SSH!")
|
||||
s.comm = comm
|
||||
state["communicator"] = comm
|
||||
break WaitLoop
|
||||
case <-timeout:
|
||||
ui.Error("Timeout waiting for SSH.")
|
||||
s.cancel = true
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
// The step sequence was cancelled, so cancel waiting for SSH
|
||||
// and just start the halting process.
|
||||
s.cancel = true
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepConnectSSH) Cleanup(map[string]interface{}) {
|
||||
}
|
||||
|
||||
func (s *StepConnectSSH) waitForSSH(state map[string]interface{}) (packer.Communicator, error) {
|
||||
handshakeAttempts := 0
|
||||
|
||||
var comm packer.Communicator
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
if s.cancel {
|
||||
log.Println("SSH wait cancelled. Exiting loop.")
|
||||
return nil, errors.New("SSH wait cancelled")
|
||||
}
|
||||
|
||||
// First we request the TCP connection information
|
||||
address, err := s.SSHAddress(state)
|
||||
if err != nil {
|
||||
log.Printf("Error getting SSH address: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Retrieve the SSH configuration
|
||||
sshConfig, err := s.SSHConfig(state)
|
||||
if err != nil {
|
||||
log.Printf("Error getting SSH config: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Attempt to connect to SSH port
|
||||
connFunc := ssh.ConnectFunc("tcp", address, 5*time.Minute)
|
||||
nc, err := connFunc()
|
||||
if err != nil {
|
||||
log.Printf("TCP connection to SSH ip/port failed: %s", err)
|
||||
continue
|
||||
}
|
||||
nc.Close()
|
||||
|
||||
// Then we attempt to connect via SSH
|
||||
config := &ssh.Config{
|
||||
Connection: connFunc,
|
||||
SSHConfig: sshConfig,
|
||||
}
|
||||
|
||||
comm, err = ssh.New(config)
|
||||
if err != nil {
|
||||
log.Printf("SSH handshake err: %s", err)
|
||||
|
||||
// Only count this as an attempt if we were able to attempt
|
||||
// to authenticate. Note this is very brittle since it depends
|
||||
// on the string of the error... but I don't see any other way.
|
||||
if strings.Contains(err.Error(), "authenticate") {
|
||||
log.Printf("Detected authentication error. Increasing handshake attempts.")
|
||||
handshakeAttempts += 1
|
||||
}
|
||||
|
||||
if handshakeAttempts < 10 {
|
||||
// Try to connect via SSH a handful of times
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return comm, nil
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepConnectSSH_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepConnectSSH)
|
||||
if _, ok := raw.(multistep.Step); !ok {
|
||||
t.Fatalf("connect ssh should be a step")
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/go-fs"
|
||||
"github.com/mitchellh/go-fs/fat"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// StepCreateFloppy will create a floppy disk with the given files.
|
||||
// The floppy disk doesn't support sub-directories. Only files at the
|
||||
// root level are supported.
|
||||
type StepCreateFloppy struct {
|
||||
Files []string
|
||||
|
||||
floppyPath string
|
||||
}
|
||||
|
||||
func (s *StepCreateFloppy) Run(state map[string]interface{}) multistep.StepAction {
|
||||
if len(s.Files) == 0 {
|
||||
log.Println("No floppy files specified. Floppy disk will not be made.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui := state["ui"].(packer.Ui)
|
||||
ui.Say("Creating floppy disk...")
|
||||
|
||||
// Create a temporary file to be our floppy drive
|
||||
floppyF, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error creating temporary file for floppy: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer floppyF.Close()
|
||||
|
||||
// Set the path so we can remove it later
|
||||
s.floppyPath = floppyF.Name()
|
||||
|
||||
log.Printf("Floppy path: %s", floppyF.Name())
|
||||
|
||||
// Set the size of the file to be a floppy sized
|
||||
if err := floppyF.Truncate(1440 * 1024); err != nil {
|
||||
state["error"] = fmt.Errorf("Error creating floppy: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// BlockDevice backed by the file for our filesystem
|
||||
log.Println("Initializing block device backed by temporary file")
|
||||
device, err := fs.NewFileDisk(floppyF)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error creating floppy: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Format the block device so it contains a valid FAT filesystem
|
||||
log.Println("Formatting the block device with a FAT filesystem...")
|
||||
formatConfig := &fat.SuperFloppyConfig{
|
||||
FATType: fat.FAT12,
|
||||
Label: "packer",
|
||||
OEMName: "packer",
|
||||
}
|
||||
if fat.FormatSuperFloppy(device, formatConfig); err != nil {
|
||||
state["error"] = fmt.Errorf("Error creating floppy: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// The actual FAT filesystem
|
||||
log.Println("Initializing FAT filesystem on block device")
|
||||
fatFs, err := fat.New(device)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error creating floppy: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Get the root directory to the filesystem
|
||||
log.Println("Reading the root directory from the filesystem")
|
||||
rootDir, err := fatFs.RootDir()
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error creating floppy: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Go over each file and copy it.
|
||||
for _, filename := range s.Files {
|
||||
ui.Message(fmt.Sprintf("Copying: %s", filepath.Base(filename)))
|
||||
if s.addSingleFile(rootDir, filename); err != nil {
|
||||
state["error"] = fmt.Errorf("Error adding file to floppy: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
// Set the path to the floppy so it can be used later
|
||||
state["floppy_path"] = s.floppyPath
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCreateFloppy) Cleanup(map[string]interface{}) {
|
||||
if s.floppyPath != "" {
|
||||
log.Printf("Deleting floppy disk: %s", s.floppyPath)
|
||||
os.Remove(s.floppyPath)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepCreateFloppy) addSingleFile(dir fs.Directory, src string) error {
|
||||
log.Printf("Adding file to floppy: %s", src)
|
||||
|
||||
inputF, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer inputF.Close()
|
||||
|
||||
entry, err := dir.AddFile(filepath.Base(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fatFile, err := entry.File()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(fatFile, inputF); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStepProvision_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepProvision)
|
||||
if _, ok := raw.(multistep.Step); !ok {
|
||||
t.Fatalf("provision should be a step")
|
||||
}
|
||||
}
|
||||
@@ -189,25 +189,28 @@ func NewRequest(d DigitalOceanClient, path string, params string) (map[string]in
|
||||
return decodedResponse, err
|
||||
}
|
||||
|
||||
log.Printf("response from digitalocean: %s", body)
|
||||
|
||||
err = json.Unmarshal(body, &decodedResponse)
|
||||
|
||||
// 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))
|
||||
log.Printf("response from digitalocean: %v", decodedResponse)
|
||||
|
||||
// Catch all non-200 status and return an error
|
||||
if resp.StatusCode != 200 {
|
||||
err = errors.New(fmt.Sprintf("Received non-200 HTTP status from DigitalOcean: %v", resp.StatusCode))
|
||||
return decodedResponse, err
|
||||
}
|
||||
|
||||
// Check for errors sent by digitalocean
|
||||
if err != nil {
|
||||
return decodedResponse, err
|
||||
}
|
||||
|
||||
// Catch all non-OK statuses from DO and return an error
|
||||
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))
|
||||
err = errors.New(fmt.Sprintf("Received bad status from DigitalOcean: %v", status))
|
||||
return decodedResponse, err
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"text/template"
|
||||
"time"
|
||||
@@ -28,8 +28,6 @@ type snapshotNameData struct {
|
||||
// to use while communicating with DO and describes the image
|
||||
// you are creating
|
||||
type config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
ClientID string `mapstructure:"client_id"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
RegionID uint `mapstructure:"region_id"`
|
||||
@@ -39,17 +37,16 @@ type config struct {
|
||||
SnapshotName string
|
||||
SSHUsername string `mapstructure:"ssh_username"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
SSHTimeout time.Duration
|
||||
EventDelay time.Duration
|
||||
StateTimeout time.Duration
|
||||
|
||||
PackerDebug bool `mapstructure:"packer_debug"`
|
||||
|
||||
RawSnapshotName string `mapstructure:"snapshot_name"`
|
||||
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
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
@@ -58,25 +55,15 @@ type Builder struct {
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
md, err := common.DecodeConfig(&b.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, raw := range raws {
|
||||
err := mapstructure.Decode(raw, &b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
// Optional configuration with defaults
|
||||
if b.config.APIKey == "" {
|
||||
// Default to environment variable for api_key, if it exists
|
||||
b.config.APIKey = os.Getenv("DIGITALOCEAN_API_KEY")
|
||||
}
|
||||
|
||||
if b.config.ClientID == "" {
|
||||
// Default to environment variable for client_id, if it exists
|
||||
b.config.ClientID = os.Getenv("DIGITALOCEAN_CLIENT_ID")
|
||||
}
|
||||
|
||||
//
|
||||
if b.config.RegionID == 0 {
|
||||
// Default to Region "New York"
|
||||
b.config.RegionID = 1
|
||||
@@ -125,37 +112,36 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
b.config.RawStateTimeout = "6m"
|
||||
}
|
||||
|
||||
// A list of errors on the configuration
|
||||
errs := make([]error, 0)
|
||||
|
||||
// Required configurations that will display errors if not set
|
||||
//
|
||||
if b.config.ClientID == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("a client_id must be specified"))
|
||||
errs = append(errs, errors.New("a client_id must be specified"))
|
||||
}
|
||||
|
||||
if b.config.APIKey == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("an api_key must be specified"))
|
||||
errs = append(errs, errors.New("an api_key must be specified"))
|
||||
}
|
||||
|
||||
sshTimeout, err := time.ParseDuration(b.config.RawSSHTimeout)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
|
||||
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
|
||||
}
|
||||
b.config.sshTimeout = sshTimeout
|
||||
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))
|
||||
errs = append(errs, fmt.Errorf("Failed parsing event_delay: %s", err))
|
||||
}
|
||||
b.config.eventDelay = eventDelay
|
||||
b.config.EventDelay = eventDelay
|
||||
|
||||
stateTimeout, err := time.ParseDuration(b.config.RawStateTimeout)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing state_timeout: %s", err))
|
||||
errs = append(errs, fmt.Errorf("Failed parsing state_timeout: %s", err))
|
||||
}
|
||||
b.config.stateTimeout = stateTimeout
|
||||
b.config.StateTimeout = stateTimeout
|
||||
|
||||
// Parse the name of the snapshot
|
||||
snapNameBuf := new(bytes.Buffer)
|
||||
@@ -164,15 +150,14 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
t, err := template.New("snapshot").Parse(b.config.RawSnapshotName)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing snapshot_name: %s", err))
|
||||
errs = append(errs, fmt.Errorf("Failed parsing snapshot_name: %s", err))
|
||||
} else {
|
||||
t.Execute(snapNameBuf, tData)
|
||||
b.config.SnapshotName = snapNameBuf.String()
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
if len(errs) > 0 {
|
||||
return &packer.MultiError{errs}
|
||||
}
|
||||
|
||||
log.Printf("Config: %+v", b.config)
|
||||
@@ -195,12 +180,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
new(stepCreateSSHKey),
|
||||
new(stepCreateDroplet),
|
||||
new(stepDropletInfo),
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: 5 * time.Minute,
|
||||
},
|
||||
new(common.StepProvision),
|
||||
new(stepConnectSSH),
|
||||
new(stepProvision),
|
||||
new(stepPowerOff),
|
||||
new(stepSnapshot),
|
||||
}
|
||||
|
||||
@@ -2,17 +2,10 @@ package digitalocean
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Clear out the credential env vars
|
||||
os.Setenv("DIGITALOCEAN_API_KEY", "")
|
||||
os.Setenv("DIGITALOCEAN_CLIENT_ID", "")
|
||||
}
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"client_id": "foo",
|
||||
@@ -62,15 +55,6 @@ func TestBuilderPrepare_APIKey(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test env variable
|
||||
delete(config, "api_key")
|
||||
os.Setenv("DIGITALOCEAN_API_KEY", "foo")
|
||||
defer os.Setenv("DIGITALOCEAN_API_KEY", "")
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ClientID(t *testing.T) {
|
||||
@@ -95,27 +79,6 @@ func TestBuilderPrepare_ClientID(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test env variable
|
||||
delete(config, "client_id")
|
||||
os.Setenv("DIGITALOCEAN_CLIENT_ID", "foo")
|
||||
defer os.Setenv("DIGITALOCEAN_CLIENT_ID", "")
|
||||
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()
|
||||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_RegionID(t *testing.T) {
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
)
|
||||
|
||||
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 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 {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUsername,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthKeyring(keyring),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stepConnectSSH struct {
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(config)
|
||||
privateKey := state["privateKey"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
ipAddress := state["droplet_ip"]
|
||||
|
||||
// Build the keyring for authentication. This stores the private key
|
||||
// we'll use to authenticate.
|
||||
keyring := &ssh.SimpleKeychain{}
|
||||
err := keyring.AddPEMKey(privateKey)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Build the actual SSH client configuration
|
||||
sshConfig := &gossh.ClientConfig{
|
||||
User: config.SSHUsername,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthKeyring(keyring),
|
||||
},
|
||||
}
|
||||
|
||||
// Start trying to connect to SSH
|
||||
connected := make(chan error, 1)
|
||||
connectQuit := make(chan bool, 1)
|
||||
defer func() {
|
||||
connectQuit <- true
|
||||
}()
|
||||
|
||||
var comm packer.Communicator
|
||||
go func() {
|
||||
var err error
|
||||
|
||||
ui.Say("Connecting to the droplet via SSH...")
|
||||
attempts := 0
|
||||
handshakeAttempts := 0
|
||||
for {
|
||||
select {
|
||||
case <-connectQuit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
attempts += 1
|
||||
log.Printf(
|
||||
"Opening TCP conn for SSH to %s:%d (attempt %d)",
|
||||
ipAddress, config.SSHPort, attempts)
|
||||
s.conn, err = net.DialTimeout(
|
||||
"tcp",
|
||||
fmt.Sprintf("%s:%d", ipAddress, config.SSHPort),
|
||||
10*time.Second)
|
||||
if err == nil {
|
||||
log.Println("TCP connection made. Attempting SSH handshake.")
|
||||
comm, err = ssh.New(s.conn, sshConfig)
|
||||
if err == nil {
|
||||
log.Println("Connected to SSH!")
|
||||
break
|
||||
}
|
||||
|
||||
handshakeAttempts += 1
|
||||
log.Printf("SSH handshake error: %s", err)
|
||||
|
||||
if handshakeAttempts > 5 {
|
||||
connected <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// A brief sleep so we're not being overly zealous attempting
|
||||
// to connect to the instance.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
connected <- nil
|
||||
}()
|
||||
|
||||
log.Printf("Waiting up to %s for SSH connection", config.SSHTimeout)
|
||||
timeout := time.After(config.SSHTimeout)
|
||||
|
||||
ConnectWaitLoop:
|
||||
for {
|
||||
select {
|
||||
case err := <-connected:
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error connecting to SSH: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// We connected. Just break the loop.
|
||||
break ConnectWaitLoop
|
||||
case <-timeout:
|
||||
err := errors.New("Timeout waiting for SSH to become available.")
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the communicator on the state bag so it can be used later
|
||||
state["communicator"] = comm
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConnectSSH) Cleanup(map[string]interface{}) {
|
||||
if s.conn != nil {
|
||||
s.conn.Close()
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func (s *stepCreateDroplet) Cleanup(state map[string]interface{}) {
|
||||
// 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)
|
||||
time.Sleep(c.EventDelay)
|
||||
|
||||
err := client.DestroyDroplet(s.dropletId)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ func (s *stepPowerOff) Run(state map[string]interface{}) multistep.StepAction {
|
||||
// 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)
|
||||
time.Sleep(c.EventDelay)
|
||||
|
||||
// Poweroff the droplet so it can be snapshot
|
||||
err := client.PowerOffDroplet(dropletId)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
type stepProvision struct{}
|
||||
|
||||
func (*stepProvision) Run(state map[string]interface{}) multistep.StepAction {
|
||||
comm := state["communicator"].(packer.Communicator)
|
||||
hook := state["hook"].(packer.Hook)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
log.Println("Running the provision hook")
|
||||
if err := hook.Run(packer.HookProvision, ui, comm, nil); err != nil {
|
||||
state["error"] = err
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*stepProvision) Cleanup(map[string]interface{}) {}
|
||||
@@ -17,7 +17,9 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce
|
||||
attempts += 1
|
||||
|
||||
log.Printf("Checking droplet status... (attempt: %d)", attempts)
|
||||
|
||||
_, status, err := client.DropletStatus(dropletId)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
break
|
||||
@@ -35,7 +37,7 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce
|
||||
}()
|
||||
|
||||
log.Printf("Waiting for up to %s for droplet to become %s", c.RawStateTimeout, desiredState)
|
||||
timeout := time.After(c.stateTimeout)
|
||||
timeout := time.After(c.StateTimeout)
|
||||
|
||||
ActiveWaitLoop:
|
||||
for {
|
||||
|
||||
+59
-145
@@ -3,6 +3,7 @@ package virtualbox
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
@@ -24,59 +25,50 @@ type Builder struct {
|
||||
}
|
||||
|
||||
type config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
BootWait time.Duration ``
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
HTTPDir string `mapstructure:"http_directory"`
|
||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||
ISOMD5 string `mapstructure:"iso_md5"`
|
||||
ISOUrl string `mapstructure:"iso_url"`
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
ShutdownTimeout time.Duration ``
|
||||
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
|
||||
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
SSHWaitTimeout time.Duration ``
|
||||
VBoxVersionFile string `mapstructure:"virtualbox_version_file"`
|
||||
VBoxManage [][]string `mapstructure:"vboxmanage"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
FloppyFiles []string `mapstructure:"floppy_files"`
|
||||
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
|
||||
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
|
||||
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
Headless bool `mapstructure:"headless"`
|
||||
HTTPDir string `mapstructure:"http_directory"`
|
||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||
ISOChecksum string `mapstructure:"iso_checksum"`
|
||||
ISOChecksumType string `mapstructure:"iso_checksum_type"`
|
||||
ISOUrl string `mapstructure:"iso_url"`
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
|
||||
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
VBoxVersionFile string `mapstructure:"virtualbox_version_file"`
|
||||
VBoxManage [][]string `mapstructure:"vboxmanage"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
PackerDebug bool `mapstructure:"packer_debug"`
|
||||
|
||||
RawBootWait string `mapstructure:"boot_wait"`
|
||||
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
|
||||
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
|
||||
|
||||
bootWait time.Duration ``
|
||||
shutdownTimeout time.Duration ``
|
||||
sshWaitTimeout time.Duration ``
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
md, err := common.DecodeConfig(&b.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
for _, raw := range raws {
|
||||
err := mapstructure.Decode(raw, &b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.DiskSize == 0 {
|
||||
b.config.DiskSize = 40000
|
||||
}
|
||||
|
||||
if b.config.FloppyFiles == nil {
|
||||
b.config.FloppyFiles = make([]string, 0)
|
||||
}
|
||||
|
||||
if b.config.GuestAdditionsPath == "" {
|
||||
b.config.GuestAdditionsPath = "VBoxGuestAdditions.iso"
|
||||
}
|
||||
@@ -94,7 +86,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
if b.config.OutputDir == "" {
|
||||
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
|
||||
b.config.OutputDir = "virtualbox"
|
||||
}
|
||||
|
||||
if b.config.RawBootWait == "" {
|
||||
@@ -122,41 +114,27 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
if b.config.VMName == "" {
|
||||
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
|
||||
b.config.VMName = "packer"
|
||||
}
|
||||
|
||||
errs := make([]error, 0)
|
||||
|
||||
if b.config.HTTPPortMin > b.config.HTTPPortMax {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("http_port_min must be less than http_port_max"))
|
||||
errs = append(errs, errors.New("http_port_min must be less than http_port_max"))
|
||||
}
|
||||
|
||||
if b.config.ISOChecksum == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Due to large file sizes, an iso_checksum is required"))
|
||||
if b.config.ISOMD5 == "" {
|
||||
errs = append(errs, errors.New("Due to large file sizes, an iso_md5 is required"))
|
||||
} else {
|
||||
b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
|
||||
}
|
||||
|
||||
if b.config.ISOChecksumType == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("The iso_checksum_type must be specified."))
|
||||
} else {
|
||||
b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
|
||||
if h := common.HashForType(b.config.ISOChecksumType); h == nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs,
|
||||
fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType))
|
||||
}
|
||||
b.config.ISOMD5 = strings.ToLower(b.config.ISOMD5)
|
||||
}
|
||||
|
||||
if b.config.ISOUrl == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("An iso_url must be specified."))
|
||||
errs = append(errs, errors.New("An iso_url must be specified."))
|
||||
} else {
|
||||
url, err := url.Parse(b.config.ISOUrl)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("iso_url is not a valid URL: %s", err))
|
||||
errs = append(errs, fmt.Errorf("iso_url is not a valid URL: %s", err))
|
||||
} else {
|
||||
if url.Scheme == "" {
|
||||
url.Scheme = "file"
|
||||
@@ -164,8 +142,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
|
||||
if url.Scheme == "file" {
|
||||
if _, err := os.Stat(url.Path); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("iso_url points to bad file: %s", err))
|
||||
errs = append(errs, fmt.Errorf("iso_url points to bad file: %s", err))
|
||||
}
|
||||
} else {
|
||||
supportedSchemes := []string{"file", "http", "https"}
|
||||
@@ -180,74 +157,24 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
if !found {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Unsupported URL scheme in iso_url: %s", scheme))
|
||||
errs = append(errs, fmt.Errorf("Unsupported URL scheme in iso_url: %s", scheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errs == nil || len(errs.Errors) == 0 {
|
||||
if len(errs) == 0 {
|
||||
// Put the URL back together since we may have modified it
|
||||
b.config.ISOUrl = url.String()
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.GuestAdditionsSHA256 != "" {
|
||||
b.config.GuestAdditionsSHA256 = strings.ToLower(b.config.GuestAdditionsSHA256)
|
||||
if _, err := os.Stat(b.config.OutputDir); err == nil {
|
||||
errs = append(errs, errors.New("Output directory already exists. It must not exist."))
|
||||
}
|
||||
|
||||
if b.config.GuestAdditionsURL != "" {
|
||||
url, err := url.Parse(b.config.GuestAdditionsURL)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("guest_additions_url is not a valid URL: %s", err))
|
||||
} else {
|
||||
if url.Scheme == "" {
|
||||
url.Scheme = "file"
|
||||
}
|
||||
|
||||
if url.Scheme == "file" {
|
||||
if _, err := os.Stat(url.Path); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("guest_additions_url points to bad file: %s", err))
|
||||
}
|
||||
} else {
|
||||
supportedSchemes := []string{"file", "http", "https"}
|
||||
scheme := strings.ToLower(url.Scheme)
|
||||
|
||||
found := false
|
||||
for _, supported := range supportedSchemes {
|
||||
if scheme == supported {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Unsupported URL scheme in guest_additions_url: %s", scheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errs == nil || len(errs.Errors) == 0 {
|
||||
// Put the URL back together since we may have modified it
|
||||
b.config.GuestAdditionsURL = url.String()
|
||||
}
|
||||
}
|
||||
|
||||
if !b.config.PackerForce {
|
||||
if _, err := os.Stat(b.config.OutputDir); err == nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs,
|
||||
fmt.Errorf("Output directory '%s' already exists. It must not exist.", b.config.OutputDir))
|
||||
}
|
||||
}
|
||||
|
||||
b.config.bootWait, err = time.ParseDuration(b.config.RawBootWait)
|
||||
b.config.BootWait, err = time.ParseDuration(b.config.RawBootWait)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing boot_wait: %s", err))
|
||||
errs = append(errs, fmt.Errorf("Failed parsing boot_wait: %s", err))
|
||||
}
|
||||
|
||||
if b.config.RawShutdownTimeout == "" {
|
||||
@@ -258,36 +185,31 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
b.config.RawSSHWaitTimeout = "20m"
|
||||
}
|
||||
|
||||
b.config.shutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout)
|
||||
b.config.ShutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
|
||||
errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %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"))
|
||||
errs = append(errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
|
||||
}
|
||||
|
||||
if b.config.SSHUser == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("An ssh_username must be specified."))
|
||||
errs = append(errs, errors.New("An ssh_username must be specified."))
|
||||
}
|
||||
|
||||
b.config.sshWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout)
|
||||
b.config.SSHWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
|
||||
errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
|
||||
}
|
||||
|
||||
b.driver, err = b.newDriver()
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed creating VirtualBox driver: %s", err))
|
||||
errs = append(errs, fmt.Errorf("Failed creating VirtualBox driver: %s", err))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
if len(errs) > 0 {
|
||||
return &packer.MultiError{errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -298,27 +220,19 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
new(stepDownloadGuestAdditions),
|
||||
new(stepDownloadISO),
|
||||
new(stepPrepareOutputDir),
|
||||
&common.StepCreateFloppy{
|
||||
Files: b.config.FloppyFiles,
|
||||
},
|
||||
new(stepHTTPServer),
|
||||
new(stepSuppressMessages),
|
||||
new(stepCreateVM),
|
||||
new(stepCreateDisk),
|
||||
new(stepAttachISO),
|
||||
new(stepAttachFloppy),
|
||||
new(stepForwardSSH),
|
||||
new(stepVBoxManage),
|
||||
new(stepRun),
|
||||
new(stepTypeBootCommand),
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: b.config.sshWaitTimeout,
|
||||
},
|
||||
new(stepWaitForSSH),
|
||||
new(stepUploadVersion),
|
||||
new(stepUploadGuestAdditions),
|
||||
new(common.StepProvision),
|
||||
new(stepProvision),
|
||||
new(stepShutdown),
|
||||
new(stepExport),
|
||||
}
|
||||
|
||||
@@ -10,12 +10,9 @@ import (
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"iso_checksum": "foo",
|
||||
"iso_checksum_type": "md5",
|
||||
"iso_url": "http://www.google.com/",
|
||||
"ssh_username": "foo",
|
||||
|
||||
packer.BuildNameConfigKey: "foo",
|
||||
"iso_md5": "foo",
|
||||
"iso_url": "http://www.google.com/",
|
||||
"ssh_username": "foo",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +36,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||
t.Errorf("bad guest OS type: %s", b.config.GuestOSType)
|
||||
}
|
||||
|
||||
if b.config.OutputDir != "output-foo" {
|
||||
if b.config.OutputDir != "virtualbox" {
|
||||
t.Errorf("bad output dir: %s", b.config.OutputDir)
|
||||
}
|
||||
|
||||
@@ -55,7 +52,7 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||
t.Errorf("bad ssh port: %d", b.config.SSHPort)
|
||||
}
|
||||
|
||||
if b.config.VMName != "packer-foo" {
|
||||
if b.config.VMName != "packer" {
|
||||
t.Errorf("bad vm name: %s", b.config.VMName)
|
||||
}
|
||||
}
|
||||
@@ -116,38 +113,11 @@ func TestBuilderPrepare_DiskSize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "floppy_files")
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("bad err: %s", err)
|
||||
}
|
||||
|
||||
if len(b.config.FloppyFiles) != 0 {
|
||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||
}
|
||||
|
||||
config["floppy_files"] = []string{"foo", "bar"}
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{"foo", "bar"}
|
||||
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_GuestAdditionsPath(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "guest_additions_path")
|
||||
delete(config, "disk_size")
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("bad err: %s", err)
|
||||
@@ -169,81 +139,6 @@ func TestBuilderPrepare_GuestAdditionsPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_GuestAdditionsSHA256(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "guest_additions_sha256")
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("bad err: %s", err)
|
||||
}
|
||||
|
||||
if b.config.GuestAdditionsSHA256 != "" {
|
||||
t.Fatalf("bad: %s", b.config.GuestAdditionsSHA256)
|
||||
}
|
||||
|
||||
config["guest_additions_sha256"] = "FOO"
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.GuestAdditionsSHA256 != "foo" {
|
||||
t.Fatalf("bad size: %s", b.config.GuestAdditionsSHA256)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_GuestAdditionsURL(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
config["guest_additions_url"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if b.config.GuestAdditionsURL != "" {
|
||||
t.Fatalf("should be empty: %s", b.config.GuestAdditionsURL)
|
||||
}
|
||||
|
||||
config["guest_additions_url"] = "i/am/a/file/that/doesnt/exist"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
config["guest_additions_url"] = "file:i/am/a/file/that/doesnt/exist"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Error("should have error")
|
||||
}
|
||||
|
||||
config["guest_additions_url"] = "http://www.packer.io"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Errorf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["guest_additions_url"] = tf.Name()
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.GuestAdditionsURL != "file://"+tf.Name() {
|
||||
t.Fatalf("guest_additions_url should be modified: %s", b.config.GuestAdditionsURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_HTTPPort(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
@@ -272,68 +167,26 @@ func TestBuilderPrepare_HTTPPort(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ISOChecksum(t *testing.T) {
|
||||
func TestBuilderPrepare_ISOMD5(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test bad
|
||||
config["iso_checksum"] = ""
|
||||
config["iso_md5"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test good
|
||||
config["iso_checksum"] = "FOo"
|
||||
config["iso_md5"] = "FOo"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ISOChecksum != "foo" {
|
||||
t.Fatalf("should've lowercased: %s", b.config.ISOChecksum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test bad
|
||||
config["iso_checksum_type"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test good
|
||||
config["iso_checksum_type"] = "mD5"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ISOChecksumType != "md5" {
|
||||
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
|
||||
}
|
||||
|
||||
// Test unknown
|
||||
config["iso_checksum_type"] = "fake"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
if b.config.ISOMD5 != "foo" {
|
||||
t.Fatalf("should've lowercased: %s", b.config.ISOMD5)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,9 +50,6 @@ func (d *VBox42Driver) IsRunning(name string) (bool, error) {
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(stdout.String(), "\n") {
|
||||
// Need to trim off CR character when running in windows
|
||||
line = strings.TrimRight(line, "\r")
|
||||
|
||||
if line == `VMState="running"` {
|
||||
return true, nil
|
||||
}
|
||||
@@ -62,12 +59,6 @@ func (d *VBox42Driver) IsRunning(name string) (bool, error) {
|
||||
if line == `VMState="stopping"` {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// We consider "paused" to still be running. We wait for it to
|
||||
// be completely stopped or some other state.
|
||||
if line == `VMState="paused"` {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
@@ -133,12 +124,11 @@ func (d *VBox42Driver) Version() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
versionOutput := strings.TrimSpace(stdout.String())
|
||||
log.Printf("VBoxManage --version output: %s", versionOutput)
|
||||
log.Printf("VBoxManage --version output: %s", stdout.String())
|
||||
versionRe := regexp.MustCompile("[^.0-9]")
|
||||
matches := versionRe.Split(versionOutput, 2)
|
||||
matches := versionRe.Split(stdout.String(), 2)
|
||||
if len(matches) == 0 {
|
||||
return "", fmt.Errorf("No version found: %s", versionOutput)
|
||||
return "", fmt.Errorf("No version found: %s", stdout.String())
|
||||
}
|
||||
|
||||
log.Printf("VirtualBox version: %s", matches[0])
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package virtualbox
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
)
|
||||
|
||||
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 map[string]interface{}) (*gossh.ClientConfig, error) {
|
||||
config := state["config"].(*config)
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
package virtualbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// This step attaches the ISO to the virtual machine.
|
||||
//
|
||||
// Uses:
|
||||
//
|
||||
// Produces:
|
||||
type stepAttachFloppy struct {
|
||||
floppyPath string
|
||||
}
|
||||
|
||||
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["floppy_path"]; ok {
|
||||
floppyPath = floppyPathRaw.(string)
|
||||
} else {
|
||||
log.Println("No floppy disk, not attaching.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// VirtualBox is really dumb and can't figure out the format of the file
|
||||
// without an extension, so we need to add the "vfd" extension to the
|
||||
// floppy.
|
||||
floppyPath, err := s.copyFloppy(floppyPath)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error preparing floppy: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
driver := state["driver"].(Driver)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
vmName := state["vmName"].(string)
|
||||
|
||||
ui.Say("Attaching floppy disk...")
|
||||
|
||||
// Create the floppy disk controller
|
||||
command := []string{
|
||||
"storagectl", vmName,
|
||||
"--name", "Floppy Controller",
|
||||
"--add", "floppy",
|
||||
}
|
||||
if err := driver.VBoxManage(command...); err != nil {
|
||||
state["error"] = fmt.Errorf("Error creating floppy controller: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Attach the floppy to the controller
|
||||
command = []string{
|
||||
"storageattach", vmName,
|
||||
"--storagectl", "Floppy Controller",
|
||||
"--port", "0",
|
||||
"--device", "0",
|
||||
"--type", "fdd",
|
||||
"--medium", floppyPath,
|
||||
}
|
||||
if err := driver.VBoxManage(command...); err != nil {
|
||||
state["error"] = fmt.Errorf("Error attaching floppy: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Track the path so that we can unregister it from VirtualBox later
|
||||
s.floppyPath = floppyPath
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepAttachFloppy) Cleanup(state map[string]interface{}) {
|
||||
if s.floppyPath == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete the floppy disk
|
||||
defer os.Remove(s.floppyPath)
|
||||
|
||||
driver := state["driver"].(Driver)
|
||||
vmName := state["vmName"].(string)
|
||||
|
||||
command := []string{
|
||||
"storageattach", vmName,
|
||||
"--storagectl", "Floppy Controller",
|
||||
"--port", "0",
|
||||
"--device", "0",
|
||||
"--medium", "none",
|
||||
}
|
||||
|
||||
if err := driver.VBoxManage(command...); err != nil {
|
||||
log.Printf("Error unregistering floppy: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stepAttachFloppy) copyFloppy(path string) (string, error) {
|
||||
tempdir, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
floppyPath := filepath.Join(tempdir, "floppy.vfd")
|
||||
f, err := os.Create(floppyPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
sourceF, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer sourceF.Close()
|
||||
|
||||
log.Printf("Copying floppy to temp location: %s", floppyPath)
|
||||
if _, err := io.Copy(f, sourceF); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return floppyPath, nil
|
||||
}
|
||||
@@ -22,10 +22,7 @@ func (s *stepCreateVM) Run(state map[string]interface{}) multistep.StepAction {
|
||||
name := config.VMName
|
||||
|
||||
commands := make([][]string, 4)
|
||||
commands[0] = []string{
|
||||
"createvm", "--name", name,
|
||||
"--ostype", config.GuestOSType, "--register",
|
||||
}
|
||||
commands[0] = []string{"createvm", "--name", name, "--ostype", config.GuestOSType, "--register"}
|
||||
commands[1] = []string{
|
||||
"modifyvm", name,
|
||||
"--boot1", "disk", "--boot2", "dvd", "--boot3", "none", "--boot4", "none",
|
||||
|
||||
@@ -33,9 +33,7 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep
|
||||
cache := state["cache"].(packer.Cache)
|
||||
driver := state["driver"].(Driver)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
config := state["config"].(*config)
|
||||
|
||||
// Get VBox version
|
||||
version, err := driver.Version()
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error reading version for guest additions download: %s", err)
|
||||
@@ -47,18 +45,68 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep
|
||||
version = newVersion
|
||||
}
|
||||
|
||||
// First things first, we get the list of checksums for the files available
|
||||
// for this version.
|
||||
checksumsUrl := fmt.Sprintf("http://download.virtualbox.org/virtualbox/%s/SHA256SUMS", version)
|
||||
checksumsFile, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf(
|
||||
"Failed creating temporary file to store guest addition checksums: %s",
|
||||
err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
checksumsFile.Close()
|
||||
defer os.Remove(checksumsFile.Name())
|
||||
|
||||
downloadConfig := &common.DownloadConfig{
|
||||
Url: checksumsUrl,
|
||||
TargetPath: checksumsFile.Name(),
|
||||
Hash: nil,
|
||||
}
|
||||
|
||||
log.Printf("Downloading guest addition checksums: %s", checksumsUrl)
|
||||
download := common.NewDownloadClient(downloadConfig)
|
||||
checksumsPath, action := s.progressDownload(download, state)
|
||||
if action != multistep.ActionContinue {
|
||||
return action
|
||||
}
|
||||
|
||||
additionsName := fmt.Sprintf("VBoxGuestAdditions_%s.iso", version)
|
||||
|
||||
// Use provided version or get it from virtualbox.org
|
||||
var checksum string
|
||||
// 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(checksumsPath)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error opening guest addition checksums: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer checksumsF.Close()
|
||||
|
||||
if config.GuestAdditionsSHA256 != "" {
|
||||
checksum = config.GuestAdditionsSHA256
|
||||
} else {
|
||||
checksum, action = s.downloadAdditionsSHA256(state, version, additionsName)
|
||||
if action != multistep.ActionContinue {
|
||||
return action
|
||||
// We copy the contents of the file into memory. In general this file
|
||||
// is quite small so that is okay. In the future, we probably want to
|
||||
// use bufio and iterate line by line.
|
||||
var contents bytes.Buffer
|
||||
io.Copy(&contents, checksumsF)
|
||||
|
||||
checksum := ""
|
||||
for _, line := range strings.Split(contents.String(), "\n") {
|
||||
parts := strings.Fields(line)
|
||||
log.Printf("Checksum file parts: %#v", parts)
|
||||
if len(parts) != 2 {
|
||||
// Bogus line
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasSuffix(parts[1], additionsName) {
|
||||
checksum = parts[0]
|
||||
log.Printf("Guest additions checksum: %s", checksum)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if checksum == "" {
|
||||
state["error"] = fmt.Errorf("The checksum for the file '%s' could not be found.", additionsName)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
checksumBytes, err := hex.DecodeString(checksum)
|
||||
@@ -67,29 +115,23 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Use the provided source (URL or file path) or generate it
|
||||
url := config.GuestAdditionsURL
|
||||
if url == "" {
|
||||
url = fmt.Sprintf(
|
||||
"http://download.virtualbox.org/virtualbox/%s/%s",
|
||||
version,
|
||||
additionsName)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf(
|
||||
"http://download.virtualbox.org/virtualbox/%s/%s",
|
||||
version, additionsName)
|
||||
log.Printf("Guest additions URL: %s", url)
|
||||
|
||||
log.Printf("Acquiring lock to download the guest additions ISO.")
|
||||
cachePath := cache.Lock(url)
|
||||
defer cache.Unlock(url)
|
||||
|
||||
downloadConfig := &common.DownloadConfig{
|
||||
downloadConfig = &common.DownloadConfig{
|
||||
Url: url,
|
||||
TargetPath: cachePath,
|
||||
Hash: sha256.New(),
|
||||
Checksum: checksumBytes,
|
||||
}
|
||||
|
||||
download := common.NewDownloadClient(downloadConfig)
|
||||
download = common.NewDownloadClient(downloadConfig)
|
||||
ui.Say("Downloading VirtualBox guest additions. Progress will be shown periodically.")
|
||||
state["guest_additions_path"], action = s.progressDownload(download, state)
|
||||
return action
|
||||
@@ -137,72 +179,3 @@ DownloadWaitLoop:
|
||||
|
||||
return result, multistep.ActionContinue
|
||||
}
|
||||
|
||||
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("http://download.virtualbox.org/virtualbox/%s/SHA256SUMS", additionsVersion)
|
||||
|
||||
checksumsFile, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf(
|
||||
"Failed creating temporary file to store guest addition checksums: %s",
|
||||
err)
|
||||
return "", multistep.ActionHalt
|
||||
}
|
||||
defer os.Remove(checksumsFile.Name())
|
||||
|
||||
checksumsFile.Close()
|
||||
|
||||
downloadConfig := &common.DownloadConfig{
|
||||
Url: checksumsUrl,
|
||||
TargetPath: checksumsFile.Name(),
|
||||
Hash: nil,
|
||||
}
|
||||
|
||||
log.Printf("Downloading guest addition checksums: %s", checksumsUrl)
|
||||
download := common.NewDownloadClient(downloadConfig)
|
||||
checksumsPath, action := s.progressDownload(download, state)
|
||||
if action != multistep.ActionContinue {
|
||||
return "", action
|
||||
}
|
||||
|
||||
// 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(checksumsPath)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error opening guest addition checksums: %s", err)
|
||||
return "", multistep.ActionHalt
|
||||
}
|
||||
defer checksumsF.Close()
|
||||
|
||||
// We copy the contents of the file into memory. In general this file
|
||||
// is quite small so that is okay. In the future, we probably want to
|
||||
// use bufio and iterate line by line.
|
||||
var contents bytes.Buffer
|
||||
io.Copy(&contents, checksumsF)
|
||||
|
||||
checksum := ""
|
||||
for _, line := range strings.Split(contents.String(), "\n") {
|
||||
parts := strings.Fields(line)
|
||||
log.Printf("Checksum file parts: %#v", parts)
|
||||
if len(parts) != 2 {
|
||||
// Bogus line
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasSuffix(parts[1], additionsName) {
|
||||
checksum = parts[0]
|
||||
log.Printf("Guest additions checksum: %s", checksum)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if checksum == "" {
|
||||
state["error"] = fmt.Errorf("The checksum for the file '%s' could not be found.", additionsName)
|
||||
return "", multistep.ActionHalt
|
||||
}
|
||||
|
||||
return checksum, multistep.ActionContinue
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package virtualbox
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -19,14 +24,16 @@ import (
|
||||
//
|
||||
// Produces:
|
||||
// iso_path string
|
||||
type stepDownloadISO struct{}
|
||||
type stepDownloadISO struct {
|
||||
isoCopyDir string
|
||||
}
|
||||
|
||||
func (s stepDownloadISO) Run(state map[string]interface{}) multistep.StepAction {
|
||||
func (s *stepDownloadISO) Run(state map[string]interface{}) multistep.StepAction {
|
||||
cache := state["cache"].(packer.Cache)
|
||||
config := state["config"].(*config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
checksum, err := hex.DecodeString(config.ISOChecksum)
|
||||
checksum, err := hex.DecodeString(config.ISOMD5)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error parsing checksum: %s", err)
|
||||
return multistep.ActionHalt
|
||||
@@ -40,7 +47,7 @@ func (s stepDownloadISO) Run(state map[string]interface{}) multistep.StepAction
|
||||
Url: config.ISOUrl,
|
||||
TargetPath: cachePath,
|
||||
CopyFile: false,
|
||||
Hash: common.HashForType(config.ISOChecksumType),
|
||||
Hash: md5.New(),
|
||||
Checksum: checksum,
|
||||
}
|
||||
|
||||
@@ -78,10 +85,44 @@ DownloadWaitLoop:
|
||||
}
|
||||
}
|
||||
|
||||
// VirtualBox is really dumb and can't figure out that the file is an
|
||||
// ISO unless it has a ".iso" extension. We can't modify the cache
|
||||
// filenames so we just do a copy.
|
||||
tempdir, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error copying ISO: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.isoCopyDir = tempdir
|
||||
|
||||
f, err := os.Create(filepath.Join(tempdir, "image.iso"))
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error copying ISO: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
sourceF, err := os.Open(cachePath)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error copying ISO: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer sourceF.Close()
|
||||
|
||||
log.Printf("Copying ISO to temp location: %s", tempdir)
|
||||
if _, err := io.Copy(f, sourceF); err != nil {
|
||||
state["error"] = fmt.Errorf("Error copying ISO: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Path to ISO on disk: %s", cachePath)
|
||||
state["iso_path"] = cachePath
|
||||
state["iso_path"] = f.Name()
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (stepDownloadISO) Cleanup(map[string]interface{}) {}
|
||||
func (s *stepDownloadISO) Cleanup(map[string]interface{}) {
|
||||
if s.isoCopyDir != "" {
|
||||
os.RemoveAll(s.isoCopyDir)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// This step cleans up forwarded ports and exports the VM to an OVF.
|
||||
// This step creates the virtual disk that will be used as the
|
||||
// hard drive for the virtual machine.
|
||||
//
|
||||
// Uses:
|
||||
//
|
||||
@@ -21,38 +22,9 @@ func (s *stepExport) Run(state map[string]interface{}) multistep.StepAction {
|
||||
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["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["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Remove the attached floppy disk, if it exists
|
||||
if _, ok := state["floppy_path"]; ok {
|
||||
ui.Message("Removing floppy drive...")
|
||||
command := []string{
|
||||
"storageattach", vmName,
|
||||
"--storagectl", "Floppy Controller",
|
||||
"--port", "0",
|
||||
"--device", "0",
|
||||
"--medium", "none",
|
||||
}
|
||||
if err := driver.VBoxManage(command...); err != nil {
|
||||
state["error"] = fmt.Errorf("Error removing floppy: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Export the VM to an OVF
|
||||
outputPath := filepath.Join(config.OutputDir, "packer.ovf")
|
||||
|
||||
command = []string{
|
||||
command := []string{
|
||||
"export",
|
||||
vmName,
|
||||
"--output",
|
||||
|
||||
@@ -36,7 +36,7 @@ func (s *stepForwardSSH) Run(state map[string]interface{}) multistep.StepAction
|
||||
}
|
||||
}
|
||||
|
||||
// Create a forwarded port mapping to the VM
|
||||
// Attach the disk to the controller
|
||||
ui.Say(fmt.Sprintf("Creating forwarded port mapping for SSH (host port %d)", sshHostPort))
|
||||
command := []string{
|
||||
"modifyvm", vmName,
|
||||
|
||||
@@ -10,12 +10,6 @@ type stepPrepareOutputDir struct{}
|
||||
|
||||
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...")
|
||||
os.RemoveAll(config.OutputDir)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
|
||||
state["error"] = err
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package virtualbox
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
type stepProvision struct{}
|
||||
|
||||
func (*stepProvision) Run(state map[string]interface{}) multistep.StepAction {
|
||||
comm := state["communicator"].(packer.Communicator)
|
||||
hook := state["hook"].(packer.Hook)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
log.Println("Running the provision hook")
|
||||
if err := hook.Run(packer.HookProvision, ui, comm, nil); err != nil {
|
||||
state["error"] = err
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*stepProvision) Cleanup(map[string]interface{}) {}
|
||||
@@ -23,14 +23,7 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
|
||||
vmName := state["vmName"].(string)
|
||||
|
||||
ui.Say("Starting the virtual machine...")
|
||||
guiArgument := "gui"
|
||||
if config.Headless == true {
|
||||
ui.Message("WARNING: The VM will be started in headless mode, as configured.\n" +
|
||||
"In headless mode, errors during the boot sequence or OS setup\n" +
|
||||
"won't be easily visible. Use at your own discretion.")
|
||||
guiArgument = "headless"
|
||||
}
|
||||
command := []string{"startvm", vmName, "--type", guiArgument}
|
||||
command := []string{"startvm", vmName, "--type", "gui"}
|
||||
if err := driver.VBoxManage(command...); err != nil {
|
||||
err := fmt.Errorf("Error starting VM: %s", err)
|
||||
state["error"] = err
|
||||
@@ -40,9 +33,9 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
|
||||
|
||||
s.vmName = vmName
|
||||
|
||||
if int64(config.bootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait))
|
||||
time.Sleep(config.bootWait)
|
||||
if int64(config.BootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.BootWait))
|
||||
time.Sleep(config.BootWait)
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
|
||||
@@ -34,16 +34,19 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ui.Say("Gracefully halting virtual machine...")
|
||||
log.Printf("Executing shutdown command: %s", config.ShutdownCommand)
|
||||
cmd := &packer.RemoteCmd{Command: config.ShutdownCommand}
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
if err := comm.Start(cmd); err != nil {
|
||||
err := fmt.Errorf("Failed to send shutdown command: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Wait for the command to run
|
||||
cmd.Wait()
|
||||
|
||||
// Wait for the machine to actually shut down
|
||||
log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout)
|
||||
shutdownTimer := time.After(config.shutdownTimeout)
|
||||
log.Printf("Waiting max %s for shutdown to complete", config.ShutdownTimeout)
|
||||
shutdownTimer := time.After(config.ShutdownTimeout)
|
||||
for {
|
||||
running, _ := driver.IsRunning(vmName)
|
||||
if !running {
|
||||
|
||||
@@ -59,16 +59,6 @@ func (s *stepTypeBootCommand) Run(state map[string]interface{}) multistep.StepAc
|
||||
continue
|
||||
}
|
||||
|
||||
if code == "wait5" {
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
if code == "wait10" {
|
||||
time.Sleep(10 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
// Since typing is sometimes so slow, we check for an interrupt
|
||||
// in between each character.
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
@@ -91,22 +81,8 @@ func (*stepTypeBootCommand) Cleanup(map[string]interface{}) {}
|
||||
|
||||
func scancodes(message string) []string {
|
||||
special := make(map[string][]string)
|
||||
special["<bs>"] = []string{"ff", "08"}
|
||||
special["<del>"] = []string{"ff", "ff"}
|
||||
special["<enter>"] = []string{"1c", "9c"}
|
||||
special["<esc>"] = []string{"01", "81"}
|
||||
special["<f1>"] = []string{"ff", "be"}
|
||||
special["<f2>"] = []string{"ff", "bf"}
|
||||
special["<f3>"] = []string{"ff", "c0"}
|
||||
special["<f4>"] = []string{"ff", "c1"}
|
||||
special["<f5>"] = []string{"ff", "c2"}
|
||||
special["<f6>"] = []string{"ff", "c3"}
|
||||
special["<f7>"] = []string{"ff", "c4"}
|
||||
special["<f8>"] = []string{"ff", "c5"}
|
||||
special["<f9>"] = []string{"ff", "c6"}
|
||||
special["<f10>"] = []string{"ff", "c7"}
|
||||
special["<f11>"] = []string{"ff", "c8"}
|
||||
special["<f12>"] = []string{"ff", "c9"}
|
||||
special["<return>"] = []string{"1c", "9c"}
|
||||
special["<tab>"] = []string{"0f", "8f"}
|
||||
|
||||
@@ -140,23 +116,11 @@ func scancodes(message string) []string {
|
||||
var scancode []string
|
||||
|
||||
if strings.HasPrefix(message, "<wait>") {
|
||||
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
|
||||
log.Printf("Special code <wait> found, will sleep at this point.")
|
||||
scancode = []string{"wait"}
|
||||
message = message[len("<wait>"):]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<wait5>") {
|
||||
log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.")
|
||||
scancode = []string{"wait5"}
|
||||
message = message[len("<wait5>"):]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(message, "<wait10>") {
|
||||
log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.")
|
||||
scancode = []string{"wait10"}
|
||||
message = message[len("<wait10>"):]
|
||||
}
|
||||
|
||||
if scancode == nil {
|
||||
for specialCode, specialValue := range special {
|
||||
if strings.HasPrefix(message, specialCode) {
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package virtualbox
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This step waits for SSH to become available and establishes an SSH
|
||||
// connection.
|
||||
//
|
||||
// Uses:
|
||||
// config *config
|
||||
// sshHostPort uint
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// communicator packer.Communicator
|
||||
type stepWaitForSSH struct {
|
||||
cancel bool
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (s *stepWaitForSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
var comm packer.Communicator
|
||||
var err error
|
||||
|
||||
waitDone := make(chan bool, 1)
|
||||
go func() {
|
||||
comm, err = s.waitForSSH(state)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
log.Printf("Waiting for SSH, up to timeout: %s", config.SSHWaitTimeout.String())
|
||||
|
||||
timeout := time.After(config.SSHWaitTimeout)
|
||||
WaitLoop:
|
||||
for {
|
||||
// Wait for either SSH to become available, a timeout to occur,
|
||||
// or an interrupt to come through.
|
||||
select {
|
||||
case <-waitDone:
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error waiting for SSH: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["communicator"] = comm
|
||||
break WaitLoop
|
||||
case <-timeout:
|
||||
ui.Error("Timeout waiting for SSH.")
|
||||
s.cancel = true
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepWaitForSSH) Cleanup(map[string]interface{}) {
|
||||
if s.conn != nil {
|
||||
s.conn.Close()
|
||||
s.conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
// This blocks until SSH becomes available, and sends the communicator
|
||||
// on the given channel.
|
||||
func (s *stepWaitForSSH) waitForSSH(state map[string]interface{}) (packer.Communicator, error) {
|
||||
config := state["config"].(*config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
sshHostPort := state["sshHostPort"].(uint)
|
||||
|
||||
ui.Say("Waiting for SSH to become available...")
|
||||
var comm packer.Communicator
|
||||
var nc net.Conn
|
||||
for {
|
||||
if nc != nil {
|
||||
nc.Close()
|
||||
}
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
if s.cancel {
|
||||
log.Println("SSH wait cancelled. Exiting loop.")
|
||||
return nil, errors.New("SSH wait cancelled")
|
||||
}
|
||||
|
||||
// Attempt to connect to SSH port
|
||||
nc, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", sshHostPort))
|
||||
if err != nil {
|
||||
log.Printf("TCP connection to SSH ip/port failed: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Then we attempt to connect via SSH
|
||||
sshConfig := &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
},
|
||||
}
|
||||
|
||||
sshConnectSuccess := make(chan bool, 1)
|
||||
go func() {
|
||||
comm, err = ssh.New(nc, sshConfig)
|
||||
if err != nil {
|
||||
log.Printf("SSH connection fail: %s", err)
|
||||
sshConnectSuccess <- false
|
||||
return
|
||||
}
|
||||
|
||||
sshConnectSuccess <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case success := <-sshConnectSuccess:
|
||||
if !success {
|
||||
continue
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
log.Printf("SSH handshake timeout. Trying again.")
|
||||
continue
|
||||
}
|
||||
|
||||
ui.Say("Connected via SSH!")
|
||||
break
|
||||
}
|
||||
|
||||
// Store the connection so we can close it later
|
||||
s.conn = nc
|
||||
return comm, nil
|
||||
}
|
||||
+68
-114
@@ -3,6 +3,7 @@ package vmware
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/builder/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -25,51 +25,43 @@ type Builder struct {
|
||||
}
|
||||
|
||||
type config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
DiskName string `mapstructure:"vmdk_name"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
ISOMD5 string `mapstructure:"iso_md5"`
|
||||
ISOUrl string `mapstructure:"iso_url"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
HTTPDir string `mapstructure:"http_directory"`
|
||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
BootWait time.Duration ``
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
ShutdownTimeout time.Duration ``
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
SSHWaitTimeout time.Duration ``
|
||||
VMXData map[string]string `mapstructure:"vmx_data"`
|
||||
VNCPortMin uint `mapstructure:"vnc_port_min"`
|
||||
VNCPortMax uint `mapstructure:"vnc_port_max"`
|
||||
|
||||
DiskName string `mapstructure:"vmdk_name"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
FloppyFiles []string `mapstructure:"floppy_files"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
ISOChecksum string `mapstructure:"iso_checksum"`
|
||||
ISOChecksumType string `mapstructure:"iso_checksum_type"`
|
||||
ISOUrl string `mapstructure:"iso_url"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
Headless bool `mapstructure:"headless"`
|
||||
HTTPDir string `mapstructure:"http_directory"`
|
||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"`
|
||||
ToolsUploadPath string `mapstructure:"tools_upload_path"`
|
||||
VMXData map[string]string `mapstructure:"vmx_data"`
|
||||
VNCPortMin uint `mapstructure:"vnc_port_min"`
|
||||
VNCPortMax uint `mapstructure:"vnc_port_max"`
|
||||
PackerDebug bool `mapstructure:"packer_debug"`
|
||||
|
||||
RawBootWait string `mapstructure:"boot_wait"`
|
||||
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
|
||||
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
|
||||
|
||||
bootWait time.Duration ``
|
||||
shutdownTimeout time.Duration ``
|
||||
sshWaitTimeout time.Duration ``
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
md, err := common.DecodeConfig(&b.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, raw := range raws {
|
||||
err := mapstructure.Decode(raw, &b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
if b.config.DiskName == "" {
|
||||
b.config.DiskName = "disk"
|
||||
}
|
||||
@@ -78,16 +70,12 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
b.config.DiskSize = 40000
|
||||
}
|
||||
|
||||
if b.config.FloppyFiles == nil {
|
||||
b.config.FloppyFiles = make([]string, 0)
|
||||
}
|
||||
|
||||
if b.config.GuestOSType == "" {
|
||||
b.config.GuestOSType = "other"
|
||||
}
|
||||
|
||||
if b.config.VMName == "" {
|
||||
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
|
||||
b.config.VMName = "packer"
|
||||
}
|
||||
|
||||
if b.config.HTTPPortMin == 0 {
|
||||
@@ -111,49 +99,33 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
if b.config.OutputDir == "" {
|
||||
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
|
||||
b.config.OutputDir = "vmware"
|
||||
}
|
||||
|
||||
if b.config.SSHPort == 0 {
|
||||
b.config.SSHPort = 22
|
||||
}
|
||||
|
||||
if b.config.ToolsUploadPath == "" {
|
||||
b.config.ToolsUploadPath = "{{ .Flavor }}.iso"
|
||||
}
|
||||
// Accumulate any errors
|
||||
var err error
|
||||
errs := make([]error, 0)
|
||||
|
||||
if b.config.HTTPPortMin > b.config.HTTPPortMax {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("http_port_min must be less than http_port_max"))
|
||||
errs = append(errs, errors.New("http_port_min must be less than http_port_max"))
|
||||
}
|
||||
|
||||
if b.config.ISOChecksum == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("Due to large file sizes, an iso_checksum is required"))
|
||||
if b.config.ISOMD5 == "" {
|
||||
errs = append(errs, errors.New("Due to large file sizes, an iso_md5 is required"))
|
||||
} else {
|
||||
b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum)
|
||||
}
|
||||
|
||||
if b.config.ISOChecksumType == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("The iso_checksum_type must be specified."))
|
||||
} else {
|
||||
b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType)
|
||||
if h := common.HashForType(b.config.ISOChecksumType); h == nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs,
|
||||
fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType))
|
||||
}
|
||||
b.config.ISOMD5 = strings.ToLower(b.config.ISOMD5)
|
||||
}
|
||||
|
||||
if b.config.ISOUrl == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("An iso_url must be specified."))
|
||||
errs = append(errs, errors.New("An iso_url must be specified."))
|
||||
} else {
|
||||
url, err := url.Parse(b.config.ISOUrl)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("iso_url is not a valid URL: %s", err))
|
||||
errs = append(errs, fmt.Errorf("iso_url is not a valid URL: %s", err))
|
||||
} else {
|
||||
if url.Scheme == "" {
|
||||
url.Scheme = "file"
|
||||
@@ -161,8 +133,7 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
|
||||
if url.Scheme == "file" {
|
||||
if _, err := os.Stat(url.Path); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("iso_url points to bad file: %s", err))
|
||||
errs = append(errs, fmt.Errorf("iso_url points to bad file: %s", err))
|
||||
}
|
||||
} else {
|
||||
supportedSchemes := []string{"file", "http", "https"}
|
||||
@@ -177,36 +148,29 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
|
||||
if !found {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Unsupported URL scheme in iso_url: %s", scheme))
|
||||
errs = append(errs, fmt.Errorf("Unsupported URL scheme in iso_url: %s", scheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errs == nil || len(errs.Errors) == 0 {
|
||||
if len(errs) == 0 {
|
||||
// Put the URL back together since we may have modified it
|
||||
b.config.ISOUrl = url.String()
|
||||
}
|
||||
}
|
||||
|
||||
if !b.config.PackerForce {
|
||||
if _, err := os.Stat(b.config.OutputDir); err == nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs,
|
||||
fmt.Errorf("Output directory '%s' already exists. It must not exist.", b.config.OutputDir))
|
||||
}
|
||||
if _, err := os.Stat(b.config.OutputDir); err == nil {
|
||||
errs = append(errs, errors.New("Output directory already exists. It must not exist."))
|
||||
}
|
||||
|
||||
if b.config.SSHUser == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("An ssh_username must be specified."))
|
||||
errs = append(errs, errors.New("An ssh_username must be specified."))
|
||||
}
|
||||
|
||||
if b.config.RawBootWait != "" {
|
||||
b.config.bootWait, err = time.ParseDuration(b.config.RawBootWait)
|
||||
b.config.BootWait, err = time.ParseDuration(b.config.RawBootWait)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing boot_wait: %s", err))
|
||||
errs = append(errs, fmt.Errorf("Failed parsing boot_wait: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,40 +178,31 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
b.config.RawShutdownTimeout = "5m"
|
||||
}
|
||||
|
||||
b.config.shutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout)
|
||||
b.config.ShutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
|
||||
errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
|
||||
}
|
||||
|
||||
if b.config.RawSSHWaitTimeout == "" {
|
||||
b.config.RawSSHWaitTimeout = "20m"
|
||||
}
|
||||
|
||||
b.config.sshWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout)
|
||||
b.config.SSHWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
|
||||
}
|
||||
|
||||
if _, err := template.New("path").Parse(b.config.ToolsUploadPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("tools_upload_path invalid: %s", err))
|
||||
errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %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"))
|
||||
errs = append(errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
|
||||
}
|
||||
|
||||
b.driver, err = NewDriver()
|
||||
b.driver, err = b.newDriver()
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Failed creating VMware driver: %s", err))
|
||||
errs = append(errs, fmt.Errorf("Failed creating VMware driver: %s", err))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
if len(errs) > 0 {
|
||||
return &packer.MultiError{errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -258,29 +213,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
steps := []multistep.Step{
|
||||
&stepPrepareTools{},
|
||||
&stepDownloadISO{},
|
||||
&stepPrepareOutputDir{},
|
||||
&common.StepCreateFloppy{
|
||||
Files: b.config.FloppyFiles,
|
||||
},
|
||||
&stepCreateDisk{},
|
||||
&stepCreateVMX{},
|
||||
&stepHTTPServer{},
|
||||
&stepConfigureVNC{},
|
||||
&stepRun{},
|
||||
&stepTypeBootCommand{},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: b.config.sshWaitTimeout,
|
||||
},
|
||||
&stepUploadTools{},
|
||||
&common.StepProvision{},
|
||||
&stepWaitForSSH{},
|
||||
&stepProvision{},
|
||||
&stepShutdown{},
|
||||
&stepCleanFiles{},
|
||||
&stepCleanVMX{},
|
||||
&stepCompactDisk{},
|
||||
}
|
||||
|
||||
// Setup the state bag
|
||||
@@ -344,3 +288,13 @@ func (b *Builder) Cancel() {
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) newDriver() (Driver, error) {
|
||||
fusionAppPath := "/Applications/VMware Fusion.app"
|
||||
driver := &Fusion5Driver{fusionAppPath}
|
||||
if err := driver.Verify(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
+21
-123
@@ -4,19 +4,15 @@ import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"iso_checksum": "foo",
|
||||
"iso_checksum_type": "md5",
|
||||
"iso_url": "http://www.packer.io",
|
||||
"ssh_username": "foo",
|
||||
|
||||
packer.BuildNameConfigKey: "foo",
|
||||
"iso_md5": "foo",
|
||||
"iso_url": "http://www.packer.io",
|
||||
"ssh_username": "foo",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,58 +54,6 @@ func TestBuilderPrepare_BootWait(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ISOChecksum(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test bad
|
||||
config["iso_checksum"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test good
|
||||
config["iso_checksum"] = "FOo"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ISOChecksum != "foo" {
|
||||
t.Fatalf("should've lowercased: %s", b.config.ISOChecksum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test bad
|
||||
config["iso_checksum_type"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test good
|
||||
config["iso_checksum_type"] = "mD5"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ISOChecksumType != "md5" {
|
||||
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
|
||||
}
|
||||
|
||||
// Test unknown
|
||||
config["iso_checksum_type"] = "fake"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
@@ -122,15 +66,15 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||
t.Errorf("bad disk name: %s", b.config.DiskName)
|
||||
}
|
||||
|
||||
if b.config.OutputDir != "output-foo" {
|
||||
if b.config.OutputDir != "vmware" {
|
||||
t.Errorf("bad output dir: %s", b.config.OutputDir)
|
||||
}
|
||||
|
||||
if b.config.sshWaitTimeout != (20 * time.Minute) {
|
||||
t.Errorf("bad wait timeout: %s", b.config.sshWaitTimeout)
|
||||
if b.config.SSHWaitTimeout != (20 * time.Minute) {
|
||||
t.Errorf("bad wait timeout: %s", b.config.SSHWaitTimeout)
|
||||
}
|
||||
|
||||
if b.config.VMName != "packer-foo" {
|
||||
if b.config.VMName != "packer" {
|
||||
t.Errorf("bad vm name: %s", b.config.VMName)
|
||||
}
|
||||
}
|
||||
@@ -161,33 +105,6 @@ func TestBuilderPrepare_DiskSize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "floppy_files")
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("bad err: %s", err)
|
||||
}
|
||||
|
||||
if len(b.config.FloppyFiles) != 0 {
|
||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||
}
|
||||
|
||||
config["floppy_files"] = []string{"foo", "bar"}
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{"foo", "bar"}
|
||||
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
|
||||
t.Fatalf("bad: %#v", b.config.FloppyFiles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_HTTPPort(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
@@ -216,16 +133,27 @@ func TestBuilderPrepare_HTTPPort(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
func TestBuilderPrepare_ISOMD5(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
// Test bad
|
||||
config["iso_md5"] = ""
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test good
|
||||
config["iso_md5"] = "FOo"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ISOMD5 != "foo" {
|
||||
t.Fatalf("should've lowercased: %s", b.config.ISOMD5)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ISOUrl(t *testing.T) {
|
||||
@@ -381,36 +309,6 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ToolsUploadPath(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test a default
|
||||
delete(config, "tools_upload_path")
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ToolsUploadPath == "" {
|
||||
t.Fatalf("bad value: %s", b.config.ToolsUploadPath)
|
||||
}
|
||||
|
||||
// Test with a bad value
|
||||
config["tools_upload_path"] = "{{{nope}"
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
config["tools_upload_path"] = "hey"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_VNCPort(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
+109
-32
@@ -1,15 +1,17 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A driver is able to talk to VMware, control virtual machines, etc.
|
||||
type Driver interface {
|
||||
// CompactDisk compacts a virtual disk.
|
||||
CompactDisk(string) error
|
||||
|
||||
// CreateDisk creates a virtual disk with the given size.
|
||||
CreateDisk(string, string) error
|
||||
|
||||
@@ -17,17 +19,11 @@ type Driver interface {
|
||||
IsRunning(string) (bool, error)
|
||||
|
||||
// Start starts a VM specified by the path to the VMX given.
|
||||
Start(string, bool) error
|
||||
Start(string) error
|
||||
|
||||
// Stop stops a VM specified by the path to the VMX given.
|
||||
Stop(string) error
|
||||
|
||||
// Get the path to the VMware ISO for the given flavor.
|
||||
ToolsIsoPath(string) string
|
||||
|
||||
// Get the path to the DHCP leases file for the given device.
|
||||
DhcpLeasesPath(string) string
|
||||
|
||||
// Verify checks to make sure that this driver should function
|
||||
// properly. This should check that all the files it will use
|
||||
// appear to exist and so on. If everything is okay, this doesn't
|
||||
@@ -35,25 +31,106 @@ type Driver interface {
|
||||
Verify() error
|
||||
}
|
||||
|
||||
// NewDriver returns a new driver implementation for this operating
|
||||
// system, or an error if the driver couldn't be initialized.
|
||||
func NewDriver() (Driver, error) {
|
||||
var driver Driver
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
driver = &Fusion5Driver{
|
||||
AppPath: "/Applications/VMware Fusion.app",
|
||||
}
|
||||
case "linux":
|
||||
driver = &Workstation9LinuxDriver{}
|
||||
default:
|
||||
return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
if err := driver.Verify(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return driver, nil
|
||||
// Fusion5Driver is a driver that can run VMWare Fusion 5.
|
||||
type Fusion5Driver struct {
|
||||
// This is the path to the "VMware Fusion.app"
|
||||
AppPath string
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) CreateDisk(output string, size string) error {
|
||||
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) {
|
||||
vmxPath, err := filepath.Abs(vmxPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "list")
|
||||
stdout, _, err := d.runAndLog(cmd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
if line == vmxPath {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) Start(vmxPath string) error {
|
||||
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "start", vmxPath, "gui")
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) Stop(vmxPath string) error {
|
||||
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "stop", vmxPath, "hard")
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) Verify() error {
|
||||
if _, err := os.Stat(d.AppPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("Fusion application not found at path: %s", d.AppPath)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(d.vmrunPath()); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("Critical application 'vmrun' not found at path: %s", d.vmrunPath())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(d.vdiskManagerPath()); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("Critical application vdisk manager not found at path: %s", d.vdiskManagerPath())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) vdiskManagerPath() string {
|
||||
return filepath.Join(d.AppPath, "Contents", "Library", "vmware-vdiskmanager")
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) vmrunPath() string {
|
||||
return filepath.Join(d.AppPath, "Contents", "Library", "vmrun")
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) runAndLog(cmd *exec.Cmd) (string, string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
|
||||
log.Printf("stdout: %s", strings.TrimSpace(stdout.String()))
|
||||
log.Printf("stderr: %s", strings.TrimSpace(stderr.String()))
|
||||
|
||||
return stdout.String(), stderr.String(), err
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Fusion5Driver is a driver that can run VMWare Fusion 5.
|
||||
type Fusion5Driver struct {
|
||||
// This is the path to the "VMware Fusion.app"
|
||||
AppPath string
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) CompactDisk(diskPath string) error {
|
||||
defragCmd := exec.Command(d.vdiskManagerPath(), "-d", diskPath)
|
||||
if _, _, err := d.runAndLog(defragCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shrinkCmd := exec.Command(d.vdiskManagerPath(), "-k", diskPath)
|
||||
if _, _, err := d.runAndLog(shrinkCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) CreateDisk(output string, size string) error {
|
||||
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) {
|
||||
vmxPath, err := filepath.Abs(vmxPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "list")
|
||||
stdout, _, err := d.runAndLog(cmd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
if line == vmxPath {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) Start(vmxPath string, headless bool) error {
|
||||
guiArgument := "gui"
|
||||
if headless == true {
|
||||
guiArgument = "nogui"
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "start", vmxPath, guiArgument)
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) Stop(vmxPath string) error {
|
||||
cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "stop", vmxPath, "hard")
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) Verify() error {
|
||||
if _, err := os.Stat(d.AppPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("Fusion application not found at path: %s", d.AppPath)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(d.vmrunPath()); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("Critical application 'vmrun' not found at path: %s", d.vmrunPath())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(d.vdiskManagerPath()); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("Critical application vdisk manager not found at path: %s", d.vdiskManagerPath())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) vdiskManagerPath() string {
|
||||
return filepath.Join(d.AppPath, "Contents", "Library", "vmware-vdiskmanager")
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) vmrunPath() string {
|
||||
return filepath.Join(d.AppPath, "Contents", "Library", "vmrun")
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) ToolsIsoPath(k string) string {
|
||||
return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", k+".iso")
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) DhcpLeasesPath(device string) string {
|
||||
return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases"
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) runAndLog(cmd *exec.Cmd) (string, string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
|
||||
log.Printf("stdout: %s", strings.TrimSpace(stdout.String()))
|
||||
log.Printf("stderr: %s", strings.TrimSpace(stderr.String()))
|
||||
|
||||
return stdout.String(), stderr.String(), err
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Workstation9LinuxDriver is a driver that can run VMware Workstation 9
|
||||
// on Linux.
|
||||
type Workstation9LinuxDriver struct {
|
||||
AppPath string
|
||||
VdiskManagerPath string
|
||||
VmrunPath string
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) CompactDisk(diskPath string) error {
|
||||
defragCmd := exec.Command(d.VdiskManagerPath, "-d", diskPath)
|
||||
if _, _, err := d.runAndLog(defragCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shrinkCmd := exec.Command(d.VdiskManagerPath, "-k", diskPath)
|
||||
if _, _, err := d.runAndLog(shrinkCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) CreateDisk(output string, size string) error {
|
||||
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) IsRunning(vmxPath string) (bool, error) {
|
||||
vmxPath, err := filepath.Abs(vmxPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.VmrunPath, "-T", "ws", "list")
|
||||
stdout, _, err := d.runAndLog(cmd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
if line == vmxPath {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) Start(vmxPath string, headless bool) error {
|
||||
guiArgument := "gui"
|
||||
if headless {
|
||||
guiArgument = "nogui"
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.VmrunPath, "-T", "ws", "start", vmxPath, guiArgument)
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) Stop(vmxPath string) error {
|
||||
cmd := exec.Command(d.VmrunPath, "-T", "ws", "stop", vmxPath, "hard")
|
||||
if _, _, err := d.runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) Verify() error {
|
||||
if err := d.findApp(); err != nil {
|
||||
return fmt.Errorf("VMware Workstation application ('vmware') not found in path.")
|
||||
}
|
||||
|
||||
if err := d.findVmrun(); err != nil {
|
||||
return fmt.Errorf("Required application 'vmrun' not found in path.")
|
||||
}
|
||||
|
||||
if err := d.findVdiskManager(); err != nil {
|
||||
return fmt.Errorf("Required application 'vmware-vdiskmanager' not found in path.")
|
||||
}
|
||||
|
||||
// Check to see if it APPEARS to be licensed.
|
||||
matches, err := filepath.Glob("/etc/vmware/license-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error looking for VMware license: %s", err)
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
return errors.New("Workstation does not appear to be licensed. Please license it.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) findApp() error {
|
||||
path, err := exec.LookPath("vmware")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.AppPath = path
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) findVdiskManager() error {
|
||||
path, err := exec.LookPath("vmware-vdiskmanager")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.VdiskManagerPath = path
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) findVmrun() error {
|
||||
path, err := exec.LookPath("vmrun")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.VmrunPath = path
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) ToolsIsoPath(flavor string) string {
|
||||
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) DhcpLeasesPath(device string) string {
|
||||
return "/etc/vmware/" + device + "/dhcpd/dhcpd.leases"
|
||||
}
|
||||
|
||||
func (d *Workstation9LinuxDriver) runAndLog(cmd *exec.Cmd) (string, string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
|
||||
stdoutString := strings.TrimSpace(stdout.String())
|
||||
stderrString := strings.TrimSpace(stderr.String())
|
||||
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
err = fmt.Errorf("VMware error: %s", stderrString)
|
||||
}
|
||||
|
||||
log.Printf("stdout: %s", stdoutString)
|
||||
log.Printf("stderr: %s", stderrString)
|
||||
|
||||
return stdout.String(), stderr.String(), err
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
@@ -17,9 +17,6 @@ type GuestIPFinder interface {
|
||||
// DHCPLeaseGuestLookup looks up the IP address of a guest using DHCP
|
||||
// lease information from the VMware network devices.
|
||||
type DHCPLeaseGuestLookup struct {
|
||||
// Driver that is being used (to find leases path)
|
||||
Driver Driver
|
||||
|
||||
// Device that the guest is connected to.
|
||||
Device string
|
||||
|
||||
@@ -28,7 +25,7 @@ type DHCPLeaseGuestLookup struct {
|
||||
}
|
||||
|
||||
func (f *DHCPLeaseGuestLookup) GuestIP() (string, error) {
|
||||
fh, err := os.Open(f.Driver.DhcpLeasesPath(f.Device))
|
||||
fh, err := os.Open(fmt.Sprintf("/var/db/vmware/vmnet-dhcpd-%s.leases", f.Device))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -71,9 +68,5 @@ func (f *DHCPLeaseGuestLookup) GuestIP() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if curIp == "" {
|
||||
return "", errors.New("IP not found for MAC in DHCP leases")
|
||||
}
|
||||
|
||||
return curIp, nil
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (f *IfconfigIPFinder) HostIP() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`inet\s*(?:addr:)?(.+?)\s`)
|
||||
re := regexp.MustCompile(`inet\s*(.+?)\s`)
|
||||
matches := re.FindStringSubmatch(stdout.String())
|
||||
if matches == nil {
|
||||
return "", errors.New("IP not found in ifconfig output...")
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
vmxBytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
vmxData := ParseVMX(string(vmxBytes))
|
||||
|
||||
var ok bool
|
||||
macAddress := ""
|
||||
if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
|
||||
if macAddress, ok = vmxData["ethernet0.generatedAddress"]; !ok || macAddress == "" {
|
||||
return "", errors.New("couldn't find MAC address in VMX")
|
||||
}
|
||||
}
|
||||
|
||||
ipLookup := &DHCPLeaseGuestLookup{
|
||||
Driver: driver,
|
||||
Device: "vmnet8",
|
||||
MACAddress: macAddress,
|
||||
}
|
||||
|
||||
ipAddress, err := ipLookup.GuestIP()
|
||||
if err != nil {
|
||||
log.Printf("IP lookup failed: %s", err)
|
||||
return "", fmt.Errorf("IP lookup failed: %s", err)
|
||||
}
|
||||
|
||||
if ipAddress == "" {
|
||||
log.Println("IP is blank, no IP yet.")
|
||||
return "", errors.New("IP is blank")
|
||||
}
|
||||
|
||||
log.Printf("Detected IP: %s", ipAddress)
|
||||
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil
|
||||
}
|
||||
|
||||
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
|
||||
config := state["config"].(*config)
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This step cleans up the VMX by removing or changing this prior to
|
||||
// being ready for use.
|
||||
//
|
||||
// Uses:
|
||||
// ui packer.Ui
|
||||
// vmx_path string
|
||||
//
|
||||
// Produces:
|
||||
// <nothing>
|
||||
type stepCleanVMX struct{}
|
||||
|
||||
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["error"] = fmt.Errorf("Error reading VMX: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
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 {
|
||||
if strings.HasPrefix(k, "floppy0.") {
|
||||
log.Printf("Deleting key: %s", k)
|
||||
delete(vmxData, k)
|
||||
}
|
||||
}
|
||||
vmxData["floppy0.present"] = "FALSE"
|
||||
}
|
||||
|
||||
ui.Message("Detatching ISO from CD-ROM device...")
|
||||
devRe := regexp.MustCompile(`^ide\d:\d\.`)
|
||||
for k, _ := range vmxData {
|
||||
match := devRe.FindString(k)
|
||||
if match == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
filenameKey := match + ".filename"
|
||||
if filename, ok := vmxData[filenameKey]; ok {
|
||||
if filename == isoPath {
|
||||
// Change the CD-ROM device back to auto-detect to eject
|
||||
vmxData[filenameKey] = "auto detect"
|
||||
vmxData[match+".deviceType"] = "cdrom-raw"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite the VMX
|
||||
if err := WriteVMX(vmxPath, vmxData); err != nil {
|
||||
state["error"] = fmt.Errorf("Error writing VMX: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (stepCleanVMX) Cleanup(map[string]interface{}) {}
|
||||
|
||||
func (stepCleanVMX) readVMX(vmxPath string) (map[string]string, error) {
|
||||
vmxF, err := os.Open(vmxPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer vmxF.Close()
|
||||
|
||||
vmxBytes, err := ioutil.ReadAll(vmxF)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ParseVMX(string(vmxBytes)), nil
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
// This step compacts the virtual disk for the VM unless the "skip_compaction"
|
||||
// boolean is true.
|
||||
//
|
||||
// Uses:
|
||||
// config *config
|
||||
// driver Driver
|
||||
// full_disk_path string
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// <nothing>
|
||||
type stepCompactDisk struct{}
|
||||
|
||||
func (stepCompactDisk) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*config)
|
||||
driver := state["driver"].(Driver)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
full_disk_path := state["full_disk_path"].(string)
|
||||
|
||||
if config.SkipCompaction == true {
|
||||
log.Println("Skipping disk compaction step...")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say("Compacting the disk image")
|
||||
if err := driver.CompactDisk(full_disk_path); err != nil {
|
||||
state["error"] = fmt.Errorf("Error compacting disk: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (stepCompactDisk) Cleanup(map[string]interface{}) {}
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// full_disk_path (string) - The full path to the created disk.
|
||||
// <nothing>
|
||||
type stepCreateDisk struct{}
|
||||
|
||||
func (stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction {
|
||||
@@ -24,16 +24,14 @@ func (stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say("Creating virtual machine disk")
|
||||
full_disk_path := filepath.Join(config.OutputDir, config.DiskName+".vmdk")
|
||||
if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize)); err != nil {
|
||||
output := filepath.Join(config.OutputDir, config.DiskName+".vmdk")
|
||||
if err := driver.CreateDisk(output, fmt.Sprintf("%dM", config.DiskSize)); err != nil {
|
||||
err := fmt.Errorf("Error creating disk: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["full_disk_path"] = full_disk_path
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
||||
@@ -36,10 +36,10 @@ func (stepCreateVMX) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ui.Say("Building and writing VMX file")
|
||||
|
||||
tplData := &vmxTemplateData{
|
||||
Name: config.VMName,
|
||||
GuestOS: config.GuestOSType,
|
||||
DiskName: config.DiskName,
|
||||
ISOPath: isoPath,
|
||||
config.VMName,
|
||||
config.GuestOSType,
|
||||
config.DiskName,
|
||||
isoPath,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
@@ -55,13 +55,6 @@ func (stepCreateVMX) Run(state map[string]interface{}) multistep.StepAction {
|
||||
}
|
||||
}
|
||||
|
||||
if floppyPathRaw, ok := state["floppy_path"]; ok {
|
||||
log.Println("Floppy path present, setting in VMX")
|
||||
vmxData["floppy0.present"] = "TRUE"
|
||||
vmxData["floppy0.fileType"] = "file"
|
||||
vmxData["floppy0.fileName"] = floppyPathRaw.(string)
|
||||
}
|
||||
|
||||
vmxPath := filepath.Join(config.OutputDir, config.VMName+".vmx")
|
||||
if err := WriteVMX(vmxPath, vmxData); err != nil {
|
||||
err := fmt.Errorf("Error creating VMX file: %s", err)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
@@ -26,7 +27,7 @@ func (s stepDownloadISO) Run(state map[string]interface{}) multistep.StepAction
|
||||
config := state["config"].(*config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
checksum, err := hex.DecodeString(config.ISOChecksum)
|
||||
checksum, err := hex.DecodeString(config.ISOMD5)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error parsing checksum: %s", err)
|
||||
state["error"] = err
|
||||
@@ -42,7 +43,7 @@ func (s stepDownloadISO) Run(state map[string]interface{}) multistep.StepAction
|
||||
Url: config.ISOUrl,
|
||||
TargetPath: cachePath,
|
||||
CopyFile: false,
|
||||
Hash: common.HashForType(config.ISOChecksumType),
|
||||
Hash: md5.New(),
|
||||
Checksum: checksum,
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,6 @@ type stepPrepareOutputDir struct{}
|
||||
|
||||
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...")
|
||||
os.RemoveAll(config.OutputDir)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
|
||||
state["error"] = err
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"os"
|
||||
)
|
||||
|
||||
type stepPrepareTools struct{}
|
||||
|
||||
func (*stepPrepareTools) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*config)
|
||||
driver := state["driver"].(Driver)
|
||||
|
||||
if config.ToolsUploadFlavor == "" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
path := driver.ToolsIsoPath(config.ToolsUploadFlavor)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
state["error"] = fmt.Errorf(
|
||||
"Couldn't find VMware tools for '%s'! VMware often downloads these\n"+
|
||||
"tools on-demand. However, to do this, you need to create a fake VM\n"+
|
||||
"of the proper type then click the 'install tools' option in the\n"+
|
||||
"VMware GUI.", config.ToolsUploadFlavor)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["tools_upload_source"] = path
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*stepPrepareTools) Cleanup(map[string]interface{}) {}
|
||||
@@ -0,0 +1,25 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
type stepProvision struct{}
|
||||
|
||||
func (*stepProvision) Run(state map[string]interface{}) multistep.StepAction {
|
||||
comm := state["communicator"].(packer.Communicator)
|
||||
hook := state["hook"].(packer.Hook)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
log.Println("Running the provision hook")
|
||||
if err := hook.Run(packer.HookProvision, ui, comm, nil); err != nil {
|
||||
state["error"] = err
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*stepProvision) Cleanup(map[string]interface{}) {}
|
||||
@@ -27,21 +27,13 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
|
||||
driver := state["driver"].(Driver)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
vmxPath := state["vmx_path"].(string)
|
||||
vncPort := state["vnc_port"].(uint)
|
||||
|
||||
// Set the VMX path so that we know we started the machine
|
||||
s.bootTime = time.Now()
|
||||
s.vmxPath = vmxPath
|
||||
|
||||
ui.Say("Starting virtual machine...")
|
||||
if config.Headless {
|
||||
ui.Message(fmt.Sprintf(
|
||||
"The VM will be run headless, without a GUI. If you want to\n"+
|
||||
"view the screen of the VM, connect via VNC without a password to\n"+
|
||||
"127.0.0.1:%d", vncPort))
|
||||
}
|
||||
|
||||
if err := driver.Start(vmxPath, config.Headless); err != nil {
|
||||
if err := driver.Start(vmxPath); err != nil {
|
||||
err := fmt.Errorf("Error starting VM: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
@@ -49,9 +41,9 @@ func (s *stepRun) Run(state map[string]interface{}) multistep.StepAction {
|
||||
}
|
||||
|
||||
// Wait the wait amount
|
||||
if int64(config.bootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait.String()))
|
||||
time.Sleep(config.bootWait)
|
||||
if int64(config.BootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.BootWait.String()))
|
||||
time.Sleep(config.BootWait)
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -36,13 +33,7 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
|
||||
if config.ShutdownCommand != "" {
|
||||
ui.Say("Gracefully halting virtual machine...")
|
||||
log.Printf("Executing shutdown command: %s", config.ShutdownCommand)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd := &packer.RemoteCmd{
|
||||
Command: config.ShutdownCommand,
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
}
|
||||
cmd := &packer.RemoteCmd{Command: config.ShutdownCommand}
|
||||
if err := comm.Start(cmd); err != nil {
|
||||
err := fmt.Errorf("Failed to send shutdown command: %s", err)
|
||||
state["error"] = err
|
||||
@@ -53,20 +44,9 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
|
||||
// Wait for the command to run
|
||||
cmd.Wait()
|
||||
|
||||
// If the command failed to run, notify the user in some way.
|
||||
if cmd.ExitStatus != 0 {
|
||||
state["error"] = fmt.Errorf(
|
||||
"Shutdown command has non-zero exit status.\n\nStdout: %s\n\nStderr: %s",
|
||||
stdout.String(), stderr.String())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Shutdown stdout: %s", stdout.String())
|
||||
log.Printf("Shutdown stderr: %s", stderr.String())
|
||||
|
||||
// Wait for the machine to actually shut down
|
||||
log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout)
|
||||
shutdownTimer := time.After(config.shutdownTimeout)
|
||||
log.Printf("Waiting max %s for shutdown to complete", config.ShutdownTimeout)
|
||||
shutdownTimer := time.After(config.ShutdownTimeout)
|
||||
for {
|
||||
running, _ := driver.IsRunning(vmxPath)
|
||||
if !running {
|
||||
@@ -92,34 +72,6 @@ func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
|
||||
}
|
||||
}
|
||||
|
||||
ui.Message("Waiting for VMware to clean up after itself...")
|
||||
lockPattern := filepath.Join(config.OutputDir, "*.lck")
|
||||
timer := time.After(15 * time.Second)
|
||||
LockWaitLoop:
|
||||
for {
|
||||
locks, err := filepath.Glob(lockPattern)
|
||||
if err == nil {
|
||||
if len(locks) == 0 {
|
||||
log.Println("No more lock files found. VMware is clean.")
|
||||
break
|
||||
}
|
||||
|
||||
if len(locks) == 1 && strings.HasSuffix(locks[0], ".vmx.lck") {
|
||||
log.Println("Only waiting on VMX lock. VMware is clean.")
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Waiting on lock files: %#v", locks)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-timer:
|
||||
log.Println("Reached timeout on waiting for clean VMware. Assuming clean.")
|
||||
break LockWaitLoop
|
||||
case <-time.After(1 * time.Second):
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("VM shut down.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
@@ -95,22 +95,8 @@ func (*stepTypeBootCommand) Cleanup(map[string]interface{}) {}
|
||||
|
||||
func vncSendString(c *vnc.ClientConn, original string) {
|
||||
special := make(map[string]uint32)
|
||||
special["<bs>"] = 0xFF08
|
||||
special["<del>"] = 0xFFFF
|
||||
special["<enter>"] = 0xFF0D
|
||||
special["<esc>"] = 0xFF1B
|
||||
special["<f1>"] = 0xFFBE
|
||||
special["<f2>"] = 0xFFBF
|
||||
special["<f3>"] = 0xFFC0
|
||||
special["<f4>"] = 0xFFC1
|
||||
special["<f5>"] = 0xFFC2
|
||||
special["<f6>"] = 0xFFC3
|
||||
special["<f7>"] = 0xFFC4
|
||||
special["<f8>"] = 0xFFC5
|
||||
special["<f9>"] = 0xFFC6
|
||||
special["<f10>"] = 0xFFC7
|
||||
special["<f11>"] = 0xFFC8
|
||||
special["<f12>"] = 0xFFC9
|
||||
special["<return>"] = 0xFF0D
|
||||
special["<tab>"] = 0xFF09
|
||||
|
||||
@@ -128,20 +114,6 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<wait5>") {
|
||||
log.Printf("Special code '<wait5>' found, sleeping 5 seconds")
|
||||
time.Sleep(5 * time.Second)
|
||||
original = original[len("<wait5>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(original, "<wait10>") {
|
||||
log.Printf("Special code '<wait10>' found, sleeping 10 seconds")
|
||||
time.Sleep(10 * time.Second)
|
||||
original = original[len("<wait10>"):]
|
||||
continue
|
||||
}
|
||||
|
||||
for specialCode, specialValue := range special {
|
||||
if strings.HasPrefix(original, specialCode) {
|
||||
log.Printf("Special code '%s' found, replacing with: %d", specialCode, specialValue)
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type toolsUploadPathTemplate struct {
|
||||
Flavor string
|
||||
}
|
||||
|
||||
type stepUploadTools struct{}
|
||||
|
||||
func (*stepUploadTools) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*config)
|
||||
if config.ToolsUploadFlavor == "" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
comm := state["communicator"].(packer.Communicator)
|
||||
tools_source := state["tools_upload_source"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say(fmt.Sprintf("Uploading the '%s' VMware Tools", config.ToolsUploadFlavor))
|
||||
f, err := os.Open(tools_source)
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error opening VMware Tools ISO: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tplData := &toolsUploadPathTemplate{Flavor: config.ToolsUploadFlavor}
|
||||
var processedPath bytes.Buffer
|
||||
t := template.Must(template.New("path").Parse(config.ToolsUploadPath))
|
||||
t.Execute(&processedPath, tplData)
|
||||
|
||||
if err := comm.Upload(processedPath.String(), f); err != nil {
|
||||
state["error"] = fmt.Errorf("Error uploading VMware Tools: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*stepUploadTools) Cleanup(map[string]interface{}) {}
|
||||
@@ -0,0 +1,183 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This step waits for SSH to become available and establishes an SSH
|
||||
// connection.
|
||||
//
|
||||
// Uses:
|
||||
// config *config
|
||||
// ui packer.Ui
|
||||
// vmx_path string
|
||||
//
|
||||
// Produces:
|
||||
// communicator packer.Communicator
|
||||
type stepWaitForSSH struct {
|
||||
cancel bool
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (s *stepWaitForSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||
config := state["config"].(*config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
var comm packer.Communicator
|
||||
var err error
|
||||
|
||||
waitDone := make(chan bool, 1)
|
||||
go func() {
|
||||
comm, err = s.waitForSSH(state)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
log.Printf("Waiting for SSH, up to timeout: %s", config.SSHWaitTimeout.String())
|
||||
|
||||
timeout := time.After(config.SSHWaitTimeout)
|
||||
WaitLoop:
|
||||
for {
|
||||
// Wait for either SSH to become available, a timeout to occur,
|
||||
// or an interrupt to come through.
|
||||
select {
|
||||
case <-waitDone:
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error waiting for SSH: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state["communicator"] = comm
|
||||
break WaitLoop
|
||||
case <-timeout:
|
||||
ui.Error("Timeout waiting for SSH.")
|
||||
s.cancel = true
|
||||
return multistep.ActionHalt
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state[multistep.StateCancelled]; ok {
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepWaitForSSH) Cleanup(map[string]interface{}) {
|
||||
if s.conn != nil {
|
||||
s.conn.Close()
|
||||
s.conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Reads the network information for lookup via DHCP.
|
||||
func (s *stepWaitForSSH) dhcpLeaseLookup(vmxPath string) (GuestIPFinder, error) {
|
||||
f, err := os.Open(vmxPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
vmxBytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vmxData := ParseVMX(string(vmxBytes))
|
||||
|
||||
var ok bool
|
||||
macAddress := ""
|
||||
if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
|
||||
if macAddress, ok = vmxData["ethernet0.generatedAddress"]; !ok || macAddress == "" {
|
||||
return nil, errors.New("couldn't find MAC address in VMX")
|
||||
}
|
||||
}
|
||||
|
||||
return &DHCPLeaseGuestLookup{"vmnet8", macAddress}, nil
|
||||
}
|
||||
|
||||
// This blocks until SSH becomes available, and sends the communicator
|
||||
// on the given channel.
|
||||
func (s *stepWaitForSSH) waitForSSH(state map[string]interface{}) (packer.Communicator, error) {
|
||||
config := state["config"].(*config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
vmxPath := state["vmx_path"].(string)
|
||||
|
||||
handshakeAttempts := 0
|
||||
|
||||
ui.Say("Waiting for SSH to become available...")
|
||||
var comm packer.Communicator
|
||||
var nc net.Conn
|
||||
for {
|
||||
if nc != nil {
|
||||
nc.Close()
|
||||
}
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
if s.cancel {
|
||||
log.Println("SSH wait cancelled. Exiting loop.")
|
||||
return nil, errors.New("SSH wait cancelled")
|
||||
}
|
||||
|
||||
// First we wait for the IP to become available...
|
||||
log.Println("Lookup up IP information...")
|
||||
ipLookup, err := s.dhcpLeaseLookup(vmxPath)
|
||||
if err != nil {
|
||||
log.Printf("Can't lookup via DHCP lease: %s", err)
|
||||
}
|
||||
|
||||
ip, err := ipLookup.GuestIP()
|
||||
if err != nil {
|
||||
log.Printf("IP lookup failed: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Detected IP: %s", ip)
|
||||
|
||||
// Attempt to connect to SSH port
|
||||
nc, err = net.Dial("tcp", fmt.Sprintf("%s:%d", ip, config.SSHPort))
|
||||
if err != nil {
|
||||
log.Printf("TCP connection to SSH ip/port failed: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Then we attempt to connect via SSH
|
||||
sshConfig := &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
},
|
||||
}
|
||||
|
||||
comm, err = ssh.New(nc, sshConfig)
|
||||
if err != nil {
|
||||
log.Printf("SSH handshake err: %s", err)
|
||||
|
||||
handshakeAttempts += 1
|
||||
if handshakeAttempts < 10 {
|
||||
// Try to connect via SSH a handful of times
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ui.Say("Connected via SSH!")
|
||||
break
|
||||
}
|
||||
|
||||
// Store the connection so we can close it later
|
||||
s.conn = nc
|
||||
return comm, nil
|
||||
}
|
||||
@@ -21,14 +21,12 @@ func (Command) Help() string {
|
||||
|
||||
func (c Command) Run(env packer.Environment, args []string) int {
|
||||
var cfgDebug bool
|
||||
var cfgForce bool
|
||||
var cfgExcept []string
|
||||
var cfgOnly []string
|
||||
|
||||
cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError)
|
||||
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
|
||||
cmdFlags.BoolVar(&cfgDebug, "debug", false, "debug mode for builds")
|
||||
cmdFlags.BoolVar(&cfgForce, "force", false, "force a build if artifacts exist")
|
||||
cmdFlags.Var((*stringSliceValue)(&cfgExcept), "except", "build all builds except these")
|
||||
cmdFlags.Var((*stringSliceValue)(&cfgOnly), "only", "only build the given builds by name")
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
@@ -143,13 +141,11 @@ func (c Command) Run(env packer.Environment, args []string) int {
|
||||
env.Ui().Say("")
|
||||
|
||||
log.Printf("Build debug mode: %v", cfgDebug)
|
||||
log.Printf("Force build: %v", cfgForce)
|
||||
|
||||
// Set the debug and force mode and prepare all the builds
|
||||
// Set the debug mode and prepare all the builds
|
||||
for _, b := range builds {
|
||||
log.Printf("Preparing build: %s", b.Name())
|
||||
b.SetDebug(cfgDebug)
|
||||
b.SetForce(cfgForce)
|
||||
err := b.Prepare()
|
||||
if err != nil {
|
||||
env.Ui().Error(err.Error())
|
||||
@@ -250,11 +246,6 @@ func (c Command) Run(env packer.Environment, args []string) int {
|
||||
env.Ui().Say("\n==> Builds finished but no artifacts were created.")
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
// If any errors occurred, exit with a non-zero exit status
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package build
|
||||
|
||||
const helpText = `
|
||||
Usage: packer build [options] TEMPLATE
|
||||
Usage: packer build TEMPLATE
|
||||
|
||||
Will execute multiple builds in parallel as defined in the template.
|
||||
The various artifacts created by the template will be outputted.
|
||||
@@ -9,7 +9,6 @@ Usage: packer build [options] TEMPLATE
|
||||
Options:
|
||||
|
||||
-debug Debug mode enabled for builds
|
||||
-force Force a build to continue if artifacts exist, deletes existing artifacts
|
||||
-except=foo,bar,baz Build all builds other than these
|
||||
-only=foo,bar,baz Only build the given builds by name
|
||||
`
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Command byte
|
||||
|
||||
func (Command) Help() string {
|
||||
return strings.TrimSpace(helpString)
|
||||
}
|
||||
|
||||
func (c Command) Run(env packer.Environment, args []string) int {
|
||||
cmdFlags := flag.NewFlagSet("fix", flag.ContinueOnError)
|
||||
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
args = cmdFlags.Args()
|
||||
if len(args) != 1 {
|
||||
cmdFlags.Usage()
|
||||
return 1
|
||||
}
|
||||
|
||||
// Read the file for decoding
|
||||
tplF, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error opening template: %s", err))
|
||||
return 1
|
||||
}
|
||||
defer tplF.Close()
|
||||
|
||||
// Decode the JSON into a generic map structure
|
||||
var templateData map[string]interface{}
|
||||
decoder := json.NewDecoder(tplF)
|
||||
if err := decoder.Decode(&templateData); err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error parsing template: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Close the file since we're done with that
|
||||
tplF.Close()
|
||||
|
||||
// Run the template through the various fixers
|
||||
fixers := []Fixer{Fixers["iso-md5"]}
|
||||
input := templateData
|
||||
for _, fixer := range fixers {
|
||||
var err error
|
||||
input, err = fixer.Fix(input)
|
||||
if err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error fixing: %s", err))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
var output bytes.Buffer
|
||||
encoder := json.NewEncoder(&output)
|
||||
if err := encoder.Encode(input); err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error encoding: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
var indented bytes.Buffer
|
||||
if err := json.Indent(&indented, output.Bytes(), "", " "); err != nil {
|
||||
env.Ui().Error(fmt.Sprintf("Error encoding: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
result := indented.String()
|
||||
result = strings.Replace(result, `\u003c`, "<", -1)
|
||||
result = strings.Replace(result, `\u003e`, ">", -1)
|
||||
env.Ui().Say(result)
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c Command) Synopsis() string {
|
||||
return "fixes templates from old versions of packer"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommand_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(Command)
|
||||
if _, ok := raw.(packer.Command); !ok {
|
||||
t.Fatalf("must be a Command")
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package fix
|
||||
|
||||
// A Fixer is something that can perform a fix operation on a template.
|
||||
type Fixer interface {
|
||||
// Fix takes a raw map structure input, potentially transforms it
|
||||
// in some way, and returns the new, transformed structure. The
|
||||
// Fix method is allowed to mutate the input.
|
||||
Fix(input map[string]interface{}) (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// Fixers is the map of all available fixers, by name.
|
||||
var Fixers map[string]Fixer
|
||||
|
||||
func init() {
|
||||
Fixers = map[string]Fixer{
|
||||
"iso-md5": new(FixerISOMD5),
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// FixerISOMD5 is a Fixer that replaces the "iso_md5" configuration key
|
||||
// with the newer "iso_checksum" and "iso_checksum_type" within builders.
|
||||
type FixerISOMD5 struct{}
|
||||
|
||||
func (FixerISOMD5) Fix(input map[string]interface{}) (map[string]interface{}, error) {
|
||||
// Our template type we'll use for this fixer only
|
||||
type template struct {
|
||||
Builders []map[string]interface{}
|
||||
}
|
||||
|
||||
// Decode the input into our structure, if we can
|
||||
var tpl template
|
||||
if err := mapstructure.Decode(input, &tpl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Go through each builder and replace the iso_md5 if we can
|
||||
for _, builder := range tpl.Builders {
|
||||
md5raw, ok := builder["iso_md5"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
md5, ok := md5raw.(string)
|
||||
if !ok {
|
||||
// TODO: error?
|
||||
continue
|
||||
}
|
||||
|
||||
delete(builder, "iso_md5")
|
||||
builder["iso_checksum"] = md5
|
||||
builder["iso_checksum_type"] = "md5"
|
||||
}
|
||||
|
||||
input["builders"] = tpl.Builders
|
||||
return input, nil
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package fix
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFixerISOMD5_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(FixerISOMD5)
|
||||
if _, ok := raw.(Fixer); !ok {
|
||||
t.Fatalf("must be a Fixer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixerISOMD5_Fix(t *testing.T) {
|
||||
var f FixerISOMD5
|
||||
|
||||
input := map[string]interface{}{
|
||||
"builders": []interface{}{
|
||||
map[string]string{
|
||||
"type": "foo",
|
||||
"iso_md5": "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"builders": []map[string]interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "foo",
|
||||
"iso_checksum": "bar",
|
||||
"iso_checksum_type": "md5",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
output, err := f.Fix(input)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(output, expected) {
|
||||
t.Fatalf("unexpected: %#v\nexpected: %#v\n", output, expected)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package fix
|
||||
|
||||
const helpString = `
|
||||
Usage: packer fix [options] TEMPLATE
|
||||
|
||||
Reads the JSON template and attempts to fix known backwards
|
||||
incompatibilities. The fixed template will be outputted to standard out.
|
||||
|
||||
If the template cannot be fixed due to an error, the command will exit
|
||||
with a non-zero exit status. Error messages will appear on standard error.
|
||||
|
||||
Fixes that are run:
|
||||
|
||||
iso-md5 Replaces "iso_md5" in builders with newer "iso_checksum"
|
||||
|
||||
`
|
||||
@@ -1,7 +1,7 @@
|
||||
package validate
|
||||
|
||||
const helpString = `
|
||||
Usage: packer validate [options] TEMPLATE
|
||||
Usage: packer validate TEMPLATE
|
||||
|
||||
Checks the template is valid by parsing the template and also
|
||||
checking the configuration with the various builders, provisioners, etc.
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
@@ -15,39 +13,18 @@ import (
|
||||
|
||||
type comm struct {
|
||||
client *ssh.ClientConn
|
||||
config *Config
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
// Config is the structure used to configure the SSH communicator.
|
||||
type Config struct {
|
||||
// The configuration of the Go SSH connection
|
||||
SSHConfig *ssh.ClientConfig
|
||||
|
||||
// Connection returns a new connection. The current connection
|
||||
// in use will be closed as part of the Close method, or in the
|
||||
// case an error occurs.
|
||||
Connection func() (net.Conn, error)
|
||||
}
|
||||
|
||||
// Creates a new packer.Communicator implementation over SSH. This takes
|
||||
// an already existing TCP connection and SSH configuration.
|
||||
func New(config *Config) (result *comm, err error) {
|
||||
// Establish an initial connection and connect
|
||||
result = &comm{
|
||||
config: config,
|
||||
}
|
||||
|
||||
if err = result.reconnect(); err != nil {
|
||||
result = nil
|
||||
return
|
||||
}
|
||||
|
||||
func New(c net.Conn, config *ssh.ClientConfig) (result *comm, err error) {
|
||||
client, err := ssh.Client(c, config)
|
||||
result = &comm{client}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
|
||||
session, err := c.newSession()
|
||||
session, err := c.client.NewSession()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -78,6 +55,7 @@ func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
|
||||
// exit boolean and status.
|
||||
go func() {
|
||||
defer session.Close()
|
||||
|
||||
err := session.Wait()
|
||||
cmd.ExitStatus = 0
|
||||
if err != nil {
|
||||
@@ -94,7 +72,8 @@ func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
|
||||
}
|
||||
|
||||
func (c *comm) Upload(path string, input io.Reader) error {
|
||||
session, err := c.newSession()
|
||||
log.Println("Opening new SSH session")
|
||||
session, err := c.client.NewSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -107,6 +86,12 @@ func (c *comm) Upload(path string, input io.Reader) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set stderr/stdout to a bytes buffer
|
||||
stderr := new(bytes.Buffer)
|
||||
stdout := new(bytes.Buffer)
|
||||
session.Stderr = stderr
|
||||
session.Stdout = stdout
|
||||
|
||||
// We only want to close once, so we nil w after we close it,
|
||||
// and only close in the defer if it hasn't been closed already.
|
||||
defer func() {
|
||||
@@ -115,26 +100,10 @@ func (c *comm) Upload(path string, input io.Reader) error {
|
||||
}
|
||||
}()
|
||||
|
||||
// Get a pipe to stdout so that we can get responses back
|
||||
stdoutPipe, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdoutR := bufio.NewReader(stdoutPipe)
|
||||
|
||||
// Set stderr to a bytes buffer
|
||||
stderr := new(bytes.Buffer)
|
||||
session.Stderr = stderr
|
||||
|
||||
// The target directory and file for talking the SCP protocol
|
||||
target_dir := filepath.Dir(path)
|
||||
target_file := filepath.Base(path)
|
||||
|
||||
// On windows, filepath.Dir uses backslash seperators (ie. "\tmp").
|
||||
// This does not work when the target host is unix. Switch to forward slash
|
||||
// which works for unix and windows
|
||||
target_dir = filepath.ToSlash(target_dir)
|
||||
|
||||
// Start the sink mode on the other side
|
||||
// TODO(mitchellh): There are probably issues with shell escaping the path
|
||||
log.Println("Starting remote scp process in sink mode")
|
||||
@@ -154,17 +123,11 @@ func (c *comm) Upload(path string, input io.Reader) error {
|
||||
// Start the protocol
|
||||
log.Println("Beginning file upload...")
|
||||
fmt.Fprintln(w, "C0644", input_memory.Len(), target_file)
|
||||
err = checkSCPStatus(stdoutR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
io.Copy(w, input_memory)
|
||||
fmt.Fprint(w, "\x00")
|
||||
err = checkSCPStatus(stdoutR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(mitchellh): Each step above results in a 0/1/2 being sent by
|
||||
// the remote side to confirm. We should check for those confirmations.
|
||||
|
||||
// Close the stdin, which sends an EOF, and then set w to nil so that
|
||||
// our defer func doesn't close it again since that is unsafe with
|
||||
@@ -182,19 +145,12 @@ func (c *comm) Upload(path string, input io.Reader) error {
|
||||
// Otherwise, we have an ExitErorr, meaning we can just read
|
||||
// the exit status
|
||||
log.Printf("non-zero exit status: %d", exitErr.ExitStatus())
|
||||
|
||||
// If we exited with status 127, it means SCP isn't available.
|
||||
// Return a more descriptive error for that.
|
||||
if exitErr.ExitStatus() == 127 {
|
||||
return errors.New(
|
||||
"SCP failed to start. This usually means that SCP is not\n" +
|
||||
"properly installed on the remote system.")
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("scp stdout (length %d): %#v", stdout.Len(), stdout.Bytes())
|
||||
log.Printf("scp stderr (length %d): %s", stderr.Len(), stderr.String())
|
||||
|
||||
return nil
|
||||
@@ -203,61 +159,3 @@ func (c *comm) Upload(path string, input io.Reader) error {
|
||||
func (c *comm) Download(string, io.Writer) error {
|
||||
panic("not implemented yet")
|
||||
}
|
||||
|
||||
func (c *comm) newSession() (*ssh.Session, error) {
|
||||
log.Println("opening new ssh session")
|
||||
session, err := c.client.NewSession()
|
||||
if err != nil {
|
||||
log.Printf("ssh session open error: '%s', attempting reconnect", err)
|
||||
if err := c.reconnect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.client.NewSession()
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (c *comm) reconnect() (err error) {
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
log.Printf("reconnecting to TCP connection for SSH")
|
||||
c.conn, err = c.config.Connection()
|
||||
if err != nil {
|
||||
log.Printf("reconnection error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("handshaking with SSH")
|
||||
c.client, err = ssh.Client(c.conn, c.config.SSHConfig)
|
||||
if err != nil {
|
||||
log.Printf("handshake error: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// checkSCPStatus checks that a prior command sent to SCP completed
|
||||
// successfully. If it did not complete successfully, an error will
|
||||
// be returned.
|
||||
func checkSCPStatus(r *bufio.Reader) error {
|
||||
code, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if code != 0 {
|
||||
// Treat any non-zero (really 1 and 2) as fatal errors
|
||||
message, _, err := r.ReadLine()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading error message: %s", err)
|
||||
}
|
||||
|
||||
return errors.New(string(message))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -115,20 +115,12 @@ func TestNew_Invalid(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
conn := func() (net.Conn, error) {
|
||||
conn, err := net.Dial("tcp", newMockLineServer(t))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to dial to remote side: %s", err)
|
||||
}
|
||||
return conn, err
|
||||
conn, err := net.Dial("tcp", newMockLineServer(t))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to dial to remote side: %s", err)
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Connection: conn,
|
||||
SSHConfig: clientConfig,
|
||||
}
|
||||
|
||||
_, err := New(config)
|
||||
_, err = New(conn, clientConfig)
|
||||
if err == nil {
|
||||
t.Fatal("should have had an error connecting")
|
||||
}
|
||||
@@ -142,20 +134,12 @@ func TestStart(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
conn := func() (net.Conn, error) {
|
||||
conn, err := net.Dial("tcp", newMockLineServer(t))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to dial to remote side: %s", err)
|
||||
}
|
||||
return conn, err
|
||||
conn, err := net.Dial("tcp", newMockLineServer(t))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to dial to remote side: %s", err)
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Connection: conn,
|
||||
SSHConfig: clientConfig,
|
||||
}
|
||||
|
||||
client, err := New(config)
|
||||
client, err := New(conn, clientConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("error connecting to SSH: %s", err)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user