Commit e5c6f1a7 authored by Nathan Hartwell's avatar Nathan Hartwell

Merge branch 'master' of https://github.com/mitchellh/packer

Conflicts:
	provisioner/salt-masterless/provisioner.go
parents 331003c8 658e6e09
...@@ -7,7 +7,7 @@ go: ...@@ -7,7 +7,7 @@ go:
install: make updatedeps install: make updatedeps
script: script:
- make test - GOMAXPROCS=2 make test
#- go test -race ./... #- go test -race ./...
matrix: matrix:
......
## 0.7.2 (unreleased) ## 0.8.0 (unreleased)
## 0.7.2 (October 28, 2014)
FEATURES: FEATURES:
* builder/digitalocean: API V2 support. [GH-1463]
* builder/parallels: Don't depend on _prl-utils_ [GH-1499] * builder/parallels: Don't depend on _prl-utils_ [GH-1499]
IMPROVEMENTS:
* builder/amazon/all: Support new AWS Frankfurt region.
* builder/docker: Allow remote `DOCKER_HOST`, which works as long as
volumes work. [GH-1594]
* builder/qemu: Can set cache mode for main disk. [GH-1558]
* builder/qemu: Can build from pre-existing disk. [GH-1342]
* builder/vmware: Can specify path to Fusion installation with environmental
variable `FUSION_APP_PATH`. [GH-1552]
* builder/vmware: Can specify the HW version for the VMX. [GH-1530]
* builder/vmware/esxi: Will now cache ISOs/floppies remotely. [GH-1479]
* builder/vmware/vmx: Source VMX can have a disk connected via SATA. [GH-1604]
* post-processors/vagrant: Support Qemu (libvirt) boxes. [GH-1330]
* post-processors/vagrantcloud: Support self-hosted box URLs.
BUG FIXES: BUG FIXES:
* core: Fix loading plugins from pwd. [GH-1521]
* builder/amazon: Prefer token in config if given. [GH-1544]
* builder/amazon/all: Extended timeout for waiting for AMI. [GH-1533]
* builder/virtualbox: Can read VirtualBox version on FreeBSD. [GH-1570]
* builder/virtualbox: More robust reading of guest additions URL. [GH-1509]
* builder/vmware: Always remove floppies/drives. [GH-1504]
* builder/vmware: Wait some time so that post-VMX update aren't
overwritten. [GH-1504]
* builder/vmware/esxi: Retry power on if it fails. [GH-1334]
* builder/vmware-vmx: Fix issue with order of boot command support [GH-1492] * builder/vmware-vmx: Fix issue with order of boot command support [GH-1492]
* builder/amazon: Extend timeout and allow user override [GH-1533]
* builder/parallels: Ignore 'The fdd0 device does not exist' [GH-1501] * builder/parallels: Ignore 'The fdd0 device does not exist' [GH-1501]
* builder/parallels: Rely on Cleanup functions to detach devices [GH-1502] * builder/parallels: Rely on Cleanup functions to detach devices [GH-1502]
* builder/parallels: Create VM without hdd and then add it later [GH-1548] * builder/parallels: Create VM without hdd and then add it later [GH-1548]
* builder/parallels: Disconnect cdrom0 [GH-1605]
* builder/qemu: Don't use `-redir` flag anymore, replace with
`hostfwd` options. [GH-1561]
* builder/qmeu: Use `pc` as default machine type instead of `pc-1.0`.
* providers/aws: Ignore transient network errors. [GH-1579]
* provisioner/ansible: Don't buffer output so output streams in. [GH-1585]
* provisioner/ansible: Use inventory file always to avoid potentially
deprecated feature. [GH-1562]
* provisioner/shell: Quote environmental variables. [GH-1568]
* provisioner/salt: Bootstrap over SSL. [GH-1608]
* post-processors/docker-push: Work with docker-tag artifacts. [GH-1526]
* post-processors/vsphere: Append "/" to object address. [GH-1615]
## 0.7.1 (September 10, 2014) ## 0.7.1 (September 10, 2014)
......
...@@ -15,6 +15,6 @@ testrace: ...@@ -15,6 +15,6 @@ testrace:
go test -race $(TEST) $(TESTARGS) go test -race $(TEST) $(TESTARGS)
updatedeps: updatedeps:
go get -u -v -p 2 ./... go get -d -v -p 2 ./...
.PHONY: bin default test updatedeps .PHONY: bin default test updatedeps
# -*- mode: ruby -*- # -*- mode: ruby -*-
# vi: set ft=ruby : # vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
$script = <<SCRIPT $script = <<SCRIPT
SRCROOT="/opt/go" SRCROOT="/opt/go"
...@@ -31,7 +28,7 @@ sudo chown -R vagrant:vagrant /opt/gopath ...@@ -31,7 +28,7 @@ sudo chown -R vagrant:vagrant /opt/gopath
sudo apt-get install -y curl git-core zip sudo apt-get install -y curl git-core zip
SCRIPT SCRIPT
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| Vagrant.configure(2) do |config|
config.vm.box = "chef/ubuntu-12.04" config.vm.box = "chef/ubuntu-12.04"
config.vm.provision "shell", inline: $script config.vm.provision "shell", inline: $script
......
...@@ -26,7 +26,7 @@ func (c *AccessConfig) Auth() (aws.Auth, error) { ...@@ -26,7 +26,7 @@ func (c *AccessConfig) Auth() (aws.Auth, error) {
c.SecretKey = auth.SecretKey c.SecretKey = auth.SecretKey
c.Token = auth.Token c.Token = auth.Token
} }
if auth.Token == "" && c.Token != "" { if c.Token != "" {
auth.Token = c.Token auth.Token = c.Token
} }
......
...@@ -52,6 +52,10 @@ func (a *Artifact) String() string { ...@@ -52,6 +52,10 @@ func (a *Artifact) String() string {
return fmt.Sprintf("AMIs were created:\n\n%s", strings.Join(amiStrings, "\n")) return fmt.Sprintf("AMIs were created:\n\n%s", strings.Join(amiStrings, "\n"))
} }
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error { func (a *Artifact) Destroy() error {
errors := make([]error, 0) errors := make([]error, 0)
......
...@@ -6,6 +6,9 @@ import ( ...@@ -6,6 +6,9 @@ import (
"github.com/mitchellh/goamz/ec2" "github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"log" "log"
"net"
"os"
"strconv"
"time" "time"
) )
...@@ -38,6 +41,9 @@ func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc { ...@@ -38,6 +41,9 @@ func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" { if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
// Set this to nil as if we didn't find anything. // Set this to nil as if we didn't find anything.
resp = nil resp = nil
} else if isTransientNetworkError(err) {
// Transient network error, treat it as if we didn't find anything
resp = nil
} else { } else {
log.Printf("Error on AMIStateRefresh: %s", err) log.Printf("Error on AMIStateRefresh: %s", err)
return nil, "", err return nil, "", err
...@@ -64,6 +70,9 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc { ...@@ -64,6 +70,9 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" { if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
// Set this to nil as if we didn't find anything. // Set this to nil as if we didn't find anything.
resp = nil resp = nil
} else if isTransientNetworkError(err) {
// Transient network error, treat it as if we didn't find anything
resp = nil
} else { } else {
log.Printf("Error on InstanceStateRefresh: %s", err) log.Printf("Error on InstanceStateRefresh: %s", err)
return nil, "", err return nil, "", err
...@@ -90,6 +99,9 @@ func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefre ...@@ -90,6 +99,9 @@ func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefre
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidSpotInstanceRequestID.NotFound" { if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidSpotInstanceRequestID.NotFound" {
// Set this to nil as if we didn't find anything. // Set this to nil as if we didn't find anything.
resp = nil resp = nil
} else if isTransientNetworkError(err) {
// Transient network error, treat it as if we didn't find anything
resp = nil
} else { } else {
log.Printf("Error on SpotRequestStateRefresh: %s", err) log.Printf("Error on SpotRequestStateRefresh: %s", err)
return nil, "", err return nil, "", err
...@@ -112,6 +124,8 @@ func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefre ...@@ -112,6 +124,8 @@ func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefre
func WaitForState(conf *StateChangeConf) (i interface{}, err error) { func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
log.Printf("Waiting for state to become: %s", conf.Target) log.Printf("Waiting for state to become: %s", conf.Target)
sleepSeconds := 2
maxTicks := int(TimeoutSeconds()/sleepSeconds) + 1
notfoundTick := 0 notfoundTick := 0
for { for {
...@@ -125,7 +139,7 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) { ...@@ -125,7 +139,7 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
// If we didn't find the resource, check if we have been // If we didn't find the resource, check if we have been
// not finding it for awhile, and if so, report an error. // not finding it for awhile, and if so, report an error.
notfoundTick += 1 notfoundTick += 1
if notfoundTick > 20 { if notfoundTick > maxTicks {
return nil, errors.New("couldn't find resource") return nil, errors.New("couldn't find resource")
} }
} else { } else {
...@@ -156,8 +170,36 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) { ...@@ -156,8 +170,36 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
} }
} }
time.Sleep(2 * time.Second) time.Sleep(time.Duration(sleepSeconds) * time.Second)
} }
return return
} }
func isTransientNetworkError(err error) bool {
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
return true
}
return false
}
// Returns 300 seconds (5 minutes) by default
// Some AWS operations, like copying an AMI to a distant region, take a very long time
// Allow user to override with AWS_TIMEOUT_SECONDS environment variable
func TimeoutSeconds() (seconds int) {
seconds = 300
override := os.Getenv("AWS_TIMEOUT_SECONDS")
if override != "" {
n, err := strconv.Atoi(override)
if err != nil {
log.Printf("Invalid timeout seconds '%s', using default", override)
} else {
seconds = n
}
}
log.Printf("Allowing %ds to complete (change with AWS_TIMEOUT_SECONDS)", seconds)
return seconds
}
This diff is collapsed.
// All of the methods used to communicate with the digital_ocean API
// are here. Their API is on a path to V2, so just plain JSON is used
// in place of a proper client library for now.
package digitalocean
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/mitchellh/mapstructure"
)
type DigitalOceanClientV1 struct {
// The http client for communicating
client *http.Client
// Credentials
ClientID string
APIKey string
// The base URL of the API
APIURL string
}
// Creates a new client for communicating with DO
func DigitalOceanClientNewV1(client string, key string, url string) *DigitalOceanClientV1 {
c := &DigitalOceanClientV1{
client: &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
},
APIURL: url,
ClientID: client,
APIKey: key,
}
return c
}
// Creates an SSH Key and returns it's id
func (d DigitalOceanClientV1) CreateKey(name string, pub string) (uint, error) {
params := url.Values{}
params.Set("name", name)
params.Set("ssh_pub_key", pub)
body, err := NewRequestV1(d, "ssh_keys/new", params)
if err != nil {
return 0, err
}
// Read the SSH key's ID we just created
key := body["ssh_key"].(map[string]interface{})
keyId := key["id"].(float64)
return uint(keyId), nil
}
// Destroys an SSH key
func (d DigitalOceanClientV1) DestroyKey(id uint) error {
path := fmt.Sprintf("ssh_keys/%v/destroy", id)
_, err := NewRequestV1(d, path, url.Values{})
return err
}
// Creates a droplet and returns it's id
func (d DigitalOceanClientV1) CreateDroplet(name string, size string, image string, region string, keyId uint, privateNetworking bool) (uint, error) {
params := url.Values{}
params.Set("name", name)
found_size, err := d.Size(size)
if err != nil {
return 0, fmt.Errorf("Invalid size or lookup failure: '%s': %s", size, err)
}
found_image, err := d.Image(image)
if err != nil {
return 0, fmt.Errorf("Invalid image or lookup failure: '%s': %s", image, err)
}
found_region, err := d.Region(region)
if err != nil {
return 0, fmt.Errorf("Invalid region or lookup failure: '%s': %s", region, err)
}
params.Set("size_slug", found_size.Slug)
params.Set("image_slug", found_image.Slug)
params.Set("region_slug", found_region.Slug)
params.Set("ssh_key_ids", fmt.Sprintf("%v", keyId))
params.Set("private_networking", fmt.Sprintf("%v", privateNetworking))
body, err := NewRequestV1(d, "droplets/new", params)
if err != nil {
return 0, err
}
// Read the Droplets ID
droplet := body["droplet"].(map[string]interface{})
dropletId := droplet["id"].(float64)
return uint(dropletId), err
}
// Destroys a droplet
func (d DigitalOceanClientV1) DestroyDroplet(id uint) error {
path := fmt.Sprintf("droplets/%v/destroy", id)
_, err := NewRequestV1(d, path, url.Values{})
return err
}
// Powers off a droplet
func (d DigitalOceanClientV1) PowerOffDroplet(id uint) error {
path := fmt.Sprintf("droplets/%v/power_off", id)
_, err := NewRequestV1(d, path, url.Values{})
return err
}
// Shutsdown a droplet. This is a "soft" shutdown.
func (d DigitalOceanClientV1) ShutdownDroplet(id uint) error {
path := fmt.Sprintf("droplets/%v/shutdown", id)
_, err := NewRequestV1(d, path, url.Values{})
return err
}
// Creates a snaphot of a droplet by it's ID
func (d DigitalOceanClientV1) CreateSnapshot(id uint, name string) error {
path := fmt.Sprintf("droplets/%v/snapshot", id)
params := url.Values{}
params.Set("name", name)
_, err := NewRequestV1(d, path, params)
return err
}
// Returns all available images.
func (d DigitalOceanClientV1) Images() ([]Image, error) {
resp, err := NewRequestV1(d, "images", url.Values{})
if err != nil {
return nil, err
}
var result ImagesResp
if err := mapstructure.Decode(resp, &result); err != nil {
return nil, err
}
return result.Images, nil
}
// Destroys an image by its ID.
func (d DigitalOceanClientV1) DestroyImage(id uint) error {
path := fmt.Sprintf("images/%d/destroy", id)
_, err := NewRequestV1(d, path, url.Values{})
return err
}
// Returns DO's string representation of status "off" "new" "active" etc.
func (d DigitalOceanClientV1) DropletStatus(id uint) (string, string, error) {
path := fmt.Sprintf("droplets/%v", id)
body, err := NewRequestV1(d, path, url.Values{})
if err != nil {
return "", "", err
}
var ip string
// Read the droplet's "status"
droplet := body["droplet"].(map[string]interface{})
status := droplet["status"].(string)
if droplet["ip_address"] != nil {
ip = droplet["ip_address"].(string)
}
return ip, status, err
}
// Sends an api request and returns a generic map[string]interface of
// the response.
func NewRequestV1(d DigitalOceanClientV1, path string, params url.Values) (map[string]interface{}, error) {
client := d.client
// Add the authentication parameters
params.Set("client_id", d.ClientID)
params.Set("api_key", d.APIKey)
url := fmt.Sprintf("%s/%s?%s", d.APIURL, path, params.Encode())
// Do some basic scrubbing so sensitive information doesn't appear in logs
scrubbedUrl := strings.Replace(url, d.ClientID, "CLIENT_ID", -1)
scrubbedUrl = strings.Replace(scrubbedUrl, d.APIKey, "API_KEY", -1)
log.Printf("sending new request to digitalocean: %s", scrubbedUrl)
var lastErr error
for attempts := 1; attempts < 10; attempts++ {
resp, err := client.Get(url)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
log.Printf("response from digitalocean: %s", body)
var decodedResponse map[string]interface{}
err = json.Unmarshal(body, &decodedResponse)
if err != nil {
err = errors.New(fmt.Sprintf("Failed to decode JSON response (HTTP %v) from DigitalOcean: %s",
resp.StatusCode, body))
return decodedResponse, err
}
// Check for errors sent by digitalocean
status := decodedResponse["status"].(string)
if status == "OK" {
return decodedResponse, nil
}
if status == "ERROR" {
statusRaw, ok := decodedResponse["error_message"]
if ok {
status = statusRaw.(string)
} else {
status = fmt.Sprintf(
"Unknown error. Full response body: %s", body)
}
}
lastErr = errors.New(fmt.Sprintf("Received error from DigitalOcean (%d): %s",
resp.StatusCode, status))
log.Println(lastErr)
if strings.Contains(status, "a pending event") {
// Retry, DigitalOcean sends these dumb "pending event"
// errors all the time.
time.Sleep(5 * time.Second)
continue
}
// Some other kind of error. Just return.
return decodedResponse, lastErr
}
return nil, lastErr
}
func (d DigitalOceanClientV1) Image(slug_or_name_or_id string) (Image, error) {
images, err := d.Images()
if err != nil {
return Image{}, err
}
for _, image := range images {
if strings.EqualFold(image.Slug, slug_or_name_or_id) {
return image, nil
}
}
for _, image := range images {
if strings.EqualFold(image.Name, slug_or_name_or_id) {
return image, nil
}
}
for _, image := range images {
id, err := strconv.Atoi(slug_or_name_or_id)
if err == nil {
if image.Id == uint(id) {
return image, nil
}
}
}
err = errors.New(fmt.Sprintf("Unknown image '%v'", slug_or_name_or_id))
return Image{}, err
}
// Returns all available regions.
func (d DigitalOceanClientV1) Regions() ([]Region, error) {
resp, err := NewRequestV1(d, "regions", url.Values{})
if err != nil {
return nil, err
}
var result RegionsResp
if err := mapstructure.Decode(resp, &result); err != nil {
return nil, err
}
return result.Regions, nil
}
func (d DigitalOceanClientV1) Region(slug_or_name_or_id string) (Region, error) {
regions, err := d.Regions()
if err != nil {
return Region{}, err
}
for _, region := range regions {
if strings.EqualFold(region.Slug, slug_or_name_or_id) {
return region, nil
}
}
for _, region := range regions {
if strings.EqualFold(region.Name, slug_or_name_or_id) {
return region, nil
}
}
for _, region := range regions {
id, err := strconv.Atoi(slug_or_name_or_id)
if err == nil {
if region.Id == uint(id) {
return region, nil
}
}
}
err = errors.New(fmt.Sprintf("Unknown region '%v'", slug_or_name_or_id))
return Region{}, err
}
// Returns all available sizes.
func (d DigitalOceanClientV1) Sizes() ([]Size, error) {
resp, err := NewRequestV1(d, "sizes", url.Values{})
if err != nil {
return nil, err
}
var result SizesResp
if err := mapstructure.Decode(resp, &result); err != nil {
return nil, err
}
return result.Sizes, nil
}
func (d DigitalOceanClientV1) Size(slug_or_name_or_id string) (Size, error) {
sizes, err := d.Sizes()
if err != nil {
return Size{}, err
}
for _, size := range sizes {
if strings.EqualFold(size.Slug, slug_or_name_or_id) {
return size, nil
}
}
for _, size := range sizes {
if strings.EqualFold(size.Name, slug_or_name_or_id) {
return size, nil
}
}
for _, size := range sizes {
id, err := strconv.Atoi(slug_or_name_or_id)
if err == nil {
if size.Id == uint(id) {
return size, nil
}
}
}
err = errors.New(fmt.Sprintf("Unknown size '%v'", slug_or_name_or_id))
return Size{}, err
}
This diff is collapsed.
...@@ -16,7 +16,7 @@ type Artifact struct { ...@@ -16,7 +16,7 @@ type Artifact struct {
regionName string regionName string
// The client for making API calls // The client for making API calls
client *DigitalOceanClient client DigitalOceanClient
} }
func (*Artifact) BuilderId() string { func (*Artifact) BuilderId() string {
...@@ -37,6 +37,10 @@ func (a *Artifact) String() string { ...@@ -37,6 +37,10 @@ func (a *Artifact) String() string {
return fmt.Sprintf("A snapshot was created: '%v' in region '%v'", a.snapshotName, a.regionName) return fmt.Sprintf("A snapshot was created: '%v' in region '%v'", a.snapshotName, a.regionName)
} }
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error { func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName) log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
return a.client.DestroyImage(a.snapshotId) return a.client.DestroyImage(a.snapshotId)
......
...@@ -40,6 +40,7 @@ type config struct { ...@@ -40,6 +40,7 @@ type config struct {
ClientID string `mapstructure:"client_id"` ClientID string `mapstructure:"client_id"`
APIKey string `mapstructure:"api_key"` APIKey string `mapstructure:"api_key"`
APIURL string `mapstructure:"api_url"` APIURL string `mapstructure:"api_url"`
APIToken string `mapstructure:"api_token"`
RegionID uint `mapstructure:"region_id"` RegionID uint `mapstructure:"region_id"`
SizeID uint `mapstructure:"size_id"` SizeID uint `mapstructure:"size_id"`
ImageID uint `mapstructure:"image_id"` ImageID uint `mapstructure:"image_id"`
...@@ -101,6 +102,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -101,6 +102,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.APIURL = os.Getenv("DIGITALOCEAN_API_URL") b.config.APIURL = os.Getenv("DIGITALOCEAN_API_URL")
} }
if b.config.APIToken == "" {
// Default to environment variable for api_token, if it exists
b.config.APIToken = os.Getenv("DIGITALOCEAN_API_TOKEN")
}
if b.config.Region == "" { if b.config.Region == "" {
if b.config.RegionID != 0 { if b.config.RegionID != 0 {
b.config.Region = fmt.Sprintf("%v", b.config.RegionID) b.config.Region = fmt.Sprintf("%v", b.config.RegionID)
...@@ -164,6 +170,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -164,6 +170,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"client_id": &b.config.ClientID, "client_id": &b.config.ClientID,
"api_key": &b.config.APIKey, "api_key": &b.config.APIKey,
"api_url": &b.config.APIURL, "api_url": &b.config.APIURL,
"api_token": &b.config.APIToken,
"snapshot_name": &b.config.SnapshotName, "snapshot_name": &b.config.SnapshotName,
"droplet_name": &b.config.DropletName, "droplet_name": &b.config.DropletName,
"ssh_username": &b.config.SSHUsername, "ssh_username": &b.config.SSHUsername,
...@@ -180,21 +187,23 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -180,21 +187,23 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
} }
} }
// Required configurations that will display errors if not set if b.config.APIToken == "" {
if b.config.ClientID == "" { // Required configurations that will display errors if not set
errs = packer.MultiErrorAppend( if b.config.ClientID == "" {
errs, errors.New("a client_id must be specified")) errs = packer.MultiErrorAppend(
errs, errors.New("a client_id for v1 auth or api_token for v2 auth must be specified"))
}
if b.config.APIKey == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("a api_key for v1 auth or api_token for v2 auth must be specified"))
}
} }
if b.config.APIURL == "" { if b.config.APIURL == "" {
b.config.APIURL = "https://api.digitalocean.com" b.config.APIURL = "https://api.digitalocean.com"
} }
if b.config.APIKey == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("an api_key must be specified"))
}
sshTimeout, err := time.ParseDuration(b.config.RawSSHTimeout) sshTimeout, err := time.ParseDuration(b.config.RawSSHTimeout)
if err != nil { if err != nil {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
...@@ -218,8 +227,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -218,8 +227,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
} }
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
var client DigitalOceanClient
// Initialize the DO API client // Initialize the DO API client
client := DigitalOceanClient{}.New(b.config.ClientID, b.config.APIKey, b.config.APIURL) if b.config.APIToken == "" {
client = DigitalOceanClientNewV1(b.config.ClientID, b.config.APIKey, b.config.APIURL)
} else {
client = DigitalOceanClientNewV2(b.config.APIToken, b.config.APIURL)
}
// Set up the state // Set up the state
state := new(multistep.BasicStateBag) state := new(multistep.BasicStateBag)
......
...@@ -12,7 +12,7 @@ type stepCreateDroplet struct { ...@@ -12,7 +12,7 @@ type stepCreateDroplet struct {
} }
func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction { func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient) client := state.Get("client").(DigitalOceanClient)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config) c := state.Get("config").(config)
sshKeyId := state.Get("ssh_key_id").(uint) sshKeyId := state.Get("ssh_key_id").(uint)
...@@ -44,7 +44,7 @@ func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) { ...@@ -44,7 +44,7 @@ func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) {
return return
} }
client := state.Get("client").(*DigitalOceanClient) client := state.Get("client").(DigitalOceanClient)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config) c := state.Get("config").(config)
......
...@@ -19,7 +19,7 @@ type stepCreateSSHKey struct { ...@@ -19,7 +19,7 @@ type stepCreateSSHKey struct {
} }
func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction { func (s *stepCreateSSHKey) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient) client := state.Get("client").(DigitalOceanClient)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Creating temporary ssh key for droplet...") ui.Say("Creating temporary ssh key for droplet...")
...@@ -71,15 +71,14 @@ func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) { ...@@ -71,15 +71,14 @@ func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
return return
} }
client := state.Get("client").(*DigitalOceanClient) client := state.Get("client").(DigitalOceanClient)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config) c := state.Get("config").(config)
ui.Say("Deleting temporary ssh key...") ui.Say("Deleting temporary ssh key...")
err := client.DestroyKey(s.keyId) err := client.DestroyKey(s.keyId)
curlstr := fmt.Sprintf("curl '%v/ssh_keys/%v/destroy?client_id=%v&api_key=%v'", curlstr := fmt.Sprintf("curl -H 'Authorization: Bearer #TOKEN#' -X DELETE '%v/v2/account/keys/%v'", c.APIURL, s.keyId)
c.APIURL, s.keyId, c.ClientID, c.APIKey)
if err != nil { if err != nil {
log.Printf("Error cleaning up ssh key: %v", err.Error()) log.Printf("Error cleaning up ssh key: %v", err.Error())
......
...@@ -2,6 +2,7 @@ package digitalocean ...@@ -2,6 +2,7 @@ package digitalocean
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
) )
...@@ -9,7 +10,7 @@ import ( ...@@ -9,7 +10,7 @@ import (
type stepDropletInfo struct{} type stepDropletInfo struct{}
func (s *stepDropletInfo) Run(state multistep.StateBag) multistep.StepAction { func (s *stepDropletInfo) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient) client := state.Get("client").(DigitalOceanClient)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config) c := state.Get("config").(config)
dropletId := state.Get("droplet_id").(uint) dropletId := state.Get("droplet_id").(uint)
......
...@@ -2,15 +2,16 @@ package digitalocean ...@@ -2,15 +2,16 @@ package digitalocean
import ( import (
"fmt" "fmt"
"log"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log"
) )
type stepPowerOff struct{} type stepPowerOff struct{}
func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient) client := state.Get("client").(DigitalOceanClient)
c := state.Get("config").(config) c := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
dropletId := state.Get("droplet_id").(uint) dropletId := state.Get("droplet_id").(uint)
......
...@@ -2,16 +2,17 @@ package digitalocean ...@@ -2,16 +2,17 @@ package digitalocean
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log" "log"
"time" "time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
) )
type stepShutdown struct{} type stepShutdown struct{}
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient) client := state.Get("client").(DigitalOceanClient)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
dropletId := state.Get("droplet_id").(uint) dropletId := state.Get("droplet_id").(uint)
......
...@@ -3,15 +3,16 @@ package digitalocean ...@@ -3,15 +3,16 @@ package digitalocean
import ( import (
"errors" "errors"
"fmt" "fmt"
"log"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log"
) )
type stepSnapshot struct{} type stepSnapshot struct{}
func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction { func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient) client := state.Get("client").(DigitalOceanClient)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config) c := state.Get("config").(config)
dropletId := state.Get("droplet_id").(uint) dropletId := state.Get("droplet_id").(uint)
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
// waitForState simply blocks until the droplet is in // waitForState simply blocks until the droplet is in
// a state we expect, while eventually timing out. // a state we expect, while eventually timing out.
func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, timeout time.Duration) error { func waitForDropletState(desiredState string, dropletId uint, client DigitalOceanClient, timeout time.Duration) error {
done := make(chan struct{}) done := make(chan struct{})
defer close(done) defer close(done)
......
...@@ -27,6 +27,10 @@ func (a *ExportArtifact) String() string { ...@@ -27,6 +27,10 @@ func (a *ExportArtifact) String() string {
return fmt.Sprintf("Exported Docker file: %s", a.path) return fmt.Sprintf("Exported Docker file: %s", a.path)
} }
func (a *ExportArtifact) State(name string) interface{} {
return nil
}
func (a *ExportArtifact) Destroy() error { func (a *ExportArtifact) Destroy() error {
return os.Remove(a.path) return os.Remove(a.path)
} }
...@@ -28,6 +28,10 @@ func (a *ImportArtifact) String() string { ...@@ -28,6 +28,10 @@ func (a *ImportArtifact) String() string {
return fmt.Sprintf("Imported Docker image: %s", a.Id()) return fmt.Sprintf("Imported Docker image: %s", a.Id())
} }
func (*ImportArtifact) State(name string) interface{} {
return nil
}
func (a *ImportArtifact) Destroy() error { func (a *ImportArtifact) Destroy() error {
return a.Driver.DeleteImage(a.Id()) return a.Driver.DeleteImage(a.Id())
} }
...@@ -258,10 +258,5 @@ func (d *DockerDriver) Verify() error { ...@@ -258,10 +258,5 @@ func (d *DockerDriver) Verify() error {
return err return err
} }
if v := os.Getenv("DOCKER_HOST"); v != "" {
return fmt.Errorf(
"DOCKER_HOST cannot be set with the Packer Docker builder.")
}
return nil return nil
} }
...@@ -37,3 +37,7 @@ func (a *Artifact) Id() string { ...@@ -37,3 +37,7 @@ func (a *Artifact) Id() string {
func (a *Artifact) String() string { func (a *Artifact) String() string {
return fmt.Sprintf("A disk image was created: %v", a.imageName) return fmt.Sprintf("A disk image was created: %v", a.imageName)
} }
func (a *Artifact) State(name string) interface{} {
return nil
}
...@@ -24,6 +24,25 @@ func (config *Config) getImage() Image { ...@@ -24,6 +24,25 @@ func (config *Config) getImage() Image {
return Image{Name: config.SourceImage, ProjectId: project} return Image{Name: config.SourceImage, ProjectId: project}
} }
func (config *Config) getInstanceMetadata(sshPublicKey string) map[string]string {
instanceMetadata := make(map[string]string)
// Copy metadata from config
for k, v := range config.Metadata {
instanceMetadata[k] = v
}
// Merge any existing ssh keys with our public key
sshMetaKey := "sshKeys"
sshKeys := fmt.Sprintf("%s:%s", config.SSHUsername, sshPublicKey)
if confSshKeys, exists := instanceMetadata[sshMetaKey]; exists {
sshKeys = fmt.Sprintf("%s\n%s", sshKeys, confSshKeys)
}
instanceMetadata[sshMetaKey] = sshKeys
return instanceMetadata
}
// Run executes the Packer build step that creates a GCE instance. // Run executes the Packer build step that creates a GCE instance.
func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction { func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config) config := state.Get("config").(*Config)
...@@ -39,13 +58,11 @@ func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction ...@@ -39,13 +58,11 @@ func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
DiskSizeGb: config.DiskSizeGb, DiskSizeGb: config.DiskSizeGb,
Image: config.getImage(), Image: config.getImage(),
MachineType: config.MachineType, MachineType: config.MachineType,
Metadata: map[string]string{ Metadata: config.getInstanceMetadata(sshPublicKey),
"sshKeys": fmt.Sprintf("%s:%s", config.SSHUsername, sshPublicKey), Name: name,
}, Network: config.Network,
Name: name, Tags: config.Tags,
Network: config.Network, Zone: config.Zone,
Tags: config.Tags,
Zone: config.Zone,
}) })
if err == nil { if err == nil {
......
...@@ -24,6 +24,10 @@ func (a *NullArtifact) String() string { ...@@ -24,6 +24,10 @@ func (a *NullArtifact) String() string {
return fmt.Sprintf("Did not export anything. This is the null builder") return fmt.Sprintf("Did not export anything. This is the null builder")
} }
func (a *NullArtifact) State(name string) interface{} {
return nil
}
func (a *NullArtifact) Destroy() error { func (a *NullArtifact) Destroy() error {
return nil return nil
} }
...@@ -5,11 +5,12 @@ import ( ...@@ -5,11 +5,12 @@ import (
"fmt" "fmt"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"strings" "strings"
"github.com/mitchellh/gophercloud-fork-40444fb"
) )
// AccessConfig is for common configuration related to openstack access // AccessConfig is for common configuration related to openstack access
......
...@@ -2,8 +2,9 @@ package openstack ...@@ -2,8 +2,9 @@ package openstack
import ( import (
"fmt" "fmt"
"github.com/rackspace/gophercloud"
"log" "log"
"github.com/mitchellh/gophercloud-fork-40444fb"
) )
// Artifact is an artifact implementation that contains built images. // Artifact is an artifact implementation that contains built images.
...@@ -35,6 +36,10 @@ func (a *Artifact) String() string { ...@@ -35,6 +36,10 @@ func (a *Artifact) String() string {
return fmt.Sprintf("An image was created: %v", a.ImageId) return fmt.Sprintf("An image was created: %v", a.ImageId)
} }
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error { func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d", a.ImageId) log.Printf("Destroying image: %d", a.ImageId)
return a.Conn.DeleteImageById(a.ImageId) return a.Conn.DeleteImageById(a.ImageId)
......
...@@ -8,8 +8,9 @@ import ( ...@@ -8,8 +8,9 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log" "log"
"github.com/mitchellh/gophercloud-fork-40444fb"
) )
// The unique ID for this builder // The unique ID for this builder
......
...@@ -5,9 +5,10 @@ import ( ...@@ -5,9 +5,10 @@ import (
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/racker/perigee" "github.com/racker/perigee"
"github.com/rackspace/gophercloud"
"log" "log"
"time" "time"
"github.com/mitchellh/gophercloud-fork-40444fb"
) )
// StateRefreshFunc is a function type used for StateChangeConf that is // StateRefreshFunc is a function type used for StateChangeConf that is
......
...@@ -5,8 +5,9 @@ import ( ...@@ -5,8 +5,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/rackspace/gophercloud"
"time" "time"
"github.com/mitchellh/gophercloud-fork-40444fb"
) )
// SSHAddress returns a function that can be given to the SSH communicator // SSHAddress returns a function that can be given to the SSH communicator
......
...@@ -4,7 +4,8 @@ import ( ...@@ -4,7 +4,8 @@ import (
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"github.com/mitchellh/gophercloud-fork-40444fb"
) )
type StepAllocateIp struct { type StepAllocateIp struct {
......
...@@ -4,9 +4,10 @@ import ( ...@@ -4,9 +4,10 @@ import (
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log" "log"
"time" "time"
"github.com/mitchellh/gophercloud-fork-40444fb"
) )
type stepCreateImage struct{} type stepCreateImage struct{}
......
...@@ -5,10 +5,11 @@ import ( ...@@ -5,10 +5,11 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log" "log"
"os" "os"
"runtime" "runtime"
"github.com/mitchellh/gophercloud-fork-40444fb"
) )
type StepKeyPair struct { type StepKeyPair struct {
......
...@@ -4,8 +4,9 @@ import ( ...@@ -4,8 +4,9 @@ import (
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log" "log"
"github.com/mitchellh/gophercloud-fork-40444fb"
) )
type StepRunSourceServer struct { type StepRunSourceServer struct {
......
...@@ -66,6 +66,10 @@ func (a *artifact) String() string { ...@@ -66,6 +66,10 @@ func (a *artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir) return fmt.Sprintf("VM files in directory: %s", a.dir)
} }
func (a *artifact) State(name string) interface{} {
return nil
}
func (a *artifact) Destroy() error { func (a *artifact) Destroy() error {
return os.RemoveAll(a.dir) return os.RemoveAll(a.dir)
} }
...@@ -81,7 +81,7 @@ func (s *stepAttachISO) Cleanup(state multistep.StateBag) { ...@@ -81,7 +81,7 @@ func (s *stepAttachISO) Cleanup(state multistep.StateBag) {
log.Println("Enabling default CD/DVD drive...") log.Println("Enabling default CD/DVD drive...")
command := []string{ command := []string{
"set", vmName, "set", vmName,
"--device-set", "cdrom0", "--enable", "--device-set", "cdrom0", "--enable", "--disconnect",
} }
if err := driver.Prlctl(command...); err != nil { if err := driver.Prlctl(command...); err != nil {
......
...@@ -8,8 +8,9 @@ import ( ...@@ -8,8 +8,9 @@ import (
// Artifact is the result of running the Qemu builder, namely a set // Artifact is the result of running the Qemu builder, namely a set
// of files associated with the resulting machine. // of files associated with the resulting machine.
type Artifact struct { type Artifact struct {
dir string dir string
f []string f []string
state map[string]interface{}
} }
func (*Artifact) BuilderId() string { func (*Artifact) BuilderId() string {
...@@ -28,6 +29,10 @@ func (a *Artifact) String() string { ...@@ -28,6 +29,10 @@ func (a *Artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir) return fmt.Sprintf("VM files in directory: %s", a.dir)
} }
func (a *Artifact) State(name string) interface{} {
return a.state[name]
}
func (a *Artifact) Destroy() error { func (a *Artifact) Destroy() error {
return os.RemoveAll(a.dir) return os.RemoveAll(a.dir)
} }
...@@ -56,6 +56,14 @@ var diskInterface = map[string]bool{ ...@@ -56,6 +56,14 @@ var diskInterface = map[string]bool{
"virtio": true, "virtio": true,
} }
var diskCache = map[string]bool{
"writethrough": true,
"writeback": true,
"none": true,
"unsafe": true,
"directsync": true,
}
type Builder struct { type Builder struct {
config config config config
runner multistep.Runner runner multistep.Runner
...@@ -68,9 +76,11 @@ type config struct { ...@@ -68,9 +76,11 @@ type config struct {
BootCommand []string `mapstructure:"boot_command"` BootCommand []string `mapstructure:"boot_command"`
DiskInterface string `mapstructure:"disk_interface"` DiskInterface string `mapstructure:"disk_interface"`
DiskSize uint `mapstructure:"disk_size"` DiskSize uint `mapstructure:"disk_size"`
DiskCache string `mapstructure:"disk_cache"`
FloppyFiles []string `mapstructure:"floppy_files"` FloppyFiles []string `mapstructure:"floppy_files"`
Format string `mapstructure:"format"` Format string `mapstructure:"format"`
Headless bool `mapstructure:"headless"` Headless bool `mapstructure:"headless"`
DiskImage bool `mapstructure:"disk_image"`
HTTPDir string `mapstructure:"http_directory"` HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"` HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"` HTTPPortMax uint `mapstructure:"http_port_max"`
...@@ -126,6 +136,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -126,6 +136,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.DiskSize = 40000 b.config.DiskSize = 40000
} }
if b.config.DiskCache == "" {
b.config.DiskCache = "writeback"
}
if b.config.Accelerator == "" { if b.config.Accelerator == "" {
b.config.Accelerator = "kvm" b.config.Accelerator = "kvm"
} }
...@@ -139,7 +153,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -139,7 +153,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
} }
if b.config.MachineType == "" { if b.config.MachineType == "" {
b.config.MachineType = "pc-1.0" b.config.MachineType = "pc"
} }
if b.config.OutputDir == "" { if b.config.OutputDir == "" {
...@@ -280,6 +294,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -280,6 +294,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
errs, errors.New("unrecognized disk interface type")) errs, errors.New("unrecognized disk interface type"))
} }
if _, ok := diskCache[b.config.DiskCache]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("unrecognized disk cache type"))
}
if b.config.HTTPPortMin > b.config.HTTPPortMax { if b.config.HTTPPortMin > b.config.HTTPPortMax {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, errors.New("http_port_min must be less than http_port_max")) errs, errors.New("http_port_min must be less than http_port_max"))
...@@ -412,6 +431,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -412,6 +431,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Files: b.config.FloppyFiles, Files: b.config.FloppyFiles,
}, },
new(stepCreateDisk), new(stepCreateDisk),
new(stepCopyDisk),
new(stepResizeDisk),
new(stepHTTPServer), new(stepHTTPServer),
new(stepForwardSSH), new(stepForwardSSH),
new(stepConfigureVNC), new(stepConfigureVNC),
...@@ -479,10 +500,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -479,10 +500,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
artifact := &Artifact{ artifact := &Artifact{
dir: b.config.OutputDir, dir: b.config.OutputDir,
f: files, f: files,
state: make(map[string]interface{}),
} }
artifact.state["diskName"] = state.Get("disk_filename").(string)
artifact.state["diskType"] = b.config.Format
artifact.state["diskSize"] = uint64(b.config.DiskSize)
artifact.state["domainType"] = b.config.Accelerator
return artifact, nil return artifact, nil
} }
......
package qemu
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"path/filepath"
"strings"
)
// This step copies the virtual disk that will be used as the
// hard drive for the virtual machine.
type stepCopyDisk struct{}
func (s *stepCopyDisk) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui)
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName,
strings.ToLower(config.Format)))
command := []string{
"convert",
"-f", config.Format,
isoPath,
path,
}
if config.DiskImage == false {
return multistep.ActionContinue
}
ui.Say("Copying hard drive...")
if err := driver.QemuImg(command...); err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepCopyDisk) Cleanup(state multistep.StateBag) {}
...@@ -16,8 +16,8 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction { ...@@ -16,8 +16,8 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config) config := state.Get("config").(*config)
driver := state.Get("driver").(Driver) driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName, name := config.VMName + "." + strings.ToLower(config.Format)
strings.ToLower(config.Format))) path := filepath.Join(config.OutputDir, name)
command := []string{ command := []string{
"create", "create",
...@@ -26,6 +26,10 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction { ...@@ -26,6 +26,10 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
fmt.Sprintf("%vM", config.DiskSize), fmt.Sprintf("%vM", config.DiskSize),
} }
if config.DiskImage == true {
return multistep.ActionContinue
}
ui.Say("Creating hard drive...") ui.Say("Creating hard drive...")
if err := driver.QemuImg(command...); err != nil { if err := driver.QemuImg(command...); err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err) err := fmt.Errorf("Error creating hard drive: %s", err)
...@@ -34,6 +38,8 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction { ...@@ -34,6 +38,8 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt return multistep.ActionHalt
} }
state.Put("disk_filename", name)
return multistep.ActionContinue return multistep.ActionContinue
} }
......
...@@ -2,11 +2,12 @@ package qemu ...@@ -2,11 +2,12 @@ package qemu
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log" "log"
"math/rand" "math/rand"
"net" "net"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
) )
// This step adds a NAT port forwarding definition so that SSH is available // This step adds a NAT port forwarding definition so that SSH is available
...@@ -23,9 +24,16 @@ func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction { ...@@ -23,9 +24,16 @@ func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
log.Printf("Looking for available SSH port between %d and %d", config.SSHHostPortMin, config.SSHHostPortMax) log.Printf("Looking for available SSH port between %d and %d", config.SSHHostPortMin, config.SSHHostPortMax)
var sshHostPort uint var sshHostPort uint
var offset uint = 0
portRange := int(config.SSHHostPortMax - config.SSHHostPortMin) portRange := int(config.SSHHostPortMax - config.SSHHostPortMin)
if portRange > 0 {
// Have to check if > 0 to avoid a panic
offset = uint(rand.Intn(portRange))
}
for { for {
sshHostPort = uint(rand.Intn(portRange)) + config.SSHHostPortMin sshHostPort = offset + config.SSHHostPortMin
log.Printf("Trying port: %d", sshHostPort) log.Printf("Trying port: %d", sshHostPort)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", sshHostPort)) l, err := net.Listen("tcp", fmt.Sprintf(":%d", sshHostPort))
if err == nil { if err == nil {
......
package qemu
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"path/filepath"
"strings"
)
// This step resizes the virtual disk that will be used as the
// hard drive for the virtual machine.
type stepResizeDisk struct{}
func (s *stepResizeDisk) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName,
strings.ToLower(config.Format)))
command := []string{
"resize",
path,
fmt.Sprintf("%vM", config.DiskSize),
}
if config.DiskImage == false {
return multistep.ActionContinue
}
ui.Say("Resizing hard drive...")
if err := driver.QemuImg(command...); err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepResizeDisk) Cleanup(state multistep.StateBag) {}
...@@ -2,11 +2,12 @@ package qemu ...@@ -2,11 +2,12 @@ package qemu
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log" "log"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
) )
// stepRun runs the virtual machine // stepRun runs the virtual machine
...@@ -78,13 +79,12 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error ...@@ -78,13 +79,12 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
defaultArgs["-name"] = vmName defaultArgs["-name"] = vmName
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType) defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
defaultArgs["-netdev"] = "user,id=user.0" defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:22", sshHostPort)
defaultArgs["-device"] = fmt.Sprintf("%s,netdev=user.0", config.NetDevice) defaultArgs["-device"] = fmt.Sprintf("%s,netdev=user.0", config.NetDevice)
defaultArgs["-drive"] = fmt.Sprintf("file=%s,if=%s", imgPath, config.DiskInterface) defaultArgs["-drive"] = fmt.Sprintf("file=%s,if=%s,cache=%s", imgPath, config.DiskInterface, config.DiskCache)
defaultArgs["-cdrom"] = isoPath defaultArgs["-cdrom"] = isoPath
defaultArgs["-boot"] = bootDrive defaultArgs["-boot"] = bootDrive
defaultArgs["-m"] = "512M" defaultArgs["-m"] = "512M"
defaultArgs["-redir"] = fmt.Sprintf("tcp:%v::22", sshHostPort)
defaultArgs["-vnc"] = vnc defaultArgs["-vnc"] = vnc
// Append the accelerator to the machine type if it is specified // Append the accelerator to the machine type if it is specified
......
...@@ -56,6 +56,10 @@ func (a *artifact) String() string { ...@@ -56,6 +56,10 @@ func (a *artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir) return fmt.Sprintf("VM files in directory: %s", a.dir)
} }
func (a *artifact) State(name string) interface{} {
return nil
}
func (a *artifact) Destroy() error { func (a *artifact) Destroy() error {
return os.RemoveAll(a.dir) return os.RemoveAll(a.dir)
} }
...@@ -49,11 +49,12 @@ func (d *VBox42Driver) Iso() (string, error) { ...@@ -49,11 +49,12 @@ func (d *VBox42Driver) Iso() (string, error) {
return "", err return "", err
} }
DefaultGuestAdditionsRe := regexp.MustCompile("Default Guest Additions ISO:(.*)") DefaultGuestAdditionsRe := regexp.MustCompile("Default Guest Additions ISO:(.+)")
for _, line := range strings.Split(stdout.String(), "\n") { for _, line := range strings.Split(stdout.String(), "\n") {
// Need to trim off CR character when running in windows // Need to trim off CR character when running in windows
line = strings.TrimRight(line, "\r") // Trimming whitespaces at this point helps to filter out empty value
line = strings.TrimRight(line, " \r")
matches := DefaultGuestAdditionsRe.FindStringSubmatch(line) matches := DefaultGuestAdditionsRe.FindStringSubmatch(line)
if matches == nil { if matches == nil {
...@@ -66,7 +67,7 @@ func (d *VBox42Driver) Iso() (string, error) { ...@@ -66,7 +67,7 @@ func (d *VBox42Driver) Iso() (string, error) {
return isoname, nil return isoname, nil
} }
return "", fmt.Errorf("Cannot find \"Default Guest Additions ISO\" in vboxmanage output") return "", fmt.Errorf("Cannot find \"Default Guest Additions ISO\" in vboxmanage output (or it is empty)")
} }
func (d *VBox42Driver) Import(name string, path string, flags []string) error { func (d *VBox42Driver) Import(name string, path string, flags []string) error {
...@@ -195,12 +196,12 @@ func (d *VBox42Driver) Version() (string, error) { ...@@ -195,12 +196,12 @@ func (d *VBox42Driver) Version() (string, error) {
return "", fmt.Errorf("VirtualBox is not properly setup: %s", versionOutput) return "", fmt.Errorf("VirtualBox is not properly setup: %s", versionOutput)
} }
versionRe := regexp.MustCompile("^[.0-9]+(?:_RC[0-9]+)?") versionRe := regexp.MustCompile("^([.0-9]+)(?:_(?:RC|OSEr)[0-9]+)?")
matches := versionRe.FindAllString(versionOutput, 1) matches := versionRe.FindAllStringSubmatch(versionOutput, 1)
if matches == nil { if matches == nil || len(matches[0]) != 2 {
return "", fmt.Errorf("No version found: %s", versionOutput) return "", fmt.Errorf("No version found: %s", versionOutput)
} }
log.Printf("VirtualBox version: %s", matches[0]) log.Printf("VirtualBox version: %s", matches[0][1])
return matches[0], nil return matches[0][1], nil
} }
...@@ -56,6 +56,10 @@ func (a *localArtifact) String() string { ...@@ -56,6 +56,10 @@ func (a *localArtifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir) return fmt.Sprintf("VM files in directory: %s", a.dir)
} }
func (a *localArtifact) State(name string) interface{} {
return nil
}
func (a *localArtifact) Destroy() error { func (a *localArtifact) Destroy() error {
return os.RemoveAll(a.dir) return os.RemoveAll(a.dir)
} }
...@@ -2,6 +2,7 @@ package common ...@@ -2,6 +2,7 @@ package common
import ( import (
"fmt" "fmt"
"os"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
) )
...@@ -11,6 +12,9 @@ type DriverConfig struct { ...@@ -11,6 +12,9 @@ type DriverConfig struct {
} }
func (c *DriverConfig) Prepare(t *packer.ConfigTemplate) []error { func (c *DriverConfig) Prepare(t *packer.ConfigTemplate) []error {
if c.FusionAppPath == "" {
c.FusionAppPath = os.Getenv("FUSION_APP_PATH")
}
if c.FusionAppPath == "" { if c.FusionAppPath == "" {
c.FusionAppPath = "/Applications/VMware Fusion.app" c.FusionAppPath = "/Applications/VMware Fusion.app"
} }
......
...@@ -26,7 +26,7 @@ func (d *Fusion6Driver) Clone(dst, src string) error { ...@@ -26,7 +26,7 @@ func (d *Fusion6Driver) Clone(dst, src string) error {
if _, _, err := runAndLog(cmd); err != nil { if _, _, err := runAndLog(cmd); err != nil {
if strings.Contains(err.Error(), "parameters was invalid") { if strings.Contains(err.Error(), "parameters was invalid") {
return fmt.Errorf( return fmt.Errorf(
"Clone is not supported with your version of Fusion. Packer " + "Clone is not supported with your version of Fusion. Packer "+
"only works with Fusion %s Professional or above. Please verify your version.", VMWARE_FUSION_VERSION) "only works with Fusion %s Professional or above. Please verify your version.", VMWARE_FUSION_VERSION)
} }
......
...@@ -32,38 +32,27 @@ func (s StepCleanVMX) Run(state multistep.StateBag) multistep.StepAction { ...@@ -32,38 +32,27 @@ func (s StepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt return multistep.ActionHalt
} }
if _, ok := state.GetOk("floppy_path"); ok { // Delete the floppy0 entries so the floppy is no longer mounted
// Delete the floppy0 entries so the floppy is no longer mounted ui.Message("Unmounting floppy from VMX...")
ui.Message("Unmounting floppy from VMX...") for k, _ := range vmxData {
for k, _ := range vmxData { if strings.HasPrefix(k, "floppy0.") {
if strings.HasPrefix(k, "floppy0.") { log.Printf("Deleting key: %s", k)
log.Printf("Deleting key: %s", k) delete(vmxData, k)
delete(vmxData, k)
}
} }
vmxData["floppy0.present"] = "FALSE"
} }
vmxData["floppy0.present"] = "FALSE"
if isoPathRaw, ok := state.GetOk("iso_path"); ok { devRe := regexp.MustCompile(`^ide\d:\d\.`)
isoPath := isoPathRaw.(string) for k, v := range vmxData {
ide := devRe.FindString(k)
if ide == "" || v != "cdrom-image" {
continue
}
ui.Message("Detaching ISO from CD-ROM device...") ui.Message("Detaching 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" vmxData[ide+"devicetype"] = "cdrom-raw"
if filename, ok := vmxData[filenameKey]; ok { vmxData[ide+"filename"] = "auto detect"
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 // Rewrite the VMX
......
...@@ -39,7 +39,6 @@ func TestStepCleanVMX_floppyPath(t *testing.T) { ...@@ -39,7 +39,6 @@ func TestStepCleanVMX_floppyPath(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
state.Put("floppy_path", "foo")
state.Put("vmx_path", vmxPath) state.Put("vmx_path", vmxPath)
// Test the run // Test the run
...@@ -89,7 +88,6 @@ func TestStepCleanVMX_isoPath(t *testing.T) { ...@@ -89,7 +88,6 @@ func TestStepCleanVMX_isoPath(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
state.Put("iso_path", "foo")
state.Put("vmx_path", vmxPath) state.Put("vmx_path", vmxPath)
// Test the run // Test the run
...@@ -136,6 +134,7 @@ floppy0.filetype = "file" ...@@ -136,6 +134,7 @@ floppy0.filetype = "file"
` `
const testVMXISOPath = ` const testVMXISOPath = `
ide0:0.devicetype = "cdrom-image"
ide0:0.filename = "foo" ide0:0.filename = "foo"
ide0:1.filename = "bar" ide0:1.filename = "bar"
foo = "bar" foo = "bar"
......
...@@ -137,10 +137,14 @@ LockWaitLoop: ...@@ -137,10 +137,14 @@ LockWaitLoop:
} }
} }
if runtime.GOOS == "windows" && !s.Testing { if runtime.GOOS != "darwin" && !s.Testing {
// Windows takes a while to yield control of the files when the // Windows takes a while to yield control of the files when the
// process is exiting. We just sleep here. In the future, it'd be // process is exiting. Ubuntu will yield control of the files but
// nice to find a better solution to this. // VMWare may overwrite the VMX cleanup steps that run after this,
// so we wait to ensure VMWare has exited and flushed the VMX.
// We just sleep here. In the future, it'd be nice to find a better
// solution to this.
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
} }
......
...@@ -28,6 +28,10 @@ func (a *Artifact) String() string { ...@@ -28,6 +28,10 @@ func (a *Artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir) return fmt.Sprintf("VM files in directory: %s", a.dir)
} }
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error { func (a *Artifact) Destroy() error {
return a.dir.RemoveAll() return a.dir.RemoveAll()
} }
...@@ -40,17 +40,20 @@ type config struct { ...@@ -40,17 +40,20 @@ type config struct {
ISOChecksum string `mapstructure:"iso_checksum"` ISOChecksum string `mapstructure:"iso_checksum"`
ISOChecksumType string `mapstructure:"iso_checksum_type"` ISOChecksumType string `mapstructure:"iso_checksum_type"`
ISOUrls []string `mapstructure:"iso_urls"` ISOUrls []string `mapstructure:"iso_urls"`
Version string `mapstructure:"version"`
VMName string `mapstructure:"vm_name"` VMName string `mapstructure:"vm_name"`
BootCommand []string `mapstructure:"boot_command"` BootCommand []string `mapstructure:"boot_command"`
SkipCompaction bool `mapstructure:"skip_compaction"` SkipCompaction bool `mapstructure:"skip_compaction"`
VMXTemplatePath string `mapstructure:"vmx_template_path"` VMXTemplatePath string `mapstructure:"vmx_template_path"`
RemoteType string `mapstructure:"remote_type"` RemoteType string `mapstructure:"remote_type"`
RemoteDatastore string `mapstructure:"remote_datastore"` RemoteDatastore string `mapstructure:"remote_datastore"`
RemoteHost string `mapstructure:"remote_host"` RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"`
RemotePort uint `mapstructure:"remote_port"` RemoteCacheDirectory string `mapstructure:"remote_cache_directory"`
RemoteUser string `mapstructure:"remote_username"` RemoteHost string `mapstructure:"remote_host"`
RemotePassword string `mapstructure:"remote_password"` RemotePort uint `mapstructure:"remote_port"`
RemoteUser string `mapstructure:"remote_username"`
RemotePassword string `mapstructure:"remote_password"`
RawSingleISOUrl string `mapstructure:"iso_url"` RawSingleISOUrl string `mapstructure:"iso_url"`
...@@ -110,6 +113,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -110,6 +113,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
} }
if b.config.Version == "" {
b.config.Version = "9"
}
if b.config.RemoteUser == "" { if b.config.RemoteUser == "" {
b.config.RemoteUser = "root" b.config.RemoteUser = "root"
} }
...@@ -118,24 +125,34 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -118,24 +125,34 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.RemoteDatastore = "datastore1" b.config.RemoteDatastore = "datastore1"
} }
if b.config.RemoteCacheDatastore == "" {
b.config.RemoteCacheDatastore = b.config.RemoteDatastore
}
if b.config.RemoteCacheDirectory == "" {
b.config.RemoteCacheDirectory = "packer_cache"
}
if b.config.RemotePort == 0 { if b.config.RemotePort == 0 {
b.config.RemotePort = 22 b.config.RemotePort = 22
} }
// Errors // Errors
templates := map[string]*string{ templates := map[string]*string{
"disk_name": &b.config.DiskName, "disk_name": &b.config.DiskName,
"guest_os_type": &b.config.GuestOSType, "guest_os_type": &b.config.GuestOSType,
"iso_checksum": &b.config.ISOChecksum, "iso_checksum": &b.config.ISOChecksum,
"iso_checksum_type": &b.config.ISOChecksumType, "iso_checksum_type": &b.config.ISOChecksumType,
"iso_url": &b.config.RawSingleISOUrl, "iso_url": &b.config.RawSingleISOUrl,
"vm_name": &b.config.VMName, "vm_name": &b.config.VMName,
"vmx_template_path": &b.config.VMXTemplatePath, "vmx_template_path": &b.config.VMXTemplatePath,
"remote_type": &b.config.RemoteType, "remote_type": &b.config.RemoteType,
"remote_host": &b.config.RemoteHost, "remote_host": &b.config.RemoteHost,
"remote_datastore": &b.config.RemoteDatastore, "remote_datastore": &b.config.RemoteDatastore,
"remote_user": &b.config.RemoteUser, "remote_cache_datastore": &b.config.RemoteCacheDatastore,
"remote_password": &b.config.RemotePassword, "remote_cache_directory": &b.config.RemoteCacheDirectory,
"remote_user": &b.config.RemoteUser,
"remote_password": &b.config.RemotePassword,
} }
for n, ptr := range templates { for n, ptr := range templates {
......
...@@ -134,6 +134,10 @@ func TestBuilderPrepare_Defaults(t *testing.T) { ...@@ -134,6 +134,10 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
t.Errorf("bad output dir: %s", b.config.OutputDir) t.Errorf("bad output dir: %s", b.config.OutputDir)
} }
if b.config.Version != "9" {
t.Errorf("bad Version: %s", b.config.Version)
}
if b.config.SSHWaitTimeout != (20 * time.Minute) { if b.config.SSHWaitTimeout != (20 * time.Minute) {
t.Errorf("bad wait timeout: %s", b.config.SSHWaitTimeout) t.Errorf("bad wait timeout: %s", b.config.SSHWaitTimeout)
} }
......
...@@ -17,11 +17,13 @@ func NewDriver(config *config) (vmwcommon.Driver, error) { ...@@ -17,11 +17,13 @@ func NewDriver(config *config) (vmwcommon.Driver, error) {
drivers = []vmwcommon.Driver{ drivers = []vmwcommon.Driver{
&ESX5Driver{ &ESX5Driver{
Host: config.RemoteHost, Host: config.RemoteHost,
Port: config.RemotePort, Port: config.RemotePort,
Username: config.RemoteUser, Username: config.RemoteUser,
Password: config.RemotePassword, Password: config.RemotePassword,
Datastore: config.RemoteDatastore, Datastore: config.RemoteDatastore,
CacheDatastore: config.RemoteCacheDatastore,
CacheDirectory: config.RemoteCacheDirectory,
}, },
} }
......
...@@ -22,11 +22,13 @@ import ( ...@@ -22,11 +22,13 @@ import (
// ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build // ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
// virtual machines. This driver can only manage one machine at a time. // virtual machines. This driver can only manage one machine at a time.
type ESX5Driver struct { type ESX5Driver struct {
Host string Host string
Port uint Port uint
Username string Username string
Password string Password string
Datastore string Datastore string
CacheDatastore string
CacheDirectory string
comm packer.Communicator comm packer.Communicator
outputDir string outputDir string
...@@ -55,7 +57,21 @@ func (d *ESX5Driver) IsRunning(string) (bool, error) { ...@@ -55,7 +57,21 @@ func (d *ESX5Driver) IsRunning(string) (bool, error) {
} }
func (d *ESX5Driver) Start(vmxPathLocal string, headless bool) error { func (d *ESX5Driver) Start(vmxPathLocal string, headless bool) error {
return d.sh("vim-cmd", "vmsvc/power.on", d.vmId) for i := 0; i < 20; i++ {
err := d.sh("vim-cmd", "vmsvc/power.on", d.vmId)
if err != nil {
return err
}
time.Sleep((time.Duration(i) * time.Second) + 1)
running, err := d.IsRunning(vmxPathLocal)
if err != nil {
return err
}
if running {
return nil
}
}
return errors.New("Retry limit exceeded")
} }
func (d *ESX5Driver) Stop(vmxPathLocal string) error { func (d *ESX5Driver) Stop(vmxPathLocal string) error {
...@@ -84,13 +100,7 @@ func (d *ESX5Driver) Unregister(vmxPathLocal string) error { ...@@ -84,13 +100,7 @@ func (d *ESX5Driver) Unregister(vmxPathLocal string) error {
} }
func (d *ESX5Driver) UploadISO(localPath string, checksum string, checksumType string) (string, error) { func (d *ESX5Driver) UploadISO(localPath string, checksum string, checksumType string) (string, error) {
cacheRoot, _ := filepath.Abs(".") finalPath := d.cachePath(localPath)
targetFile, err := filepath.Rel(cacheRoot, localPath)
if err != nil {
return "", err
}
finalPath := d.datastorePath(targetFile)
if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil { if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil {
return "", err return "", err
} }
...@@ -300,6 +310,10 @@ func (d *ESX5Driver) datastorePath(path string) string { ...@@ -300,6 +310,10 @@ func (d *ESX5Driver) datastorePath(path string) string {
return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.Datastore, baseDir, filepath.Base(path))) return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.Datastore, baseDir, filepath.Base(path)))
} }
func (d *ESX5Driver) cachePath(path string) string {
return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.CacheDatastore, d.CacheDirectory, filepath.Base(path)))
}
func (d *ESX5Driver) connect() error { func (d *ESX5Driver) connect() error {
address := fmt.Sprintf("%s:%d", d.Host, d.Port) address := fmt.Sprintf("%s:%d", d.Host, d.Port)
......
...@@ -15,6 +15,7 @@ type vmxTemplateData struct { ...@@ -15,6 +15,7 @@ type vmxTemplateData struct {
GuestOS string GuestOS string
DiskName string DiskName string
ISOPath string ISOPath string
Version string
} }
// This step creates the VMX file for the VM. // This step creates the VMX file for the VM.
...@@ -41,6 +42,7 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction { ...@@ -41,6 +42,7 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction {
Name: config.VMName, Name: config.VMName,
GuestOS: config.GuestOSType, GuestOS: config.GuestOSType,
DiskName: config.DiskName, DiskName: config.DiskName,
Version: config.Version,
ISOPath: isoPath, ISOPath: isoPath,
} }
...@@ -180,7 +182,7 @@ tools.upgrade.policy = "upgradeAtPowerCycle" ...@@ -180,7 +182,7 @@ tools.upgrade.policy = "upgradeAtPowerCycle"
usb.pciSlotNumber = "32" usb.pciSlotNumber = "32"
usb.present = "FALSE" usb.present = "FALSE"
virtualHW.productCompatibility = "hosted" virtualHW.productCompatibility = "hosted"
virtualHW.version = "9" virtualHW.version = "{{ .Version }}"
vmci0.id = "1861462627" vmci0.id = "1861462627"
vmci0.pciSlotNumber = "35" vmci0.pciSlotNumber = "35"
vmci0.present = "TRUE" vmci0.present = "TRUE"
......
...@@ -37,8 +37,17 @@ func (s *StepCloneVMX) Run(state multistep.StateBag) multistep.StepAction { ...@@ -37,8 +37,17 @@ func (s *StepCloneVMX) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt return multistep.ActionHalt
} }
diskName, ok := vmxData["scsi0:0.filename"] var diskName string
if !ok { if _, ok := vmxData["scsi0:0.filename"]; ok {
diskName = vmxData["scsi0:0.filename"]
}
if _, ok := vmxData["sata0:0.filename"]; ok {
diskName = vmxData["sata0:0.filename"]
}
if _, ok := vmxData["ide0:0.filename"]; ok {
diskName = vmxData["ide0:0.filename"]
}
if diskName == "" {
err := fmt.Errorf("Root disk filename could not be found!") err := fmt.Errorf("Root disk filename could not be found!")
state.Put("error", err) state.Put("error", err)
return multistep.ActionHalt return multistep.ActionHalt
......
...@@ -6,11 +6,10 @@ import ( ...@@ -6,11 +6,10 @@ import (
"path/filepath" "path/filepath"
"github.com/hashicorp/go-checkpoint" "github.com/hashicorp/go-checkpoint"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/command"
) )
func init() { func init() {
packer.VersionChecker = packerVersionCheck
checkpointResult = make(chan *checkpoint.CheckResponse, 1) checkpointResult = make(chan *checkpoint.CheckResponse, 1)
} }
...@@ -33,9 +32,9 @@ func runCheckpoint(c *config) { ...@@ -33,9 +32,9 @@ func runCheckpoint(c *config) {
return return
} }
version := packer.Version version := Version
if packer.VersionPrerelease != "" { if VersionPrerelease != "" {
version += fmt.Sprintf(".%s", packer.VersionPrerelease) version += fmt.Sprintf(".%s", VersionPrerelease)
} }
signaturePath := filepath.Join(configDir, "checkpoint_signature") signaturePath := filepath.Join(configDir, "checkpoint_signature")
...@@ -58,21 +57,23 @@ func runCheckpoint(c *config) { ...@@ -58,21 +57,23 @@ func runCheckpoint(c *config) {
checkpointResult <- resp checkpointResult <- resp
} }
// packerVersionCheck implements packer.VersionCheckFunc and is used // commandVersionCheck implements command.VersionCheckFunc and is used
// as the version checker. // as the version checker.
func packerVersionCheck(current string) (packer.VersionCheckInfo, error) { func commandVersionCheck() (command.VersionCheckInfo, error) {
// Wait for the result to come through
info := <-checkpointResult info := <-checkpointResult
if info == nil { if info == nil {
var zero packer.VersionCheckInfo var zero command.VersionCheckInfo
return zero, nil return zero, nil
} }
// Build the alerts that we may have received about our version
alerts := make([]string, len(info.Alerts)) alerts := make([]string, len(info.Alerts))
for i, a := range info.Alerts { for i, a := range info.Alerts {
alerts[i] = a.Message alerts[i] = a.Message
} }
return packer.VersionCheckInfo{ return command.VersionCheckInfo{
Outdated: info.Outdated, Outdated: info.Outdated,
Latest: info.CurrentVersion, Latest: info.CurrentVersion,
Alerts: alerts, Alerts: alerts,
......
package build package command
import ( import (
"bytes" "bytes"
...@@ -14,16 +14,20 @@ import ( ...@@ -14,16 +14,20 @@ import (
"sync" "sync"
) )
type Command byte type BuildCommand struct {
Meta
func (Command) Help() string {
return strings.TrimSpace(helpText)
} }
func (c Command) Run(env packer.Environment, args []string) int { func (c BuildCommand) Run(args []string) int {
var cfgColor, cfgDebug, cfgForce, cfgParallel bool var cfgColor, cfgDebug, cfgForce, cfgParallel bool
buildOptions := new(cmdcommon.BuildOptions) buildOptions := new(cmdcommon.BuildOptions)
env, err := c.Meta.Environment()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
return 1
}
cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError) cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError)
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
cmdFlags.BoolVar(&cfgColor, "color", true, "enable or disable color") cmdFlags.BoolVar(&cfgColor, "color", true, "enable or disable color")
...@@ -278,6 +282,28 @@ func (c Command) Run(env packer.Environment, args []string) int { ...@@ -278,6 +282,28 @@ func (c Command) Run(env packer.Environment, args []string) int {
return 0 return 0
} }
func (Command) Synopsis() string { func (BuildCommand) Help() string {
helpText := `
Usage: packer build [options] TEMPLATE
Will execute multiple builds in parallel as defined in the template.
The various artifacts created by the template will be outputted.
Options:
-debug Debug mode enabled for builds
-force Force a build to continue if artifacts exist, deletes existing artifacts
-machine-readable Machine-readable output
-except=foo,bar,baz Build all builds other than these
-only=foo,bar,baz Only build the given builds by name
-parallel=false Disable parallelization (on by default)
-var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON file containing user variables.
`
return strings.TrimSpace(helpText)
}
func (BuildCommand) Synopsis() string {
return "build image(s) from template" return "build image(s) from template"
} }
package build
import (
"bytes"
"github.com/mitchellh/packer/packer"
"testing"
)
func testEnvironment() packer.Environment {
config := packer.DefaultEnvironmentConfig()
config.Ui = &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
env, err := packer.NewEnvironment(config)
if err != nil {
panic(err)
}
return env
}
func TestCommand_Implements(t *testing.T) {
var _ packer.Command = new(Command)
}
func TestCommand_Run_NoArgs(t *testing.T) {
command := new(Command)
result := command.Run(testEnvironment(), make([]string, 0))
if result != 1 {
t.Fatalf("bad: %d", result)
}
}
func TestCommand_Run_MoreThanOneArg(t *testing.T) {
command := new(Command)
args := []string{"one", "two"}
result := command.Run(testEnvironment(), args)
if result != 1 {
t.Fatalf("bad: %d", result)
}
}
func TestCommand_Run_MissingFile(t *testing.T) {
command := new(Command)
args := []string{"i-better-not-exist"}
result := command.Run(testEnvironment(), args)
if result != 1 {
t.Fatalf("bad: %d", result)
}
}
package build
const helpText = `
Usage: packer build [options] TEMPLATE
Will execute multiple builds in parallel as defined in the template.
The various artifacts created by the template will be outputted.
Options:
-debug Debug mode enabled for builds
-force Force a build to continue if artifacts exist, deletes existing artifacts
-machine-readable Machine-readable output
-except=foo,bar,baz Build all builds other than these
-only=foo,bar,baz Only build the given builds by name
-parallel=false Disable parallelization (on by default)
-var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON file containing user variables.
`
package fix package command
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"github.com/mitchellh/packer/packer"
"log" "log"
"os" "os"
"strings" "strings"
)
type Command byte "github.com/mitchellh/packer/fix"
)
func (Command) Help() string { type FixCommand struct {
return strings.TrimSpace(helpString) Meta
} }
func (c Command) Run(env packer.Environment, args []string) int { func (c *FixCommand) Run(args []string) int {
env, err := c.Meta.Environment()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
return 1
}
cmdFlags := flag.NewFlagSet("fix", flag.ContinueOnError) cmdFlags := flag.NewFlagSet("fix", flag.ContinueOnError)
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
if err := cmdFlags.Parse(args); err != nil { if err := cmdFlags.Parse(args); err != nil {
...@@ -50,9 +55,9 @@ func (c Command) Run(env packer.Environment, args []string) int { ...@@ -50,9 +55,9 @@ func (c Command) Run(env packer.Environment, args []string) int {
tplF.Close() tplF.Close()
input := templateData input := templateData
for _, name := range FixerOrder { for _, name := range fix.FixerOrder {
var err error var err error
fixer, ok := Fixers[name] fixer, ok := fix.Fixers[name]
if !ok { if !ok {
panic("fixer not found: " + name) panic("fixer not found: " + name)
} }
...@@ -85,6 +90,30 @@ func (c Command) Run(env packer.Environment, args []string) int { ...@@ -85,6 +90,30 @@ func (c Command) Run(env packer.Environment, args []string) int {
return 0 return 0
} }
func (c Command) Synopsis() string { func (*FixCommand) Help() string {
helpText := `
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"
createtime Replaces ".CreateTime" in builder configs with "{{timestamp}}"
virtualbox-gaattach Updates VirtualBox builders using "guest_additions_attach"
to use "guest_additions_mode"
pp-vagrant-override Replaces old-style provider overrides for the Vagrant
post-processor to new-style as of Packer 0.5.0.
virtualbox-rename Updates "virtualbox" builders to "virtualbox-iso"
`
return strings.TrimSpace(helpText)
}
func (c *FixCommand) Synopsis() string {
return "fixes templates from old versions of packer" return "fixes templates from old versions of packer"
} }
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")
}
}
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"
createtime Replaces ".CreateTime" in builder configs with "{{timestamp}}"
virtualbox-gaattach Updates VirtualBox builders using "guest_additions_attach"
to use "guest_additions_mode"
pp-vagrant-override Replaces old-style provider overrides for the Vagrant
post-processor to new-style as of Packer 0.5.0.
virtualbox-rename Updates "virtualbox" builders to "virtualbox-iso"
`
package inspect package command
import ( import (
"flag" "flag"
...@@ -9,17 +9,17 @@ import ( ...@@ -9,17 +9,17 @@ import (
"strings" "strings"
) )
type Command struct{} type InspectCommand struct{
Meta
func (Command) Help() string {
return strings.TrimSpace(helpText)
} }
func (c Command) Synopsis() string { func (c *InspectCommand) Run(args []string) int {
return "see components of a template" env, err := c.Meta.Environment()
} if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
return 1
}
func (c Command) Run(env packer.Environment, args []string) int {
flags := flag.NewFlagSet("inspect", flag.ContinueOnError) flags := flag.NewFlagSet("inspect", flag.ContinueOnError)
flags.Usage = func() { env.Ui().Say(c.Help()) } flags.Usage = func() { env.Ui().Say(c.Help()) }
if err := flags.Parse(args); err != nil { if err := flags.Parse(args); err != nil {
...@@ -148,3 +148,23 @@ func (c Command) Run(env packer.Environment, args []string) int { ...@@ -148,3 +148,23 @@ func (c Command) Run(env packer.Environment, args []string) int {
return 0 return 0
} }
func (*InspectCommand) Help() string {
helpText := `
Usage: packer inspect TEMPLATE
Inspects a template, parsing and outputting the components a template
defines. This does not validate the contents of a template (other than
basic syntax by necessity).
Options:
-machine-readable Machine-readable output
`
return strings.TrimSpace(helpText)
}
func (c *InspectCommand) Synopsis() string {
return "see components of a template"
}
package inspect
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")
}
}
package inspect
const helpText = `
Usage: packer inspect TEMPLATE
Inspects a template, parsing and outputting the components a template
defines. This does not validate the contents of a template (other than
basic syntax by necessity).
Options:
-machine-readable Machine-readable output
`
package command
import (
"github.com/mitchellh/cli"
"github.com/mitchellh/packer/packer"
)
type Meta struct {
EnvConfig *packer.EnvironmentConfig
Ui cli.Ui
}
func (m *Meta) Environment() (packer.Environment, error) {
return packer.NewEnvironment(m.EnvConfig)
}
package validate package command
import ( import (
"flag" "flag"
...@@ -9,16 +9,20 @@ import ( ...@@ -9,16 +9,20 @@ import (
"strings" "strings"
) )
type Command byte type ValidateCommand struct {
Meta
func (Command) Help() string {
return strings.TrimSpace(helpString)
} }
func (c Command) Run(env packer.Environment, args []string) int { func (c *ValidateCommand) Run(args []string) int {
var cfgSyntaxOnly bool var cfgSyntaxOnly bool
buildOptions := new(cmdcommon.BuildOptions) buildOptions := new(cmdcommon.BuildOptions)
env, err := c.Meta.Environment()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
return 1
}
cmdFlags := flag.NewFlagSet("validate", flag.ContinueOnError) cmdFlags := flag.NewFlagSet("validate", flag.ContinueOnError)
cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } cmdFlags.Usage = func() { env.Ui().Say(c.Help()) }
cmdFlags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only") cmdFlags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only")
...@@ -123,6 +127,29 @@ func (c Command) Run(env packer.Environment, args []string) int { ...@@ -123,6 +127,29 @@ func (c Command) Run(env packer.Environment, args []string) int {
return 0 return 0
} }
func (Command) Synopsis() string { func (*ValidateCommand) Help() string {
helpText := `
Usage: packer validate [options] TEMPLATE
Checks the template is valid by parsing the template and also
checking the configuration with the various builders, provisioners, etc.
If it is not valid, the errors will be shown and the command will exit
with a non-zero exit status. If it is valid, it will exit with a zero
exit status.
Options:
-syntax-only Only check syntax. Do not verify config of the template.
-except=foo,bar,baz Validate all builds other than these
-only=foo,bar,baz Validate only these builds
-var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON file containing user variables.
`
return strings.TrimSpace(helpText)
}
func (*ValidateCommand) Synopsis() string {
return "check that a template is valid" return "check that a template is valid"
} }
package validate
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")
}
}
package validate
const helpString = `
Usage: packer validate [options] TEMPLATE
Checks the template is valid by parsing the template and also
checking the configuration with the various builders, provisioners, etc.
If it is not valid, the errors will be shown and the command will exit
with a non-zero exit status. If it is valid, it will exit with a zero
exit status.
Options:
-syntax-only Only check syntax. Do not verify config of the template.
-except=foo,bar,baz Validate all builds other than these
-only=foo,bar,baz Validate only these builds
-var 'key=value' Variable for templates, can be used multiple times.
-var-file=path JSON file containing user variables.
`
package command
import (
"bytes"
"fmt"
)
// VersionCommand is a Command implementation prints the version.
type VersionCommand struct {
Meta
Revision string
Version string
VersionPrerelease string
CheckFunc VersionCheckFunc
}
// VersionCheckFunc is the callback called by the Version command to
// check if there is a new version of Packer.
type VersionCheckFunc func() (VersionCheckInfo, error)
// VersionCheckInfo is the return value for the VersionCheckFunc callback
// and tells the Version command information about the latest version
// of Packer.
type VersionCheckInfo struct {
Outdated bool
Latest string
Alerts []string
}
func (c *VersionCommand) Help() string {
return ""
}
func (c *VersionCommand) Run(args []string) int {
env, err := c.Meta.Environment()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing environment: %s", err))
return 1
}
env.Ui().Machine("version", c.Version)
env.Ui().Machine("version-prelease", c.VersionPrerelease)
env.Ui().Machine("version-commit", c.Revision)
var versionString bytes.Buffer
fmt.Fprintf(&versionString, "Packer v%s", c.Version)
if c.VersionPrerelease != "" {
fmt.Fprintf(&versionString, ".%s", c.VersionPrerelease)
if c.Revision != "" {
fmt.Fprintf(&versionString, " (%s)", c.Revision)
}
}
c.Ui.Output(versionString.String())
// If we have a version check function, then let's check for
// the latest version as well.
if c.CheckFunc != nil {
// Separate the prior output with a newline
c.Ui.Output("")
// Check the latest version
info, err := c.CheckFunc()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error checking latest version: %s", err))
}
if info.Outdated {
c.Ui.Output(fmt.Sprintf(
"Your version of Packer is out of date! The latest version\n"+
"is %s. You can update by downloading from www.packer.io",
info.Latest))
}
}
return 0
}
func (c *VersionCommand) Synopsis() string {
return "Prints the Packer version"
}
package command
import (
"testing"
"github.com/mitchellh/cli"
)
func TestVersionCommand_implements(t *testing.T) {
var _ cli.Command = &VersionCommand{}
}
package main
import (
"os"
"os/signal"
"github.com/mitchellh/cli"
"github.com/mitchellh/packer/command"
)
// Commands is the mapping of all the available Terraform commands.
var Commands map[string]cli.CommandFactory
// Ui is the cli.Ui used for communicating to the outside world.
var Ui cli.Ui
const ErrorPrefix = "e:"
const OutputPrefix = "o:"
func init() {
Ui = &cli.PrefixedUi{
AskPrefix: OutputPrefix,
OutputPrefix: OutputPrefix,
InfoPrefix: OutputPrefix,
ErrorPrefix: ErrorPrefix,
Ui: &cli.BasicUi{Writer: os.Stdout},
}
meta := command.Meta{
EnvConfig: &EnvConfig,
Ui: Ui,
}
Commands = map[string]cli.CommandFactory{
"build": func() (cli.Command, error) {
return &command.BuildCommand{
Meta: meta,
}, nil
},
"fix": func() (cli.Command, error) {
return &command.FixCommand{
Meta: meta,
}, nil
},
"inspect": func() (cli.Command, error) {
return &command.InspectCommand{
Meta: meta,
}, nil
},
"validate": func() (cli.Command, error) {
return &command.ValidateCommand{
Meta: meta,
}, nil
},
"version": func() (cli.Command, error) {
return &command.VersionCommand{
Meta: meta,
Revision: GitCommit,
Version: Version,
VersionPrerelease: VersionPrerelease,
CheckFunc: commandVersionCheck,
}, nil
},
}
}
// makeShutdownCh creates an interrupt listener and returns a channel.
// A message will be sent on the channel for every interrupt received.
func makeShutdownCh() <-chan struct{} {
resultCh := make(chan struct{})
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, os.Interrupt)
go func() {
for {
<-signalCh
resultCh <- struct{}{}
}
}()
return resultCh
}
...@@ -3,10 +3,11 @@ package common ...@@ -3,10 +3,11 @@ package common
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log" "log"
"time" "time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
) )
// StepDownload downloads a remote file using the download client within // StepDownload downloads a remote file using the download client within
...@@ -70,7 +71,7 @@ func (s *StepDownload) Run(state multistep.StateBag) multistep.StepAction { ...@@ -70,7 +71,7 @@ func (s *StepDownload) Run(state multistep.StateBag) multistep.StepAction {
CopyFile: false, CopyFile: false,
Hash: HashForType(s.ChecksumType), Hash: HashForType(s.ChecksumType),
Checksum: checksum, Checksum: checksum,
UserAgent: packer.VersionString(), UserAgent: "Packer",
} }
path, err, retry := s.download(config, state) path, err, retry := s.download(config, state)
......
...@@ -13,6 +13,9 @@ import ( ...@@ -13,6 +13,9 @@ import (
"github.com/mitchellh/packer/packer/plugin" "github.com/mitchellh/packer/packer/plugin"
) )
// EnvConfig is the global EnvironmentConfig we use to initialize the CLI.
var EnvConfig packer.EnvironmentConfig
type config struct { type config struct {
DisableCheckpoint bool `json:"disable_checkpoint"` DisableCheckpoint bool `json:"disable_checkpoint"`
DisableCheckpointSignature bool `json:"disable_checkpoint_signature"` DisableCheckpointSignature bool `json:"disable_checkpoint_signature"`
...@@ -20,7 +23,6 @@ type config struct { ...@@ -20,7 +23,6 @@ type config struct {
PluginMaxPort uint PluginMaxPort uint
Builders map[string]string Builders map[string]string
Commands map[string]string
PostProcessors map[string]string `json:"post-processors"` PostProcessors map[string]string `json:"post-processors"`
Provisioners map[string]string Provisioners map[string]string
} }
...@@ -79,15 +81,6 @@ func (c *config) Discover() error { ...@@ -79,15 +81,6 @@ func (c *config) Discover() error {
return nil return nil
} }
// Returns an array of defined command names.
func (c *config) CommandNames() (result []string) {
result = make([]string, 0, len(c.Commands))
for name := range c.Commands {
result = append(result, name)
}
return
}
// This is a proper packer.BuilderFunc that can be used to load packer.Builder // This is a proper packer.BuilderFunc that can be used to load packer.Builder
// implementations from the defined plugins. // implementations from the defined plugins.
func (c *config) LoadBuilder(name string) (packer.Builder, error) { func (c *config) LoadBuilder(name string) (packer.Builder, error) {
...@@ -101,19 +94,6 @@ func (c *config) LoadBuilder(name string) (packer.Builder, error) { ...@@ -101,19 +94,6 @@ func (c *config) LoadBuilder(name string) (packer.Builder, error) {
return c.pluginClient(bin).Builder() return c.pluginClient(bin).Builder()
} }
// This is a proper packer.CommandFunc that can be used to load packer.Command
// implementations from the defined plugins.
func (c *config) LoadCommand(name string) (packer.Command, error) {
log.Printf("Loading command: %s\n", name)
bin, ok := c.Commands[name]
if !ok {
log.Printf("Command not found: %s\n", name)
return nil, nil
}
return c.pluginClient(bin).Command()
}
// This is a proper implementation of packer.HookFunc that can be used // This is a proper implementation of packer.HookFunc that can be used
// to load packer.Hook implementations from the defined plugins. // to load packer.Hook implementations from the defined plugins.
func (c *config) LoadHook(name string) (packer.Hook, error) { func (c *config) LoadHook(name string) (packer.Hook, error) {
...@@ -149,14 +129,16 @@ func (c *config) LoadProvisioner(name string) (packer.Provisioner, error) { ...@@ -149,14 +129,16 @@ func (c *config) LoadProvisioner(name string) (packer.Provisioner, error) {
func (c *config) discover(path string) error { func (c *config) discover(path string) error {
var err error var err error
err = c.discoverSingle(
filepath.Join(path, "packer-builder-*"), &c.Builders) if !filepath.IsAbs(path) {
if err != nil { path, err = filepath.Abs(path)
return err if err != nil {
return err
}
} }
err = c.discoverSingle( err = c.discoverSingle(
filepath.Join(path, "packer-command-*"), &c.Commands) filepath.Join(path, "packer-builder-*"), &c.Builders)
if err != nil { if err != nil {
return err return err
} }
......
package main
import (
"io"
"os"
)
// These are the environmental variables that determine if we log, and if
// we log whether or not the log should go to a file.
const EnvLog = "PACKER_LOG" //Set to True
const EnvLogFile = "PACKER_LOG_PATH" //Set to a file
// logOutput determines where we should send logs (if anywhere).
func logOutput() (logOutput io.Writer, err error) {
logOutput = nil
if os.Getenv(EnvLog) != "" {
logOutput = os.Stderr
if logPath := os.Getenv(EnvLogFile); logPath != "" {
var err error
logOutput, err = os.Create(logPath)
if err != nil {
return nil, err
}
}
}
return
}
...@@ -9,10 +9,13 @@ import ( ...@@ -9,10 +9,13 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync"
"github.com/mitchellh/cli"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/packer/plugin" "github.com/mitchellh/packer/packer/plugin"
"github.com/mitchellh/panicwrap" "github.com/mitchellh/panicwrap"
"github.com/mitchellh/prefixedio"
) )
func main() { func main() {
...@@ -24,63 +27,82 @@ func main() { ...@@ -24,63 +27,82 @@ func main() {
// realMain is executed from main and returns the exit status to exit with. // realMain is executed from main and returns the exit status to exit with.
func realMain() int { func realMain() int {
// If there is no explicit number of Go threads to use, then set it var wrapConfig panicwrap.WrapConfig
if os.Getenv("GOMAXPROCS") == "" {
runtime.GOMAXPROCS(runtime.NumCPU())
}
// Determine where logs should go in general (requested by the user) if !panicwrap.Wrapped(&wrapConfig) {
logWriter, err := logOutput() // Determine where logs should go in general (requested by the user)
if err != nil { logWriter, err := logOutput()
fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err) if err != nil {
return 1 fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err)
} return 1
}
if logWriter == nil {
logWriter = ioutil.Discard
}
// We also always send logs to a temporary file that we use in case // We always send logs to a temporary file that we use in case
// there is a panic. Otherwise, we delete it. // there is a panic. Otherwise, we delete it.
logTempFile, err := ioutil.TempFile("", "packer-log") logTempFile, err := ioutil.TempFile("", "packer-log")
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err) fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err)
return 1 return 1
} }
defer os.Remove(logTempFile.Name()) defer os.Remove(logTempFile.Name())
defer logTempFile.Close() defer logTempFile.Close()
// Tell the logger to log to this file
os.Setenv(EnvLog, "")
os.Setenv(EnvLogFile, "")
// Setup the prefixed readers that send data properly to
// stdout/stderr.
doneCh := make(chan struct{})
outR, outW := io.Pipe()
go copyOutput(outR, doneCh)
// Create the configuration for panicwrap and wrap our executable
wrapConfig.Handler = panicHandler(logTempFile)
wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter)
wrapConfig.Stdout = outW
exitStatus, err := panicwrap.Wrap(&wrapConfig)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't start Packer: %s", err)
return 1
}
// Reset the log variables to minimize work in the subprocess // If >= 0, we're the parent, so just exit
os.Setenv("PACKER_LOG", "") if exitStatus >= 0 {
os.Setenv("PACKER_LOG_FILE", "") // Close the stdout writer so that our copy process can finish
outW.Close()
// Create the configuration for panicwrap and wrap our executable // Wait for the output copying to finish
wrapConfig := &panicwrap.WrapConfig{ <-doneCh
Handler: panicHandler(logTempFile),
Writer: io.MultiWriter(logTempFile, logWriter),
}
exitStatus, err := panicwrap.Wrap(wrapConfig) return exitStatus
if err != nil { }
fmt.Fprintf(os.Stderr, "Couldn't start Packer: %s", err)
return 1
}
if exitStatus >= 0 { // We're the child, so just close the tempfile we made in order to
return exitStatus // save file handles since the tempfile is only used by the parent.
logTempFile.Close()
} }
// We're the child, so just close the tempfile we made in order to // Call the real main
// save file handles since the tempfile is only used by the parent.
logTempFile.Close()
return wrappedMain() return wrappedMain()
} }
// wrappedMain is called only when we're wrapped by panicwrap and // wrappedMain is called only when we're wrapped by panicwrap and
// returns the exit status to exit with. // returns the exit status to exit with.
func wrappedMain() int { func wrappedMain() int {
// If there is no explicit number of Go threads to use, then set it
if os.Getenv("GOMAXPROCS") == "" {
runtime.GOMAXPROCS(runtime.NumCPU())
}
log.SetOutput(os.Stderr) log.SetOutput(os.Stderr)
log.Printf( log.Printf(
"Packer Version: %s %s %s", "[INFO] Packer version: %s %s %s",
packer.Version, packer.VersionPrerelease, packer.GitCommit) Version, VersionPrerelease, GitCommit)
log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH) log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH)
log.Printf("Built with Go Version: %s", runtime.Version()) log.Printf("Built with Go Version: %s", runtime.Version())
...@@ -118,16 +140,14 @@ func wrappedMain() int { ...@@ -118,16 +140,14 @@ func wrappedMain() int {
defer plugin.CleanupClients() defer plugin.CleanupClients()
// Create the environment configuration // Create the environment configuration
envConfig := packer.DefaultEnvironmentConfig() EnvConfig = *packer.DefaultEnvironmentConfig()
envConfig.Cache = cache EnvConfig.Cache = cache
envConfig.Commands = config.CommandNames() EnvConfig.Components.Builder = config.LoadBuilder
envConfig.Components.Builder = config.LoadBuilder EnvConfig.Components.Hook = config.LoadHook
envConfig.Components.Command = config.LoadCommand EnvConfig.Components.PostProcessor = config.LoadPostProcessor
envConfig.Components.Hook = config.LoadHook EnvConfig.Components.Provisioner = config.LoadProvisioner
envConfig.Components.PostProcessor = config.LoadPostProcessor
envConfig.Components.Provisioner = config.LoadProvisioner
if machineReadable { if machineReadable {
envConfig.Ui = &packer.MachineReadableUi{ EnvConfig.Ui = &packer.MachineReadableUi{
Writer: os.Stdout, Writer: os.Stdout,
} }
...@@ -139,17 +159,18 @@ func wrappedMain() int { ...@@ -139,17 +159,18 @@ func wrappedMain() int {
} }
} }
env, err := packer.NewEnvironment(envConfig) //setupSignalHandlers(env)
if err != nil {
fmt.Fprintf(os.Stderr, "Packer initialization error: \n\n%s\n", err)
return 1
}
setupSignalHandlers(env) cli := &cli.CLI{
Args: args,
Commands: Commands,
HelpFunc: cli.BasicHelpFunc("packer"),
HelpWriter: os.Stdout,
}
exitCode, err := env.Cli(args) exitCode, err := cli.Run()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error()) fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err)
return 1 return 1
} }
...@@ -220,20 +241,44 @@ func loadConfig() (*config, error) { ...@@ -220,20 +241,44 @@ func loadConfig() (*config, error) {
return &config, nil return &config, nil
} }
// logOutput determines where we should send logs (if anywhere). // copyOutput uses output prefixes to determine whether data on stdout
func logOutput() (logOutput io.Writer, err error) { // should go to stdout or stderr. This is due to panicwrap using stderr
logOutput = ioutil.Discard // as the log and error channel.
if os.Getenv("PACKER_LOG") != "" { func copyOutput(r io.Reader, doneCh chan<- struct{}) {
logOutput = os.Stderr defer close(doneCh)
if logPath := os.Getenv("PACKER_LOG_PATH"); logPath != "" { pr, err := prefixedio.NewReader(r)
var err error if err != nil {
logOutput, err = os.Create(logPath) panic(err)
if err != nil { }
return nil, err
} stderrR, err := pr.Prefix(ErrorPrefix)
} if err != nil {
panic(err)
}
stdoutR, err := pr.Prefix(OutputPrefix)
if err != nil {
panic(err)
}
defaultR, err := pr.Prefix("")
if err != nil {
panic(err)
} }
return var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
io.Copy(os.Stderr, stderrR)
}()
go func() {
defer wg.Done()
io.Copy(os.Stdout, stdoutR)
}()
go func() {
defer wg.Done()
io.Copy(os.Stdout, defaultR)
}()
wg.Wait()
} }
...@@ -25,6 +25,10 @@ type Artifact interface { ...@@ -25,6 +25,10 @@ type Artifact interface {
// This is used for UI output. It can be multiple lines. // This is used for UI output. It can be multiple lines.
String() string String() string
// State allows the caller to ask for builder specific state information
// relating to the artifact instance.
State(name string) interface{}
// Destroy deletes the artifact. Packer calls this for various reasons, // Destroy deletes the artifact. Packer calls this for various reasons,
// such as if a post-processor has processed this artifact and it is // such as if a post-processor has processed this artifact and it is
// no longer needed. // no longer needed.
......
...@@ -5,6 +5,7 @@ type MockArtifact struct { ...@@ -5,6 +5,7 @@ type MockArtifact struct {
BuilderIdValue string BuilderIdValue string
FilesValue []string FilesValue []string
IdValue string IdValue string
StateValues map[string]interface{}
DestroyCalled bool DestroyCalled bool
} }
...@@ -37,6 +38,11 @@ func (*MockArtifact) String() string { ...@@ -37,6 +38,11 @@ func (*MockArtifact) String() string {
return "string" return "string"
} }
func (a *MockArtifact) State(name string) interface{} {
value, _ := a.StateValues[name]
return value
}
func (a *MockArtifact) Destroy() error { func (a *MockArtifact) Destroy() error {
a.DestroyCalled = true a.DestroyCalled = true
return nil return nil
......
...@@ -2,6 +2,7 @@ package packer ...@@ -2,6 +2,7 @@ package packer
type TestArtifact struct { type TestArtifact struct {
id string id string
state map[string]interface{}
destroyCalled bool destroyCalled bool
} }
...@@ -26,6 +27,11 @@ func (*TestArtifact) String() string { ...@@ -26,6 +27,11 @@ func (*TestArtifact) String() string {
return "string" return "string"
} }
func (a *TestArtifact) State(name string) interface{} {
value, _ := a.state[name]
return value
}
func (a *TestArtifact) Destroy() error { func (a *TestArtifact) Destroy() error {
a.destroyCalled = true a.destroyCalled = true
return nil return nil
......
package packer
// A command is a runnable sub-command of the `packer` application.
// When `packer` is called with the proper subcommand, this will be
// called.
//
// The mapping of command names to command interfaces is in the
// Environment struct.
type Command interface {
// Help should return long-form help text that includes the command-line
// usage, a brief few sentences explaining the function of the command,
// and the complete list of flags the command accepts.
Help() string
// Run should run the actual command with the given environmet and
// command-line arguments. It should return the exit status when it is
// finished.
Run(env Environment, args []string) int
// Synopsis should return a one-line, short synopsis of the command.
// This should be less than 50 characters ideally.
Synopsis() string
}
package packer
type TestCommand struct {
runArgs []string
runCalled bool
runEnv Environment
}
func (tc *TestCommand) Help() string {
return "bar"
}
func (tc *TestCommand) Run(env Environment, args []string) int {
tc.runCalled = true
tc.runArgs = args
tc.runEnv = env
return 0
}
func (tc *TestCommand) Synopsis() string {
return "foo"
}
...@@ -4,19 +4,12 @@ package packer ...@@ -4,19 +4,12 @@ package packer
import ( import (
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"sort"
"strings"
"sync"
) )
// The function type used to lookup Builder implementations. // The function type used to lookup Builder implementations.
type BuilderFunc func(name string) (Builder, error) type BuilderFunc func(name string) (Builder, error)
// The function type used to lookup Command implementations.
type CommandFunc func(name string) (Command, error)
// The function type used to lookup Hook implementations. // The function type used to lookup Hook implementations.
type HookFunc func(name string) (Hook, error) type HookFunc func(name string) (Hook, error)
...@@ -31,7 +24,6 @@ type ProvisionerFunc func(name string) (Provisioner, error) ...@@ -31,7 +24,6 @@ type ProvisionerFunc func(name string) (Provisioner, error)
// commands, etc. // commands, etc.
type ComponentFinder struct { type ComponentFinder struct {
Builder BuilderFunc Builder BuilderFunc
Command CommandFunc
Hook HookFunc Hook HookFunc
PostProcessor PostProcessorFunc PostProcessor PostProcessorFunc
Provisioner ProvisionerFunc Provisioner ProvisionerFunc
...@@ -45,7 +37,6 @@ type ComponentFinder struct { ...@@ -45,7 +37,6 @@ type ComponentFinder struct {
type Environment interface { type Environment interface {
Builder(string) (Builder, error) Builder(string) (Builder, error)
Cache() Cache Cache() Cache
Cli([]string) (int, error)
Hook(string) (Hook, error) Hook(string) (Hook, error)
PostProcessor(string) (PostProcessor, error) PostProcessor(string) (PostProcessor, error)
Provisioner(string) (Provisioner, error) Provisioner(string) (Provisioner, error)
...@@ -56,7 +47,6 @@ type Environment interface { ...@@ -56,7 +47,6 @@ type Environment interface {
// environment. // environment.
type coreEnvironment struct { type coreEnvironment struct {
cache Cache cache Cache
commands []string
components ComponentFinder components ComponentFinder
ui Ui ui Ui
} }
...@@ -64,22 +54,14 @@ type coreEnvironment struct { ...@@ -64,22 +54,14 @@ type coreEnvironment struct {
// This struct configures new environments. // This struct configures new environments.
type EnvironmentConfig struct { type EnvironmentConfig struct {
Cache Cache Cache Cache
Commands []string
Components ComponentFinder Components ComponentFinder
Ui Ui Ui Ui
} }
type helpCommandEntry struct {
i int
key string
synopsis string
}
// DefaultEnvironmentConfig returns a default EnvironmentConfig that can // DefaultEnvironmentConfig returns a default EnvironmentConfig that can
// be used to create a new enviroment with NewEnvironment with sane defaults. // be used to create a new enviroment with NewEnvironment with sane defaults.
func DefaultEnvironmentConfig() *EnvironmentConfig { func DefaultEnvironmentConfig() *EnvironmentConfig {
config := &EnvironmentConfig{} config := &EnvironmentConfig{}
config.Commands = make([]string, 0)
config.Ui = &BasicUi{ config.Ui = &BasicUi{
Reader: os.Stdin, Reader: os.Stdin,
Writer: os.Stdout, Writer: os.Stdout,
...@@ -98,7 +80,6 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error ...@@ -98,7 +80,6 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error
env := &coreEnvironment{} env := &coreEnvironment{}
env.cache = config.Cache env.cache = config.Cache
env.commands = config.Commands
env.components = config.Components env.components = config.Components
env.ui = config.Ui env.ui = config.Ui
...@@ -109,10 +90,6 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error ...@@ -109,10 +90,6 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error
env.components.Builder = func(string) (Builder, error) { return nil, nil } env.components.Builder = func(string) (Builder, error) { return nil, nil }
} }
if env.components.Command == nil {
env.components.Command = func(string) (Command, error) { return nil, nil }
}
if env.components.Hook == nil { if env.components.Hook == nil {
env.components.Hook = func(string) (Hook, error) { return nil, nil } env.components.Hook = func(string) (Hook, error) { return nil, nil }
} }
...@@ -199,159 +176,6 @@ func (e *coreEnvironment) Provisioner(name string) (p Provisioner, err error) { ...@@ -199,159 +176,6 @@ func (e *coreEnvironment) Provisioner(name string) (p Provisioner, err error) {
return return
} }
// Executes a command as if it was typed on the command-line interface.
// The return value is the exit code of the command.
func (e *coreEnvironment) Cli(args []string) (result int, err error) {
log.Printf("Environment.Cli: %#v\n", args)
// If we have no arguments, just short-circuit here and print the help
if len(args) == 0 {
e.printHelp()
return 1, nil
}
// This variable will track whether or not we're supposed to print
// the help or not.
isHelp := false
for _, arg := range args {
if arg == "-h" || arg == "--help" {
isHelp = true
break
}
}
// Trim up to the command name
for i, v := range args {
if len(v) > 0 && v[0] != '-' {
args = args[i:]
break
}
}
log.Printf("command + args: %#v", args)
version := args[0] == "version"
if !version {
for _, arg := range args {
if arg == "--version" || arg == "-v" {
version = true
break
}
}
}
var command Command
if version {
command = new(versionCommand)
}
if command == nil {
command, err = e.components.Command(args[0])
if err != nil {
return
}
// If we still don't have a command, show the help.
if command == nil {
e.ui.Error(fmt.Sprintf("Unknown command: %s\n", args[0]))
e.printHelp()
return 1, nil
}
}
// If we're supposed to print help, then print the help of the
// command rather than running it.
if isHelp {
e.ui.Say(command.Help())
return 0, nil
}
log.Printf("Executing command: %s\n", args[0])
return command.Run(e, args[1:]), nil
}
// Prints the CLI help to the UI.
func (e *coreEnvironment) printHelp() {
// Created a sorted slice of the map keys and record the longest
// command name so we can better format the output later.
maxKeyLen := 0
for _, command := range e.commands {
if len(command) > maxKeyLen {
maxKeyLen = len(command)
}
}
// Sort the keys
sort.Strings(e.commands)
// Create the communication/sync mechanisms to get the synopsis' of
// the various commands. We do this in parallel since the overhead
// of the subprocess underneath is very expensive and this speeds things
// up an incredible amount.
var wg sync.WaitGroup
ch := make(chan *helpCommandEntry)
for i, key := range e.commands {
wg.Add(1)
// Get the synopsis in a goroutine since it may take awhile
// to subprocess out.
go func(i int, key string) {
defer wg.Done()
var synopsis string
command, err := e.components.Command(key)
if err != nil {
synopsis = fmt.Sprintf("Error loading command: %s", err.Error())
} else if command == nil {
return
} else {
synopsis = command.Synopsis()
}
// Pad the key with spaces so that they're all the same width
key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key)))
// Output the command and the synopsis
ch <- &helpCommandEntry{
i: i,
key: key,
synopsis: synopsis,
}
}(i, key)
}
e.ui.Say("usage: packer [--version] [--help] <command> [<args>]\n")
e.ui.Say("Available commands are:")
// Make a goroutine that just waits for all the synopsis gathering
// to complete, and then output it.
synopsisDone := make(chan struct{})
go func() {
defer close(synopsisDone)
entries := make([]string, len(e.commands))
for entry := range ch {
e.ui.Machine("command", entry.key, entry.synopsis)
message := fmt.Sprintf(" %s %s", entry.key, entry.synopsis)
entries[entry.i] = message
}
for _, message := range entries {
if message != "" {
e.ui.Say(message)
}
}
}()
// Wait to complete getting the synopsis' then close the channel
wg.Wait()
close(ch)
<-synopsisDone
e.ui.Say("\nGlobally recognized options:")
e.ui.Say(" -machine-readable Machine-readable output format.")
}
// Returns the UI for the environment. The UI is the interface that should // Returns the UI for the environment. The UI is the interface that should
// be used for all communication with the outside world. // be used for all communication with the outside world.
func (e *coreEnvironment) Ui() Ui { func (e *coreEnvironment) Ui() Ui {
......
...@@ -6,8 +6,6 @@ import ( ...@@ -6,8 +6,6 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"reflect"
"strings"
"testing" "testing"
) )
...@@ -43,13 +41,6 @@ func testEnvironment() Environment { ...@@ -43,13 +41,6 @@ func testEnvironment() Environment {
return env return env
} }
func TestEnvironment_DefaultConfig_Commands(t *testing.T) {
config := DefaultEnvironmentConfig()
if len(config.Commands) != 0 {
t.Fatalf("bad: %#v", config.Commands)
}
}
func TestEnvironment_DefaultConfig_Ui(t *testing.T) { func TestEnvironment_DefaultConfig_Ui(t *testing.T) {
config := DefaultEnvironmentConfig() config := DefaultEnvironmentConfig()
if config.Ui == nil { if config.Ui == nil {
...@@ -91,7 +82,6 @@ func TestEnvironment_NilComponents(t *testing.T) { ...@@ -91,7 +82,6 @@ func TestEnvironment_NilComponents(t *testing.T) {
// anything but if there is a panic in the test then yeah, something // anything but if there is a panic in the test then yeah, something
// went wrong. // went wrong.
env.Builder("foo") env.Builder("foo")
env.Cli([]string{"foo"})
env.Hook("foo") env.Hook("foo")
env.PostProcessor("foo") env.PostProcessor("foo")
env.Provisioner("foo") env.Provisioner("foo")
...@@ -154,117 +144,6 @@ func TestEnvironment_Cache(t *testing.T) { ...@@ -154,117 +144,6 @@ func TestEnvironment_Cache(t *testing.T) {
} }
} }
func TestEnvironment_Cli_Error(t *testing.T) {
config := DefaultEnvironmentConfig()
config.Components.Command = func(n string) (Command, error) { return nil, errors.New("foo") }
env, _ := NewEnvironment(config)
_, err := env.Cli([]string{"foo"})
if err == nil {
t.Fatal("should have error")
}
if err.Error() != "foo" {
t.Fatalf("bad: %s", err)
}
}
func TestEnvironment_Cli_CallsRun(t *testing.T) {
command := &TestCommand{}
commands := make(map[string]Command)
commands["foo"] = command
config := &EnvironmentConfig{}
config.Commands = []string{"foo"}
config.Components.Command = func(n string) (Command, error) { return commands[n], nil }
env, _ := NewEnvironment(config)
exitCode, err := env.Cli([]string{"foo", "bar", "baz"})
if err != nil {
t.Fatalf("err: %s", err)
}
if exitCode != 0 {
t.Fatalf("bad: %d", exitCode)
}
if !command.runCalled {
t.Fatal("command should be run")
}
if command.runEnv != env {
t.Fatalf("bad env: %#v", command.runEnv)
}
if !reflect.DeepEqual(command.runArgs, []string{"bar", "baz"}) {
t.Fatalf("bad: %#v", command.runArgs)
}
}
func TestEnvironment_DefaultCli_Empty(t *testing.T) {
defaultEnv := testEnvironment()
// Test with no args
exitCode, _ := defaultEnv.Cli([]string{})
if exitCode != 1 {
t.Fatalf("bad: %d", exitCode)
}
// Test with only blank args
exitCode, _ = defaultEnv.Cli([]string{""})
if exitCode != 1 {
t.Fatalf("bad: %d", exitCode)
}
}
func TestEnvironment_DefaultCli_Help(t *testing.T) {
defaultEnv := testEnvironment()
// A little lambda to help us test the output actually contains help
testOutput := func() {
buffer := defaultEnv.Ui().(*BasicUi).Writer.(*bytes.Buffer)
output := buffer.String()
buffer.Reset()
if !strings.Contains(output, "usage: packer") {
t.Fatalf("should contain help: %#v", output)
}
}
// Test "--help"
exitCode, _ := defaultEnv.Cli([]string{"--help"})
if exitCode != 1 {
t.Fatalf("bad: %d", exitCode)
}
testOutput()
// Test "-h"
exitCode, _ = defaultEnv.Cli([]string{"--help"})
if exitCode != 1 {
t.Fatalf("bad: %d", exitCode)
}
testOutput()
}
func TestEnvironment_DefaultCli_Version(t *testing.T) {
defaultEnv := testEnvironment()
versionCommands := []string{"version", "--version", "-v"}
for _, command := range versionCommands {
exitCode, _ := defaultEnv.Cli([]string{command})
if exitCode != 0 {
t.Fatalf("bad: %d", exitCode)
}
// Test the --version and -v can appear anywhere
exitCode, _ = defaultEnv.Cli([]string{"bad", command})
if command != "version" {
if exitCode != 0 {
t.Fatalf("bad: %d", exitCode)
}
} else {
if exitCode != 1 {
t.Fatalf("bad: %d", exitCode)
}
}
}
}
func TestEnvironment_Hook(t *testing.T) { func TestEnvironment_Hook(t *testing.T) {
hook := &MockHook{} hook := &MockHook{}
hooks := make(map[string]Hook) hooks := make(map[string]Hook)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment