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:
install: make updatedeps
script:
- make test
- GOMAXPROCS=2 make test
#- go test -race ./...
matrix:
......
## 0.7.2 (unreleased)
## 0.8.0 (unreleased)
## 0.7.2 (October 28, 2014)
FEATURES:
* builder/digitalocean: API V2 support. [GH-1463]
* 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:
* 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/amazon: Extend timeout and allow user override [GH-1533]
* builder/parallels: Ignore 'The fdd0 device does not exist' [GH-1501]
* 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: 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)
......
......@@ -15,6 +15,6 @@ testrace:
go test -race $(TEST) $(TESTARGS)
updatedeps:
go get -u -v -p 2 ./...
go get -d -v -p 2 ./...
.PHONY: bin default test updatedeps
# -*- mode: 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
SRCROOT="/opt/go"
......@@ -31,7 +28,7 @@ sudo chown -R vagrant:vagrant /opt/gopath
sudo apt-get install -y curl git-core zip
SCRIPT
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
Vagrant.configure(2) do |config|
config.vm.box = "chef/ubuntu-12.04"
config.vm.provision "shell", inline: $script
......
......@@ -26,7 +26,7 @@ func (c *AccessConfig) Auth() (aws.Auth, error) {
c.SecretKey = auth.SecretKey
c.Token = auth.Token
}
if auth.Token == "" && c.Token != "" {
if c.Token != "" {
auth.Token = c.Token
}
......
......@@ -52,6 +52,10 @@ func (a *Artifact) String() string {
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 {
errors := make([]error, 0)
......
......@@ -6,6 +6,9 @@ import (
"github.com/mitchellh/goamz/ec2"
"github.com/mitchellh/multistep"
"log"
"net"
"os"
"strconv"
"time"
)
......@@ -38,6 +41,9 @@ func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" {
// Set this to nil as if we didn't find anything.
resp = nil
} else if isTransientNetworkError(err) {
// Transient network error, treat it as if we didn't find anything
resp = nil
} else {
log.Printf("Error on AMIStateRefresh: %s", err)
return nil, "", err
......@@ -64,6 +70,9 @@ func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc {
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
// Set this to nil as if we didn't find anything.
resp = nil
} else if isTransientNetworkError(err) {
// Transient network error, treat it as if we didn't find anything
resp = nil
} else {
log.Printf("Error on InstanceStateRefresh: %s", err)
return nil, "", err
......@@ -90,6 +99,9 @@ func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefre
if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidSpotInstanceRequestID.NotFound" {
// Set this to nil as if we didn't find anything.
resp = nil
} else if isTransientNetworkError(err) {
// Transient network error, treat it as if we didn't find anything
resp = nil
} else {
log.Printf("Error on SpotRequestStateRefresh: %s", err)
return nil, "", err
......@@ -112,6 +124,8 @@ func SpotRequestStateRefreshFunc(conn *ec2.EC2, spotRequestId string) StateRefre
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
log.Printf("Waiting for state to become: %s", conf.Target)
sleepSeconds := 2
maxTicks := int(TimeoutSeconds()/sleepSeconds) + 1
notfoundTick := 0
for {
......@@ -125,7 +139,7 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
// If we didn't find the resource, check if we have been
// not finding it for awhile, and if so, report an error.
notfoundTick += 1
if notfoundTick > 20 {
if notfoundTick > maxTicks {
return nil, errors.New("couldn't find resource")
}
} else {
......@@ -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
}
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 {
regionName string
// The client for making API calls
client *DigitalOceanClient
client DigitalOceanClient
}
func (*Artifact) BuilderId() 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)
}
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
return a.client.DestroyImage(a.snapshotId)
......
......@@ -40,6 +40,7 @@ type config struct {
ClientID string `mapstructure:"client_id"`
APIKey string `mapstructure:"api_key"`
APIURL string `mapstructure:"api_url"`
APIToken string `mapstructure:"api_token"`
RegionID uint `mapstructure:"region_id"`
SizeID uint `mapstructure:"size_id"`
ImageID uint `mapstructure:"image_id"`
......@@ -101,6 +102,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
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.RegionID != 0 {
b.config.Region = fmt.Sprintf("%v", b.config.RegionID)
......@@ -164,6 +170,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"client_id": &b.config.ClientID,
"api_key": &b.config.APIKey,
"api_url": &b.config.APIURL,
"api_token": &b.config.APIToken,
"snapshot_name": &b.config.SnapshotName,
"droplet_name": &b.config.DropletName,
"ssh_username": &b.config.SSHUsername,
......@@ -180,19 +187,21 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
}
if b.config.APIToken == "" {
// Required configurations that will display errors if not set
if b.config.ClientID == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("a client_id must be specified"))
}
if b.config.APIURL == "" {
b.config.APIURL = "https://api.digitalocean.com"
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("an api_key must be specified"))
errs, errors.New("a api_key for v1 auth or api_token for v2 auth must be specified"))
}
}
if b.config.APIURL == "" {
b.config.APIURL = "https://api.digitalocean.com"
}
sshTimeout, err := time.ParseDuration(b.config.RawSSHTimeout)
......@@ -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) {
var client DigitalOceanClient
// 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
state := new(multistep.BasicStateBag)
......
......@@ -12,7 +12,7 @@ type stepCreateDroplet struct {
}
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)
c := state.Get("config").(config)
sshKeyId := state.Get("ssh_key_id").(uint)
......@@ -44,7 +44,7 @@ func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) {
return
}
client := state.Get("client").(*DigitalOceanClient)
client := state.Get("client").(DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
......
......@@ -19,7 +19,7 @@ type stepCreateSSHKey struct {
}
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.Say("Creating temporary ssh key for droplet...")
......@@ -71,15 +71,14 @@ func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
return
}
client := state.Get("client").(*DigitalOceanClient)
client := state.Get("client").(DigitalOceanClient)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(config)
ui.Say("Deleting temporary ssh key...")
err := client.DestroyKey(s.keyId)
curlstr := fmt.Sprintf("curl '%v/ssh_keys/%v/destroy?client_id=%v&api_key=%v'",
c.APIURL, s.keyId, c.ClientID, c.APIKey)
curlstr := fmt.Sprintf("curl -H 'Authorization: Bearer #TOKEN#' -X DELETE '%v/v2/account/keys/%v'", c.APIURL, s.keyId)
if err != nil {
log.Printf("Error cleaning up ssh key: %v", err.Error())
......
......@@ -2,6 +2,7 @@ package digitalocean
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
......@@ -9,7 +10,7 @@ import (
type stepDropletInfo struct{}
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)
c := state.Get("config").(config)
dropletId := state.Get("droplet_id").(uint)
......
......@@ -2,15 +2,16 @@ package digitalocean
import (
"fmt"
"log"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
type stepPowerOff struct{}
func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*DigitalOceanClient)
client := state.Get("client").(DigitalOceanClient)
c := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui)
dropletId := state.Get("droplet_id").(uint)
......
......@@ -2,16 +2,17 @@ package digitalocean
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type stepShutdown struct{}
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)
dropletId := state.Get("droplet_id").(uint)
......
......@@ -3,15 +3,16 @@ package digitalocean
import (
"errors"
"fmt"
"log"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
type stepSnapshot struct{}
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)
c := state.Get("config").(config)
dropletId := state.Get("droplet_id").(uint)
......
......@@ -8,7 +8,7 @@ import (
// waitForState simply blocks until the droplet is in
// a state we expect, while eventually timing out.
func waitForDropletState(desiredState string, dropletId uint, client *DigitalOceanClient, timeout time.Duration) error {
func waitForDropletState(desiredState string, dropletId uint, client DigitalOceanClient, timeout time.Duration) error {
done := make(chan struct{})
defer close(done)
......
......@@ -27,6 +27,10 @@ func (a *ExportArtifact) String() string {
return fmt.Sprintf("Exported Docker file: %s", a.path)
}
func (a *ExportArtifact) State(name string) interface{} {
return nil
}
func (a *ExportArtifact) Destroy() error {
return os.Remove(a.path)
}
......@@ -28,6 +28,10 @@ func (a *ImportArtifact) String() string {
return fmt.Sprintf("Imported Docker image: %s", a.Id())
}
func (*ImportArtifact) State(name string) interface{} {
return nil
}
func (a *ImportArtifact) Destroy() error {
return a.Driver.DeleteImage(a.Id())
}
......@@ -258,10 +258,5 @@ func (d *DockerDriver) Verify() error {
return err
}
if v := os.Getenv("DOCKER_HOST"); v != "" {
return fmt.Errorf(
"DOCKER_HOST cannot be set with the Packer Docker builder.")
}
return nil
}
......@@ -37,3 +37,7 @@ func (a *Artifact) Id() string {
func (a *Artifact) String() string {
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 {
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.
func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
......@@ -39,9 +58,7 @@ func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
DiskSizeGb: config.DiskSizeGb,
Image: config.getImage(),
MachineType: config.MachineType,
Metadata: map[string]string{
"sshKeys": fmt.Sprintf("%s:%s", config.SSHUsername, sshPublicKey),
},
Metadata: config.getInstanceMetadata(sshPublicKey),
Name: name,
Network: config.Network,
Tags: config.Tags,
......
......@@ -24,6 +24,10 @@ func (a *NullArtifact) String() string {
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 {
return nil
}
......@@ -5,11 +5,12 @@ import (
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"net/http"
"net/url"
"os"
"strings"
"github.com/mitchellh/gophercloud-fork-40444fb"
)
// AccessConfig is for common configuration related to openstack access
......
......@@ -2,8 +2,9 @@ package openstack
import (
"fmt"
"github.com/rackspace/gophercloud"
"log"
"github.com/mitchellh/gophercloud-fork-40444fb"
)
// Artifact is an artifact implementation that contains built images.
......@@ -35,6 +36,10 @@ func (a *Artifact) String() string {
return fmt.Sprintf("An image was created: %v", a.ImageId)
}
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %d", a.ImageId)
return a.Conn.DeleteImageById(a.ImageId)
......
......@@ -8,8 +8,9 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
"github.com/mitchellh/gophercloud-fork-40444fb"
)
// The unique ID for this builder
......
......@@ -5,9 +5,10 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/racker/perigee"
"github.com/rackspace/gophercloud"
"log"
"time"
"github.com/mitchellh/gophercloud-fork-40444fb"
)
// StateRefreshFunc is a function type used for StateChangeConf that is
......
......@@ -5,8 +5,9 @@ import (
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/rackspace/gophercloud"
"time"
"github.com/mitchellh/gophercloud-fork-40444fb"
)
// SSHAddress returns a function that can be given to the SSH communicator
......
......@@ -4,7 +4,8 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"github.com/mitchellh/gophercloud-fork-40444fb"
)
type StepAllocateIp struct {
......
......@@ -4,9 +4,10 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
"time"
"github.com/mitchellh/gophercloud-fork-40444fb"
)
type stepCreateImage struct{}
......
......@@ -5,10 +5,11 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
"os"
"runtime"
"github.com/mitchellh/gophercloud-fork-40444fb"
)
type StepKeyPair struct {
......
......@@ -4,8 +4,9 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud"
"log"
"github.com/mitchellh/gophercloud-fork-40444fb"
)
type StepRunSourceServer struct {
......
......@@ -66,6 +66,10 @@ func (a *artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir)
}
func (a *artifact) State(name string) interface{} {
return nil
}
func (a *artifact) Destroy() error {
return os.RemoveAll(a.dir)
}
......@@ -81,7 +81,7 @@ func (s *stepAttachISO) Cleanup(state multistep.StateBag) {
log.Println("Enabling default CD/DVD drive...")
command := []string{
"set", vmName,
"--device-set", "cdrom0", "--enable",
"--device-set", "cdrom0", "--enable", "--disconnect",
}
if err := driver.Prlctl(command...); err != nil {
......
......@@ -10,6 +10,7 @@ import (
type Artifact struct {
dir string
f []string
state map[string]interface{}
}
func (*Artifact) BuilderId() string {
......@@ -28,6 +29,10 @@ func (a *Artifact) String() string {
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 {
return os.RemoveAll(a.dir)
}
......@@ -56,6 +56,14 @@ var diskInterface = map[string]bool{
"virtio": true,
}
var diskCache = map[string]bool{
"writethrough": true,
"writeback": true,
"none": true,
"unsafe": true,
"directsync": true,
}
type Builder struct {
config config
runner multistep.Runner
......@@ -68,9 +76,11 @@ type config struct {
BootCommand []string `mapstructure:"boot_command"`
DiskInterface string `mapstructure:"disk_interface"`
DiskSize uint `mapstructure:"disk_size"`
DiskCache string `mapstructure:"disk_cache"`
FloppyFiles []string `mapstructure:"floppy_files"`
Format string `mapstructure:"format"`
Headless bool `mapstructure:"headless"`
DiskImage bool `mapstructure:"disk_image"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"`
......@@ -126,6 +136,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.DiskSize = 40000
}
if b.config.DiskCache == "" {
b.config.DiskCache = "writeback"
}
if b.config.Accelerator == "" {
b.config.Accelerator = "kvm"
}
......@@ -139,7 +153,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
if b.config.MachineType == "" {
b.config.MachineType = "pc-1.0"
b.config.MachineType = "pc"
}
if b.config.OutputDir == "" {
......@@ -280,6 +294,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
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 {
errs = packer.MultiErrorAppend(
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
Files: b.config.FloppyFiles,
},
new(stepCreateDisk),
new(stepCopyDisk),
new(stepResizeDisk),
new(stepHTTPServer),
new(stepForwardSSH),
new(stepConfigureVNC),
......@@ -481,8 +502,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
artifact := &Artifact{
dir: b.config.OutputDir,
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
}
......
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 {
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)))
name := config.VMName + "." + strings.ToLower(config.Format)
path := filepath.Join(config.OutputDir, name)
command := []string{
"create",
......@@ -26,6 +26,10 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
fmt.Sprintf("%vM", config.DiskSize),
}
if config.DiskImage == true {
return multistep.ActionContinue
}
ui.Say("Creating hard drive...")
if err := driver.QemuImg(command...); err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
......@@ -34,6 +38,8 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt
}
state.Put("disk_filename", name)
return multistep.ActionContinue
}
......
......@@ -2,11 +2,12 @@ package qemu
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"math/rand"
"net"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// 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 {
log.Printf("Looking for available SSH port between %d and %d", config.SSHHostPortMin, config.SSHHostPortMax)
var sshHostPort uint
var offset uint = 0
portRange := int(config.SSHHostPortMax - config.SSHHostPortMin)
if portRange > 0 {
// Have to check if > 0 to avoid a panic
offset = uint(rand.Intn(portRange))
}
for {
sshHostPort = uint(rand.Intn(portRange)) + config.SSHHostPortMin
sshHostPort = offset + config.SSHHostPortMin
log.Printf("Trying port: %d", sshHostPort)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", sshHostPort))
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
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"path/filepath"
"strings"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// stepRun runs the virtual machine
......@@ -78,13 +79,12 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
defaultArgs["-name"] = vmName
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["-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["-boot"] = bootDrive
defaultArgs["-m"] = "512M"
defaultArgs["-redir"] = fmt.Sprintf("tcp:%v::22", sshHostPort)
defaultArgs["-vnc"] = vnc
// Append the accelerator to the machine type if it is specified
......
......@@ -56,6 +56,10 @@ func (a *artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir)
}
func (a *artifact) State(name string) interface{} {
return nil
}
func (a *artifact) Destroy() error {
return os.RemoveAll(a.dir)
}
......@@ -49,11 +49,12 @@ func (d *VBox42Driver) Iso() (string, error) {
return "", err
}
DefaultGuestAdditionsRe := regexp.MustCompile("Default Guest Additions ISO:(.*)")
DefaultGuestAdditionsRe := regexp.MustCompile("Default Guest Additions ISO:(.+)")
for _, line := range strings.Split(stdout.String(), "\n") {
// 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)
if matches == nil {
......@@ -66,7 +67,7 @@ func (d *VBox42Driver) Iso() (string, error) {
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 {
......@@ -195,12 +196,12 @@ func (d *VBox42Driver) Version() (string, error) {
return "", fmt.Errorf("VirtualBox is not properly setup: %s", versionOutput)
}
versionRe := regexp.MustCompile("^[.0-9]+(?:_RC[0-9]+)?")
matches := versionRe.FindAllString(versionOutput, 1)
if matches == nil {
versionRe := regexp.MustCompile("^([.0-9]+)(?:_(?:RC|OSEr)[0-9]+)?")
matches := versionRe.FindAllStringSubmatch(versionOutput, 1)
if matches == nil || len(matches[0]) != 2 {
return "", fmt.Errorf("No version found: %s", versionOutput)
}
log.Printf("VirtualBox version: %s", matches[0])
return matches[0], nil
log.Printf("VirtualBox version: %s", matches[0][1])
return matches[0][1], nil
}
......@@ -56,6 +56,10 @@ func (a *localArtifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir)
}
func (a *localArtifact) State(name string) interface{} {
return nil
}
func (a *localArtifact) Destroy() error {
return os.RemoveAll(a.dir)
}
......@@ -2,6 +2,7 @@ package common
import (
"fmt"
"os"
"github.com/mitchellh/packer/packer"
)
......@@ -11,6 +12,9 @@ type DriverConfig struct {
}
func (c *DriverConfig) Prepare(t *packer.ConfigTemplate) []error {
if c.FusionAppPath == "" {
c.FusionAppPath = os.Getenv("FUSION_APP_PATH")
}
if c.FusionAppPath == "" {
c.FusionAppPath = "/Applications/VMware Fusion.app"
}
......
......@@ -26,7 +26,7 @@ func (d *Fusion6Driver) Clone(dst, src string) error {
if _, _, err := runAndLog(cmd); err != nil {
if strings.Contains(err.Error(), "parameters was invalid") {
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)
}
......
......@@ -32,7 +32,6 @@ func (s StepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt
}
if _, ok := state.GetOk("floppy_path"); ok {
// Delete the floppy0 entries so the floppy is no longer mounted
ui.Message("Unmounting floppy from VMX...")
for k, _ := range vmxData {
......@@ -42,28 +41,18 @@ func (s StepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
}
}
vmxData["floppy0.present"] = "FALSE"
}
if isoPathRaw, ok := state.GetOk("iso_path"); ok {
isoPath := isoPathRaw.(string)
ui.Message("Detaching ISO from CD-ROM device...")
devRe := regexp.MustCompile(`^ide\d:\d\.`)
for k, _ := range vmxData {
match := devRe.FindString(k)
if match == "" {
for k, v := range vmxData {
ide := devRe.FindString(k)
if ide == "" || v != "cdrom-image" {
continue
}
filenameKey := match + "filename"
if filename, ok := vmxData[filenameKey]; ok {
if filename == isoPath {
// Change the CD-ROM device back to auto-detect to eject
vmxData[filenameKey] = "auto detect"
vmxData[match+"devicetype"] = "cdrom-raw"
}
}
}
ui.Message("Detaching ISO from CD-ROM device...")
vmxData[ide+"devicetype"] = "cdrom-raw"
vmxData[ide+"filename"] = "auto detect"
}
// Rewrite the VMX
......
......@@ -39,7 +39,6 @@ func TestStepCleanVMX_floppyPath(t *testing.T) {
t.Fatalf("err: %s", err)
}
state.Put("floppy_path", "foo")
state.Put("vmx_path", vmxPath)
// Test the run
......@@ -89,7 +88,6 @@ func TestStepCleanVMX_isoPath(t *testing.T) {
t.Fatalf("err: %s", err)
}
state.Put("iso_path", "foo")
state.Put("vmx_path", vmxPath)
// Test the run
......@@ -136,6 +134,7 @@ floppy0.filetype = "file"
`
const testVMXISOPath = `
ide0:0.devicetype = "cdrom-image"
ide0:0.filename = "foo"
ide0:1.filename = "bar"
foo = "bar"
......
......@@ -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
// process is exiting. We just sleep here. In the future, it'd be
// nice to find a better solution to this.
// process is exiting. Ubuntu will yield control of the files but
// 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)
}
......
......@@ -28,6 +28,10 @@ func (a *Artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir)
}
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
return a.dir.RemoveAll()
}
......@@ -40,6 +40,7 @@ type config struct {
ISOChecksum string `mapstructure:"iso_checksum"`
ISOChecksumType string `mapstructure:"iso_checksum_type"`
ISOUrls []string `mapstructure:"iso_urls"`
Version string `mapstructure:"version"`
VMName string `mapstructure:"vm_name"`
BootCommand []string `mapstructure:"boot_command"`
SkipCompaction bool `mapstructure:"skip_compaction"`
......@@ -47,6 +48,8 @@ type config struct {
RemoteType string `mapstructure:"remote_type"`
RemoteDatastore string `mapstructure:"remote_datastore"`
RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"`
RemoteCacheDirectory string `mapstructure:"remote_cache_directory"`
RemoteHost string `mapstructure:"remote_host"`
RemotePort uint `mapstructure:"remote_port"`
RemoteUser string `mapstructure:"remote_username"`
......@@ -110,6 +113,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
}
if b.config.Version == "" {
b.config.Version = "9"
}
if b.config.RemoteUser == "" {
b.config.RemoteUser = "root"
}
......@@ -118,6 +125,14 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
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 {
b.config.RemotePort = 22
}
......@@ -134,6 +149,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"remote_type": &b.config.RemoteType,
"remote_host": &b.config.RemoteHost,
"remote_datastore": &b.config.RemoteDatastore,
"remote_cache_datastore": &b.config.RemoteCacheDatastore,
"remote_cache_directory": &b.config.RemoteCacheDirectory,
"remote_user": &b.config.RemoteUser,
"remote_password": &b.config.RemotePassword,
}
......
......@@ -134,6 +134,10 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
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) {
t.Errorf("bad wait timeout: %s", b.config.SSHWaitTimeout)
}
......
......@@ -22,6 +22,8 @@ func NewDriver(config *config) (vmwcommon.Driver, error) {
Username: config.RemoteUser,
Password: config.RemotePassword,
Datastore: config.RemoteDatastore,
CacheDatastore: config.RemoteCacheDatastore,
CacheDirectory: config.RemoteCacheDirectory,
},
}
......
......@@ -27,6 +27,8 @@ type ESX5Driver struct {
Username string
Password string
Datastore string
CacheDatastore string
CacheDirectory string
comm packer.Communicator
outputDir string
......@@ -55,7 +57,21 @@ func (d *ESX5Driver) IsRunning(string) (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 {
......@@ -84,13 +100,7 @@ func (d *ESX5Driver) Unregister(vmxPathLocal string) error {
}
func (d *ESX5Driver) UploadISO(localPath string, checksum string, checksumType string) (string, error) {
cacheRoot, _ := filepath.Abs(".")
targetFile, err := filepath.Rel(cacheRoot, localPath)
if err != nil {
return "", err
}
finalPath := d.datastorePath(targetFile)
finalPath := d.cachePath(localPath)
if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil {
return "", err
}
......@@ -300,6 +310,10 @@ func (d *ESX5Driver) datastorePath(path string) string {
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 {
address := fmt.Sprintf("%s:%d", d.Host, d.Port)
......
......@@ -15,6 +15,7 @@ type vmxTemplateData struct {
GuestOS string
DiskName string
ISOPath string
Version string
}
// This step creates the VMX file for the VM.
......@@ -41,6 +42,7 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction {
Name: config.VMName,
GuestOS: config.GuestOSType,
DiskName: config.DiskName,
Version: config.Version,
ISOPath: isoPath,
}
......@@ -180,7 +182,7 @@ tools.upgrade.policy = "upgradeAtPowerCycle"
usb.pciSlotNumber = "32"
usb.present = "FALSE"
virtualHW.productCompatibility = "hosted"
virtualHW.version = "9"
virtualHW.version = "{{ .Version }}"
vmci0.id = "1861462627"
vmci0.pciSlotNumber = "35"
vmci0.present = "TRUE"
......
......@@ -37,8 +37,17 @@ func (s *StepCloneVMX) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt
}
diskName, ok := vmxData["scsi0:0.filename"]
if !ok {
var diskName string
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!")
state.Put("error", err)
return multistep.ActionHalt
......
......@@ -6,11 +6,10 @@ import (
"path/filepath"
"github.com/hashicorp/go-checkpoint"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/command"
)
func init() {
packer.VersionChecker = packerVersionCheck
checkpointResult = make(chan *checkpoint.CheckResponse, 1)
}
......@@ -33,9 +32,9 @@ func runCheckpoint(c *config) {
return
}
version := packer.Version
if packer.VersionPrerelease != "" {
version += fmt.Sprintf(".%s", packer.VersionPrerelease)
version := Version
if VersionPrerelease != "" {
version += fmt.Sprintf(".%s", VersionPrerelease)
}
signaturePath := filepath.Join(configDir, "checkpoint_signature")
......@@ -58,21 +57,23 @@ func runCheckpoint(c *config) {
checkpointResult <- resp
}
// packerVersionCheck implements packer.VersionCheckFunc and is used
// commandVersionCheck implements command.VersionCheckFunc and is used
// 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
if info == nil {
var zero packer.VersionCheckInfo
var zero command.VersionCheckInfo
return zero, nil
}
// Build the alerts that we may have received about our version
alerts := make([]string, len(info.Alerts))
for i, a := range info.Alerts {
alerts[i] = a.Message
}
return packer.VersionCheckInfo{
return command.VersionCheckInfo{
Outdated: info.Outdated,
Latest: info.CurrentVersion,
Alerts: alerts,
......
package build
package command
import (
"bytes"
......@@ -14,16 +14,20 @@ import (
"sync"
)
type Command byte
func (Command) Help() string {
return strings.TrimSpace(helpText)
type BuildCommand struct {
Meta
}
func (c Command) Run(env packer.Environment, args []string) int {
func (c BuildCommand) Run(args []string) int {
var cfgColor, cfgDebug, cfgForce, cfgParallel bool
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.Usage = func() { env.Ui().Say(c.Help()) }
cmdFlags.BoolVar(&cfgColor, "color", true, "enable or disable color")
......@@ -278,6 +282,28 @@ func (c Command) Run(env packer.Environment, args []string) int {
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"
}
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 (
"bytes"
"encoding/json"
"flag"
"fmt"
"github.com/mitchellh/packer/packer"
"log"
"os"
"strings"
)
type Command byte
"github.com/mitchellh/packer/fix"
)
func (Command) Help() string {
return strings.TrimSpace(helpString)
type FixCommand struct {
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.Usage = func() { env.Ui().Say(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
......@@ -50,9 +55,9 @@ func (c Command) Run(env packer.Environment, args []string) int {
tplF.Close()
input := templateData
for _, name := range FixerOrder {
for _, name := range fix.FixerOrder {
var err error
fixer, ok := Fixers[name]
fixer, ok := fix.Fixers[name]
if !ok {
panic("fixer not found: " + name)
}
......@@ -85,6 +90,30 @@ func (c Command) Run(env packer.Environment, args []string) int {
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"
}
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 (
"flag"
......@@ -9,17 +9,17 @@ import (
"strings"
)
type Command struct{}
func (Command) Help() string {
return strings.TrimSpace(helpText)
type InspectCommand struct{
Meta
}
func (c Command) Synopsis() string {
return "see components of a template"
}
func (c *InspectCommand) Run(args []string) int {
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.Usage = func() { env.Ui().Say(c.Help()) }
if err := flags.Parse(args); err != nil {
......@@ -148,3 +148,23 @@ func (c Command) Run(env packer.Environment, args []string) int {
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 (
"flag"
......@@ -9,16 +9,20 @@ import (
"strings"
)
type Command byte
func (Command) Help() string {
return strings.TrimSpace(helpString)
type ValidateCommand struct {
Meta
}
func (c Command) Run(env packer.Environment, args []string) int {
func (c *ValidateCommand) Run(args []string) int {
var cfgSyntaxOnly bool
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.Usage = func() { env.Ui().Say(c.Help()) }
cmdFlags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only")
......@@ -123,6 +127,29 @@ func (c Command) Run(env packer.Environment, args []string) int {
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"
}
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
import (
"encoding/hex"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// StepDownload downloads a remote file using the download client within
......@@ -70,7 +71,7 @@ func (s *StepDownload) Run(state multistep.StateBag) multistep.StepAction {
CopyFile: false,
Hash: HashForType(s.ChecksumType),
Checksum: checksum,
UserAgent: packer.VersionString(),
UserAgent: "Packer",
}
path, err, retry := s.download(config, state)
......
......@@ -13,6 +13,9 @@ import (
"github.com/mitchellh/packer/packer/plugin"
)
// EnvConfig is the global EnvironmentConfig we use to initialize the CLI.
var EnvConfig packer.EnvironmentConfig
type config struct {
DisableCheckpoint bool `json:"disable_checkpoint"`
DisableCheckpointSignature bool `json:"disable_checkpoint_signature"`
......@@ -20,7 +23,6 @@ type config struct {
PluginMaxPort uint
Builders map[string]string
Commands map[string]string
PostProcessors map[string]string `json:"post-processors"`
Provisioners map[string]string
}
......@@ -79,15 +81,6 @@ func (c *config) Discover() error {
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
// implementations from the defined plugins.
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()
}
// 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
// to load packer.Hook implementations from the defined plugins.
func (c *config) LoadHook(name string) (packer.Hook, error) {
......@@ -149,14 +129,16 @@ func (c *config) LoadProvisioner(name string) (packer.Provisioner, error) {
func (c *config) discover(path string) error {
var err error
err = c.discoverSingle(
filepath.Join(path, "packer-builder-*"), &c.Builders)
if !filepath.IsAbs(path) {
path, err = filepath.Abs(path)
if err != nil {
return err
}
}
err = c.discoverSingle(
filepath.Join(path, "packer-command-*"), &c.Commands)
filepath.Join(path, "packer-builder-*"), &c.Builders)
if err != nil {
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 (
"os"
"path/filepath"
"runtime"
"sync"
"github.com/mitchellh/cli"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/packer/plugin"
"github.com/mitchellh/panicwrap"
"github.com/mitchellh/prefixedio"
)
func main() {
......@@ -24,19 +27,20 @@ func main() {
// realMain is executed from main and returns the exit status to exit with.
func realMain() int {
// If there is no explicit number of Go threads to use, then set it
if os.Getenv("GOMAXPROCS") == "" {
runtime.GOMAXPROCS(runtime.NumCPU())
}
var wrapConfig panicwrap.WrapConfig
if !panicwrap.Wrapped(&wrapConfig) {
// Determine where logs should go in general (requested by the user)
logWriter, err := logOutput()
if err != nil {
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.
logTempFile, err := ioutil.TempFile("", "packer-log")
if err != nil {
......@@ -46,41 +50,59 @@ func realMain() int {
defer os.Remove(logTempFile.Name())
defer logTempFile.Close()
// Reset the log variables to minimize work in the subprocess
os.Setenv("PACKER_LOG", "")
os.Setenv("PACKER_LOG_FILE", "")
// Tell the logger to log to this file
os.Setenv(EnvLog, "")
os.Setenv(EnvLogFile, "")
// Create the configuration for panicwrap and wrap our executable
wrapConfig := &panicwrap.WrapConfig{
Handler: panicHandler(logTempFile),
Writer: io.MultiWriter(logTempFile, logWriter),
}
// Setup the prefixed readers that send data properly to
// stdout/stderr.
doneCh := make(chan struct{})
outR, outW := io.Pipe()
go copyOutput(outR, doneCh)
exitStatus, err := panicwrap.Wrap(wrapConfig)
// 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
}
// If >= 0, we're the parent, so just exit
if exitStatus >= 0 {
// Close the stdout writer so that our copy process can finish
outW.Close()
// Wait for the output copying to finish
<-doneCh
return exitStatus
}
// We're the child, so just close the tempfile we made in order to
// save file handles since the tempfile is only used by the parent.
logTempFile.Close()
}
// Call the real main
return wrappedMain()
}
// wrappedMain is called only when we're wrapped by panicwrap and
// returns the exit status to exit with.
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.Printf(
"Packer Version: %s %s %s",
packer.Version, packer.VersionPrerelease, packer.GitCommit)
"[INFO] Packer version: %s %s %s",
Version, VersionPrerelease, GitCommit)
log.Printf("Packer Target OS/Arch: %s %s", runtime.GOOS, runtime.GOARCH)
log.Printf("Built with Go Version: %s", runtime.Version())
......@@ -118,16 +140,14 @@ func wrappedMain() int {
defer plugin.CleanupClients()
// Create the environment configuration
envConfig := packer.DefaultEnvironmentConfig()
envConfig.Cache = cache
envConfig.Commands = config.CommandNames()
envConfig.Components.Builder = config.LoadBuilder
envConfig.Components.Command = config.LoadCommand
envConfig.Components.Hook = config.LoadHook
envConfig.Components.PostProcessor = config.LoadPostProcessor
envConfig.Components.Provisioner = config.LoadProvisioner
EnvConfig = *packer.DefaultEnvironmentConfig()
EnvConfig.Cache = cache
EnvConfig.Components.Builder = config.LoadBuilder
EnvConfig.Components.Hook = config.LoadHook
EnvConfig.Components.PostProcessor = config.LoadPostProcessor
EnvConfig.Components.Provisioner = config.LoadProvisioner
if machineReadable {
envConfig.Ui = &packer.MachineReadableUi{
EnvConfig.Ui = &packer.MachineReadableUi{
Writer: os.Stdout,
}
......@@ -139,17 +159,18 @@ func wrappedMain() int {
}
}
env, err := packer.NewEnvironment(envConfig)
if err != nil {
fmt.Fprintf(os.Stderr, "Packer initialization error: \n\n%s\n", err)
return 1
}
//setupSignalHandlers(env)
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 {
fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error())
fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err)
return 1
}
......@@ -220,20 +241,44 @@ func loadConfig() (*config, error) {
return &config, nil
}
// logOutput determines where we should send logs (if anywhere).
func logOutput() (logOutput io.Writer, err error) {
logOutput = ioutil.Discard
if os.Getenv("PACKER_LOG") != "" {
logOutput = os.Stderr
// copyOutput uses output prefixes to determine whether data on stdout
// should go to stdout or stderr. This is due to panicwrap using stderr
// as the log and error channel.
func copyOutput(r io.Reader, doneCh chan<- struct{}) {
defer close(doneCh)
if logPath := os.Getenv("PACKER_LOG_PATH"); logPath != "" {
var err error
logOutput, err = os.Create(logPath)
pr, err := prefixedio.NewReader(r)
if err != nil {
return nil, err
panic(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 {
// This is used for UI output. It can be multiple lines.
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,
// such as if a post-processor has processed this artifact and it is
// no longer needed.
......
......@@ -5,6 +5,7 @@ type MockArtifact struct {
BuilderIdValue string
FilesValue []string
IdValue string
StateValues map[string]interface{}
DestroyCalled bool
}
......@@ -37,6 +38,11 @@ func (*MockArtifact) String() string {
return "string"
}
func (a *MockArtifact) State(name string) interface{} {
value, _ := a.StateValues[name]
return value
}
func (a *MockArtifact) Destroy() error {
a.DestroyCalled = true
return nil
......
......@@ -2,6 +2,7 @@ package packer
type TestArtifact struct {
id string
state map[string]interface{}
destroyCalled bool
}
......@@ -26,6 +27,11 @@ func (*TestArtifact) String() string {
return "string"
}
func (a *TestArtifact) State(name string) interface{} {
value, _ := a.state[name]
return value
}
func (a *TestArtifact) Destroy() error {
a.destroyCalled = true
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
import (
"errors"
"fmt"
"log"
"os"
"sort"
"strings"
"sync"
)
// The function type used to lookup Builder implementations.
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.
type HookFunc func(name string) (Hook, error)
......@@ -31,7 +24,6 @@ type ProvisionerFunc func(name string) (Provisioner, error)
// commands, etc.
type ComponentFinder struct {
Builder BuilderFunc
Command CommandFunc
Hook HookFunc
PostProcessor PostProcessorFunc
Provisioner ProvisionerFunc
......@@ -45,7 +37,6 @@ type ComponentFinder struct {
type Environment interface {
Builder(string) (Builder, error)
Cache() Cache
Cli([]string) (int, error)
Hook(string) (Hook, error)
PostProcessor(string) (PostProcessor, error)
Provisioner(string) (Provisioner, error)
......@@ -56,7 +47,6 @@ type Environment interface {
// environment.
type coreEnvironment struct {
cache Cache
commands []string
components ComponentFinder
ui Ui
}
......@@ -64,22 +54,14 @@ type coreEnvironment struct {
// This struct configures new environments.
type EnvironmentConfig struct {
Cache Cache
Commands []string
Components ComponentFinder
Ui Ui
}
type helpCommandEntry struct {
i int
key string
synopsis string
}
// DefaultEnvironmentConfig returns a default EnvironmentConfig that can
// be used to create a new enviroment with NewEnvironment with sane defaults.
func DefaultEnvironmentConfig() *EnvironmentConfig {
config := &EnvironmentConfig{}
config.Commands = make([]string, 0)
config.Ui = &BasicUi{
Reader: os.Stdin,
Writer: os.Stdout,
......@@ -98,7 +80,6 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error
env := &coreEnvironment{}
env.cache = config.Cache
env.commands = config.Commands
env.components = config.Components
env.ui = config.Ui
......@@ -109,10 +90,6 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error
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 {
env.components.Hook = func(string) (Hook, error) { return nil, nil }
}
......@@ -199,159 +176,6 @@ func (e *coreEnvironment) Provisioner(name string) (p Provisioner, err error) {
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
// be used for all communication with the outside world.
func (e *coreEnvironment) Ui() Ui {
......
......@@ -6,8 +6,6 @@ import (
"io/ioutil"
"log"
"os"
"reflect"
"strings"
"testing"
)
......@@ -43,13 +41,6 @@ func testEnvironment() Environment {
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) {
config := DefaultEnvironmentConfig()
if config.Ui == nil {
......@@ -91,7 +82,6 @@ func TestEnvironment_NilComponents(t *testing.T) {
// anything but if there is a panic in the test then yeah, something
// went wrong.
env.Builder("foo")
env.Cli([]string{"foo"})
env.Hook("foo")
env.PostProcessor("foo")
env.Provisioner("foo")
......@@ -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) {
hook := &MockHook{}
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