mirror of https://github.com/hashicorp/packer
commit
431647997c
@ -1,38 +1,20 @@
|
||||
NO_COLOR=\033[0m
|
||||
OK_COLOR=\033[32;01m
|
||||
ERROR_COLOR=\033[31;01m
|
||||
WARN_COLOR=\033[33;01m
|
||||
DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
|
||||
UNAME := $(shell uname -s)
|
||||
ifeq ($(UNAME),Darwin)
|
||||
ECHO=echo
|
||||
else
|
||||
ECHO=/bin/echo -e
|
||||
endif
|
||||
TEST?=./...
|
||||
|
||||
all: deps
|
||||
@mkdir -p bin/
|
||||
@$(ECHO) "$(OK_COLOR)==> Building$(NO_COLOR)"
|
||||
@bash --norc -i ./scripts/devcompile.sh
|
||||
default: test
|
||||
|
||||
deps:
|
||||
@$(ECHO) "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)"
|
||||
@go get -d -v ./...
|
||||
@echo $(DEPS) | xargs -n1 go get -d
|
||||
bin:
|
||||
@sh -c "$(CURDIR)/scripts/build.sh"
|
||||
|
||||
updatedeps:
|
||||
@$(ECHO) "$(OK_COLOR)==> Updating all dependencies$(NO_COLOR)"
|
||||
@go get -d -v -u ./...
|
||||
@echo $(DEPS) | xargs -n1 go get -d -u
|
||||
dev:
|
||||
@TF_DEV=1 sh -c "$(CURDIR)/scripts/build.sh"
|
||||
|
||||
clean:
|
||||
@rm -rf bin/ local/ pkg/ src/ website/.sass-cache website/build
|
||||
test:
|
||||
go test $(TEST) $(TESTARGS) -timeout=10s
|
||||
|
||||
format:
|
||||
go fmt ./...
|
||||
testrace:
|
||||
go test -race $(TEST) $(TESTARGS)
|
||||
|
||||
test: deps
|
||||
@$(ECHO) "$(OK_COLOR)==> Testing Packer...$(NO_COLOR)"
|
||||
go test ./...
|
||||
updatedeps:
|
||||
go get -d -v -p 2 ./...
|
||||
|
||||
.PHONY: all clean deps format test updatedeps
|
||||
.PHONY: bin default test updatedeps
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
$script = <<SCRIPT
|
||||
SRCROOT="/opt/go"
|
||||
|
||||
# Install Go
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential mercurial
|
||||
sudo hg clone -u release https://code.google.com/p/go ${SRCROOT}
|
||||
cd ${SRCROOT}/src
|
||||
sudo ./all.bash
|
||||
|
||||
# Setup the GOPATH
|
||||
sudo mkdir -p /opt/gopath
|
||||
cat <<EOF >/tmp/gopath.sh
|
||||
export GOPATH="/opt/gopath"
|
||||
export PATH="/opt/go/bin:\$GOPATH/bin:\$PATH"
|
||||
EOF
|
||||
sudo mv /tmp/gopath.sh /etc/profile.d/gopath.sh
|
||||
sudo chmod 0755 /etc/profile.d/gopath.sh
|
||||
|
||||
# Make sure the gopath is usable by vagrant
|
||||
sudo chown -R vagrant:vagrant $SRCROOT
|
||||
sudo chown -R vagrant:vagrant /opt/gopath
|
||||
|
||||
# Install some other stuff we need
|
||||
sudo apt-get install -y curl git-core zip
|
||||
SCRIPT
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = "chef/ubuntu-12.04"
|
||||
|
||||
config.vm.provision "shell", inline: $script
|
||||
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
|
||||
["vmware_fusion", "vmware_workstation"].each do |p|
|
||||
config.vm.provider "p" do |v|
|
||||
v.vmx["memsize"] = "2048"
|
||||
v.vmx["numvcpus"] = "2"
|
||||
v.vmx["cpuid.coresPerSocket"] = "1"
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,31 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepCheckRootDevice makes sure the root device on the AMI is EBS-backed.
|
||||
type StepCheckRootDevice struct{}
|
||||
|
||||
func (s *StepCheckRootDevice) Run(state multistep.StateBag) multistep.StepAction {
|
||||
image := state.Get("source_image").(*ec2.Image)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Checking the root device on source AMI...")
|
||||
|
||||
// It must be EBS-backed otherwise the build won't work
|
||||
if image.RootDeviceType != "ebs" {
|
||||
err := fmt.Errorf("The root device of the source AMI must be EBS-backed.")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCheckRootDevice) Cleanup(multistep.StateBag) {}
|
||||
@ -0,0 +1,73 @@
|
||||
package chroot
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testImage() ec2.Image {
|
||||
return ec2.Image{
|
||||
Id: "ami-abcd1234",
|
||||
Name: "ami_test_name",
|
||||
Architecture: "x86_64",
|
||||
KernelId: "aki-abcd1234",
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepRegisterAmi_buildRegisterOpts_pv(t *testing.T) {
|
||||
config := Config{}
|
||||
config.AMIName = "test_ami_name"
|
||||
config.AMIDescription = "test_ami_description"
|
||||
config.AMIVirtType = "paravirtual"
|
||||
|
||||
image := testImage()
|
||||
|
||||
blockDevices := []ec2.BlockDeviceMapping{}
|
||||
|
||||
opts := buildRegisterOpts(&config, &image, blockDevices)
|
||||
|
||||
expected := config.AMIVirtType
|
||||
if opts.VirtType != expected {
|
||||
t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, opts.VirtType)
|
||||
}
|
||||
|
||||
expected = config.AMIName
|
||||
if opts.Name != expected {
|
||||
t.Fatalf("Unexpected Name value: expected %s got %s\n", expected, opts.Name)
|
||||
}
|
||||
|
||||
expected = image.KernelId
|
||||
if opts.KernelId != expected {
|
||||
t.Fatalf("Unexpected KernelId value: expected %s got %s\n", expected, opts.KernelId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) {
|
||||
config := Config{}
|
||||
config.AMIName = "test_ami_name"
|
||||
config.AMIDescription = "test_ami_description"
|
||||
config.AMIVirtType = "hvm"
|
||||
|
||||
image := testImage()
|
||||
|
||||
blockDevices := []ec2.BlockDeviceMapping{}
|
||||
|
||||
opts := buildRegisterOpts(&config, &image, blockDevices)
|
||||
|
||||
expected := config.AMIVirtType
|
||||
if opts.VirtType != expected {
|
||||
t.Fatalf("Unexpected VirtType value: expected %s got %s\n", expected, opts.VirtType)
|
||||
}
|
||||
|
||||
expected = config.AMIName
|
||||
if opts.Name != expected {
|
||||
t.Fatalf("Unexpected Name value: expected %s got %s\n", expected, opts.Name)
|
||||
}
|
||||
|
||||
expected = ""
|
||||
if opts.KernelId != expected {
|
||||
t.Fatalf("Unexpected KernelId value: expected %s got %s\n", expected, opts.KernelId)
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package ebs
|
||||
|
||||
// This hook is fired prior to launching the EC2 instance.
|
||||
const HookPreLaunch = "amazonebs_pre_launch"
|
||||
@ -0,0 +1,39 @@
|
||||
package ebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type stepModifyInstance struct{}
|
||||
|
||||
func (s *stepModifyInstance) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(config)
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
instance := state.Get("instance").(*ec2.Instance)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
|
||||
if config.AMIEnhancedNetworking {
|
||||
ui.Say("Enabling Enhanced Networking...")
|
||||
_, err := ec2conn.ModifyInstance(
|
||||
instance.InstanceId,
|
||||
&ec2.ModifyInstance{SriovNetSupport: true},
|
||||
)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error enabling Enhanced Networking on %s: %s", instance.InstanceId, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepModifyInstance) Cleanup(state multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
||||
@ -0,0 +1,382 @@
|
||||
// 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
|
||||
}
|
||||
@ -0,0 +1,448 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DigitalOceanClientV2 struct {
|
||||
// The http client for communicating
|
||||
client *http.Client
|
||||
|
||||
// Credentials
|
||||
APIToken string
|
||||
|
||||
// The base URL of the API
|
||||
APIURL string
|
||||
}
|
||||
|
||||
// Creates a new client for communicating with DO
|
||||
func DigitalOceanClientNewV2(token string, url string) *DigitalOceanClientV2 {
|
||||
c := &DigitalOceanClientV2{
|
||||
client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
},
|
||||
APIURL: url,
|
||||
APIToken: token,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Creates an SSH Key and returns it's id
|
||||
func (d DigitalOceanClientV2) CreateKey(name string, pub string) (uint, error) {
|
||||
type KeyReq struct {
|
||||
Name string `json:"name"`
|
||||
PublicKey string `json:"public_key"`
|
||||
}
|
||||
type KeyRes struct {
|
||||
SSHKey struct {
|
||||
Id uint
|
||||
Name string
|
||||
Fingerprint string
|
||||
PublicKey string `json:"public_key"`
|
||||
} `json:"ssh_key"`
|
||||
}
|
||||
req := &KeyReq{Name: name, PublicKey: pub}
|
||||
res := KeyRes{}
|
||||
err := NewRequestV2(d, "v2/account/keys", "POST", req, &res)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return res.SSHKey.Id, err
|
||||
}
|
||||
|
||||
// Destroys an SSH key
|
||||
func (d DigitalOceanClientV2) DestroyKey(id uint) error {
|
||||
path := fmt.Sprintf("v2/account/keys/%v", id)
|
||||
return NewRequestV2(d, path, "DELETE", nil, nil)
|
||||
}
|
||||
|
||||
// Creates a droplet and returns it's id
|
||||
func (d DigitalOceanClientV2) CreateDroplet(name string, size string, image string, region string, keyId uint, privateNetworking bool) (uint, error) {
|
||||
type DropletReq struct {
|
||||
Name string `json:"name"`
|
||||
Region string `json:"region"`
|
||||
Size string `json:"size"`
|
||||
Image string `json:"image"`
|
||||
SSHKeys []string `json:"ssh_keys,omitempty"`
|
||||
Backups bool `json:"backups,omitempty"`
|
||||
IPv6 bool `json:"ipv6,omitempty"`
|
||||
PrivateNetworking bool `json:"private_networking,omitempty"`
|
||||
}
|
||||
type DropletRes struct {
|
||||
Droplet struct {
|
||||
Id uint
|
||||
Name string
|
||||
Memory uint
|
||||
VCPUS uint `json:"vcpus"`
|
||||
Disk uint
|
||||
Region Region
|
||||
Image Image
|
||||
Size Size
|
||||
Locked bool
|
||||
CreateAt string `json:"created_at"`
|
||||
Status string
|
||||
Networks struct {
|
||||
V4 []struct {
|
||||
IPAddr string `json:"ip_address"`
|
||||
Netmask string
|
||||
Gateway string
|
||||
Type string
|
||||
} `json:"v4,omitempty"`
|
||||
V6 []struct {
|
||||
IPAddr string `json:"ip_address"`
|
||||
CIDR uint `json:"cidr"`
|
||||
Gateway string
|
||||
Type string
|
||||
} `json:"v6,omitempty"`
|
||||
}
|
||||
Kernel struct {
|
||||
Id uint
|
||||
Name string
|
||||
Version string
|
||||
}
|
||||
BackupIds []uint
|
||||
SnapshotIds []uint
|
||||
ActionIds []uint
|
||||
Features []string `json:"features,omitempty"`
|
||||
}
|
||||
}
|
||||
req := &DropletReq{Name: name}
|
||||
res := DropletRes{}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
req.Size = found_size.Slug
|
||||
req.Image = found_image.Slug
|
||||
req.Region = found_region.Slug
|
||||
req.SSHKeys = []string{fmt.Sprintf("%v", keyId)}
|
||||
req.PrivateNetworking = privateNetworking
|
||||
|
||||
err = NewRequestV2(d, "v2/droplets", "POST", req, &res)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return res.Droplet.Id, err
|
||||
}
|
||||
|
||||
// Destroys a droplet
|
||||
func (d DigitalOceanClientV2) DestroyDroplet(id uint) error {
|
||||
path := fmt.Sprintf("v2/droplets/%v", id)
|
||||
return NewRequestV2(d, path, "DELETE", nil, nil)
|
||||
}
|
||||
|
||||
// Powers off a droplet
|
||||
func (d DigitalOceanClientV2) PowerOffDroplet(id uint) error {
|
||||
type ActionReq struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
type ActionRes struct {
|
||||
}
|
||||
req := &ActionReq{Type: "power_off"}
|
||||
path := fmt.Sprintf("v2/droplets/%v/actions", id)
|
||||
return NewRequestV2(d, path, "POST", req, nil)
|
||||
}
|
||||
|
||||
// Shutsdown a droplet. This is a "soft" shutdown.
|
||||
func (d DigitalOceanClientV2) ShutdownDroplet(id uint) error {
|
||||
type ActionReq struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
type ActionRes struct {
|
||||
}
|
||||
req := &ActionReq{Type: "shutdown"}
|
||||
|
||||
path := fmt.Sprintf("v2/droplets/%v/actions", id)
|
||||
return NewRequestV2(d, path, "POST", req, nil)
|
||||
}
|
||||
|
||||
// Creates a snaphot of a droplet by it's ID
|
||||
func (d DigitalOceanClientV2) CreateSnapshot(id uint, name string) error {
|
||||
type ActionReq struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type ActionRes struct {
|
||||
}
|
||||
req := &ActionReq{Type: "snapshot", Name: name}
|
||||
path := fmt.Sprintf("v2/droplets/%v/actions", id)
|
||||
return NewRequestV2(d, path, "POST", req, nil)
|
||||
}
|
||||
|
||||
// Returns all available images.
|
||||
func (d DigitalOceanClientV2) Images() ([]Image, error) {
|
||||
res := ImagesResp{}
|
||||
|
||||
err := NewRequestV2(d, "v2/images?per_page=200", "GET", nil, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Images, nil
|
||||
}
|
||||
|
||||
// Destroys an image by its ID.
|
||||
func (d DigitalOceanClientV2) DestroyImage(id uint) error {
|
||||
path := fmt.Sprintf("v2/images/%d", id)
|
||||
return NewRequestV2(d, path, "DELETE", nil, nil)
|
||||
}
|
||||
|
||||
// Returns DO's string representation of status "off" "new" "active" etc.
|
||||
func (d DigitalOceanClientV2) DropletStatus(id uint) (string, string, error) {
|
||||
path := fmt.Sprintf("v2/droplets/%v", id)
|
||||
type DropletRes struct {
|
||||
Droplet struct {
|
||||
Id uint
|
||||
Name string
|
||||
Memory uint
|
||||
VCPUS uint `json:"vcpus"`
|
||||
Disk uint
|
||||
Region Region
|
||||
Image Image
|
||||
Size Size
|
||||
Locked bool
|
||||
CreateAt string `json:"created_at"`
|
||||
Status string
|
||||
Networks struct {
|
||||
V4 []struct {
|
||||
IPAddr string `json:"ip_address"`
|
||||
Netmask string
|
||||
Gateway string
|
||||
Type string
|
||||
} `json:"v4,omitempty"`
|
||||
V6 []struct {
|
||||
IPAddr string `json:"ip_address"`
|
||||
CIDR uint `json:"cidr"`
|
||||
Gateway string
|
||||
Type string
|
||||
} `json:"v6,omitempty"`
|
||||
}
|
||||
Kernel struct {
|
||||
Id uint
|
||||
Name string
|
||||
Version string
|
||||
}
|
||||
BackupIds []uint
|
||||
SnapshotIds []uint
|
||||
ActionIds []uint
|
||||
Features []string `json:"features,omitempty"`
|
||||
}
|
||||
}
|
||||
res := DropletRes{}
|
||||
err := NewRequestV2(d, path, "GET", nil, &res)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
var ip string
|
||||
|
||||
if len(res.Droplet.Networks.V4) > 0 {
|
||||
ip = res.Droplet.Networks.V4[0].IPAddr
|
||||
}
|
||||
|
||||
return ip, res.Droplet.Status, err
|
||||
}
|
||||
|
||||
// Sends an api request and returns a generic map[string]interface of
|
||||
// the response.
|
||||
func NewRequestV2(d DigitalOceanClientV2, path string, method string, req interface{}, res interface{}) error {
|
||||
var err error
|
||||
var request *http.Request
|
||||
|
||||
client := d.client
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
// Add the authentication parameters
|
||||
url := fmt.Sprintf("%s/%s", d.APIURL, path)
|
||||
if req != nil {
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.Encode(req)
|
||||
defer buf.Reset()
|
||||
request, err = http.NewRequest(method, url, buf)
|
||||
} else {
|
||||
request, err = http.NewRequest(method, url, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Add the authentication parameters
|
||||
request.Header.Add("Authorization", "Bearer "+d.APIToken)
|
||||
|
||||
log.Printf("sending new request to digitalocean: %s", url)
|
||||
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if method == "DELETE" && resp.StatusCode == 204 {
|
||||
if resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if resp.Body == nil {
|
||||
return errors.New("Request returned empty body")
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("response from digitalocean: %s", body)
|
||||
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Failed to decode JSON response %s (HTTP %v) from DigitalOcean: %s", err.Error(),
|
||||
resp.StatusCode, body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d DigitalOceanClientV2) 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 DigitalOceanClientV2) Regions() ([]Region, error) {
|
||||
res := RegionsResp{}
|
||||
err := NewRequestV2(d, "v2/regions?per_page=200", "GET", nil, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Regions, nil
|
||||
}
|
||||
|
||||
func (d DigitalOceanClientV2) 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 DigitalOceanClientV2) Sizes() ([]Size, error) {
|
||||
res := SizesResp{}
|
||||
err := NewRequestV2(d, "v2/sizes?per_page=200", "GET", nil, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Sizes, nil
|
||||
}
|
||||
|
||||
func (d DigitalOceanClientV2) 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
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepCommit commits the container to a image.
|
||||
type StepCommit struct {
|
||||
imageId string
|
||||
}
|
||||
|
||||
func (s *StepCommit) Run(state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(Driver)
|
||||
containerId := state.Get("container_id").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Committing the container")
|
||||
imageId, err := driver.Commit(containerId)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Save the container ID
|
||||
s.imageId = imageId
|
||||
state.Put("image_id", s.imageId)
|
||||
ui.Message(fmt.Sprintf("Image ID: %s", s.imageId))
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCommit) Cleanup(state multistep.StateBag) {}
|
||||
@ -0,0 +1,66 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mitchellh/multistep"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testStepCommitState(t *testing.T) multistep.StateBag {
|
||||
state := testState(t)
|
||||
state.Put("container_id", "foo")
|
||||
return state
|
||||
}
|
||||
|
||||
func TestStepCommit_impl(t *testing.T) {
|
||||
var _ multistep.Step = new(StepCommit)
|
||||
}
|
||||
|
||||
func TestStepCommit(t *testing.T) {
|
||||
state := testStepCommitState(t)
|
||||
step := new(StepCommit)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.CommitImageId = "bar"
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify we did the right thing
|
||||
if !driver.CommitCalled {
|
||||
t.Fatal("should've called")
|
||||
}
|
||||
|
||||
// verify the ID is saved
|
||||
idRaw, ok := state.GetOk("image_id")
|
||||
if !ok {
|
||||
t.Fatal("should've saved ID")
|
||||
}
|
||||
|
||||
id := idRaw.(string)
|
||||
if id != driver.CommitImageId {
|
||||
t.Fatalf("bad: %#v", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCommit_error(t *testing.T) {
|
||||
state := testStepCommitState(t)
|
||||
step := new(StepCommit)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*MockDriver)
|
||||
driver.CommitErr = errors.New("foo")
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// verify the ID is not saved
|
||||
if _, ok := state.GetOk("image_id"); ok {
|
||||
t.Fatal("shouldn't save image ID")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
)
|
||||
|
||||
// accountFile represents the structure of the account file JSON file.
|
||||
type accountFile struct {
|
||||
PrivateKeyId string `json:"private_key_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
ClientEmail string `json:"client_email"`
|
||||
ClientId string `json:"client_id"`
|
||||
}
|
||||
|
||||
func loadJSON(result interface{}, path string) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dec := json.NewDecoder(f)
|
||||
return dec.Decode(result)
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// clientSecrets represents the client secrets of a GCE service account.
|
||||
type clientSecrets struct {
|
||||
Web struct {
|
||||
AuthURI string `json:"auth_uri"`
|
||||
ClientEmail string `json:"client_email"`
|
||||
ClientId string `json:"client_id"`
|
||||
TokenURI string `json:"token_uri"`
|
||||
}
|
||||
}
|
||||
|
||||
// loadClientSecrets loads the GCE client secrets file identified by path.
|
||||
func loadClientSecrets(path string) (*clientSecrets, error) {
|
||||
var cs *clientSecrets
|
||||
secretBytes, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(secretBytes, &cs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cs, nil
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
package googlecompute
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testClientSecretsFile(t *testing.T) string {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer tf.Close()
|
||||
|
||||
if _, err := tf.Write([]byte(testClientSecretsContent)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return tf.Name()
|
||||
}
|
||||
|
||||
func TestLoadClientSecrets(t *testing.T) {
|
||||
_, err := loadClientSecrets(testClientSecretsFile(t))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// This is just some dummy data that doesn't actually work (it was revoked
|
||||
// a long time ago).
|
||||
const testClientSecretsContent = `{"web":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873@developer.gserviceaccount.com","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873@developer.gserviceaccount.com","client_id":"774313886706-eorlsj0r4eqkh5e7nvea5fuf59ifr873.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}`
|
||||
@ -0,0 +1,67 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
|
||||
"github.com/mitchellh/gophercloud-fork-40444fb"
|
||||
)
|
||||
|
||||
type StepAllocateIp struct {
|
||||
FloatingIpPool string
|
||||
FloatingIp string
|
||||
}
|
||||
|
||||
func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
csp := state.Get("csp").(gophercloud.CloudServersProvider)
|
||||
server := state.Get("server").(*gophercloud.Server)
|
||||
|
||||
var instanceIp gophercloud.FloatingIp
|
||||
// This is here in case we error out before putting instanceIp into the
|
||||
// statebag below, because it is requested by Cleanup()
|
||||
state.Put("access_ip", instanceIp)
|
||||
|
||||
if s.FloatingIp != "" {
|
||||
instanceIp.Ip = s.FloatingIp
|
||||
} else if s.FloatingIpPool != "" {
|
||||
newIp, err := csp.CreateFloatingIp(s.FloatingIpPool)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating floating ip from pool '%s'", s.FloatingIpPool)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
instanceIp = newIp
|
||||
ui.Say(fmt.Sprintf("Created temporary floating IP %s...", instanceIp.Ip))
|
||||
}
|
||||
|
||||
if instanceIp.Ip != "" {
|
||||
if err := csp.AssociateFloatingIp(server.Id, instanceIp); err != nil {
|
||||
err := fmt.Errorf("Error associating floating IP %s with instance.", instanceIp.Ip)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
} else {
|
||||
ui.Say(fmt.Sprintf("Added floating IP %s to instance...", instanceIp.Ip))
|
||||
}
|
||||
}
|
||||
|
||||
state.Put("access_ip", instanceIp)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepAllocateIp) Cleanup(state multistep.StateBag) {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
csp := state.Get("csp").(gophercloud.CloudServersProvider)
|
||||
instanceIp := state.Get("access_ip").(gophercloud.FloatingIp)
|
||||
if s.FloatingIpPool != "" && instanceIp.Id != 0 {
|
||||
if err := csp.DeleteFloatingIp(instanceIp); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error deleting temporary floating IP %s", instanceIp.Ip))
|
||||
return
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.Ip))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// This is the common builder ID to all of these artifacts.
|
||||
const BuilderId = "packer.parallels"
|
||||
|
||||
// These are the extensions of files and directories that are unnecessary for the function
|
||||
// of a Parallels virtual machine.
|
||||
var unnecessaryFiles = []string{"\\.log$", "\\.backup$", "\\.Backup$", "\\.app"}
|
||||
|
||||
// Artifact is the result of running the parallels builder, namely a set
|
||||
// of files associated with the resulting machine.
|
||||
type artifact struct {
|
||||
dir string
|
||||
f []string
|
||||
}
|
||||
|
||||
// NewArtifact returns a Parallels artifact containing the files
|
||||
// in the given directory.
|
||||
func NewArtifact(dir string) (packer.Artifact, error) {
|
||||
files := make([]string, 0, 5)
|
||||
visit := func(path string, info os.FileInfo, err error) error {
|
||||
for _, unnecessaryFile := range unnecessaryFiles {
|
||||
if unnecessary, _ := regexp.MatchString(unnecessaryFile, path); unnecessary {
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
files = append(files, path)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := filepath.Walk(dir, visit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &artifact{
|
||||
dir: dir,
|
||||
f: files,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*artifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
func (a *artifact) Files() []string {
|
||||
return a.f
|
||||
}
|
||||
|
||||
func (*artifact) Id() string {
|
||||
return "VM"
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_impl(t *testing.T) {
|
||||
var _ packer.Artifact = new(artifact)
|
||||
}
|
||||
|
||||
func TestNewArtifact(t *testing.T) {
|
||||
td, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(td, "a"), []byte("foo"), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := os.Mkdir(filepath.Join(td, "b"), 0755); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
a, err := NewArtifact(td)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if a.BuilderId() != BuilderId {
|
||||
t.Fatalf("bad: %#v", a.BuilderId())
|
||||
}
|
||||
if len(a.Files()) != 1 {
|
||||
t.Fatalf("should length 1: %d", len(a.Files()))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfigTemplate(t *testing.T) *packer.ConfigTemplate {
|
||||
result, err := packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A driver is able to talk to Parallels and perform certain
|
||||
// operations with it. Some of the operations on here may seem overly
|
||||
// specific, but they were built specifically in mind to handle features
|
||||
// of the Parallels builder for Packer, and to abstract differences in
|
||||
// versions out of the builder steps, so sometimes the methods are
|
||||
// extremely specific.
|
||||
type Driver interface {
|
||||
// Adds new CD/DVD drive to the VM and returns name of this device
|
||||
DeviceAddCdRom(string, string) (string, error)
|
||||
|
||||
// Import a VM
|
||||
Import(string, string, string, bool) error
|
||||
|
||||
// Checks if the VM with the given name is running.
|
||||
IsRunning(string) (bool, error)
|
||||
|
||||
// Stop stops a running machine, forcefully.
|
||||
Stop(string) error
|
||||
|
||||
// Prlctl executes the given Prlctl command
|
||||
Prlctl(...string) error
|
||||
|
||||
// Get the path to the Parallels Tools ISO for the given flavor.
|
||||
ToolsIsoPath(string) (string, error)
|
||||
|
||||
// Verify checks to make sure that this driver should function
|
||||
// properly. If there is any indication the driver can't function,
|
||||
// this will return an error.
|
||||
Verify() error
|
||||
|
||||
// Version reads the version of Parallels that is installed.
|
||||
Version() (string, error)
|
||||
|
||||
// Send scancodes to the vm using the prltype python script.
|
||||
SendKeyScanCodes(string, ...string) error
|
||||
|
||||
// Finds the MAC address of the NIC nic0
|
||||
Mac(string) (string, error)
|
||||
|
||||
// Finds the IP address of a VM connected that uses DHCP by its MAC address
|
||||
IpAddress(string) (string, error)
|
||||
}
|
||||
|
||||
func NewDriver() (Driver, error) {
|
||||
var drivers map[string]Driver
|
||||
var prlctlPath string
|
||||
var supportedVersions []string
|
||||
|
||||
if runtime.GOOS != "darwin" {
|
||||
return nil, fmt.Errorf(
|
||||
"Parallels builder works only on \"darwin\" platform!")
|
||||
}
|
||||
|
||||
if prlctlPath == "" {
|
||||
var err error
|
||||
prlctlPath, err = exec.LookPath("prlctl")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("prlctl path: %s", prlctlPath)
|
||||
|
||||
drivers = map[string]Driver{
|
||||
"10": &Parallels10Driver{
|
||||
Parallels9Driver: Parallels9Driver{
|
||||
PrlctlPath: prlctlPath,
|
||||
},
|
||||
},
|
||||
"9": &Parallels9Driver{
|
||||
PrlctlPath: prlctlPath,
|
||||
},
|
||||
}
|
||||
|
||||
for v, d := range drivers {
|
||||
version, _ := d.Version()
|
||||
if strings.HasPrefix(version, v) {
|
||||
return d, nil
|
||||
}
|
||||
supportedVersions = append(supportedVersions, v)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(
|
||||
"Unable to initialize any driver. Supported Parallels Desktop versions: "+
|
||||
"%s\n", strings.Join(supportedVersions, ", "))
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package common
|
||||
|
||||
// Parallels10Driver are inherited from Parallels9Driver.
|
||||
type Parallels10Driver struct {
|
||||
Parallels9Driver
|
||||
}
|
||||
@ -0,0 +1,315 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/going/toolkit/xmlpath"
|
||||
)
|
||||
|
||||
type Parallels9Driver struct {
|
||||
// This is the path to the "prlctl" application.
|
||||
PrlctlPath string
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) Import(name, srcPath, dstDir string, reassignMac bool) error {
|
||||
|
||||
err := d.Prlctl("register", srcPath, "--preserve-uuid")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srcId, err := getVmId(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srcMac := "auto"
|
||||
if !reassignMac {
|
||||
srcMac, err = getFirtsMacAddress(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = d.Prlctl("clone", srcId, "--name", name, "--dst", dstDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.Prlctl("unregister", srcId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.Prlctl("set", name, "--device-set", "net0", "--mac", srcMac)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVmId(path string) (string, error) {
|
||||
return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Identification/VmUuid")
|
||||
}
|
||||
|
||||
func getFirtsMacAddress(path string) (string, error) {
|
||||
return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Hardware/NetworkAdapter[@id='0']/MAC")
|
||||
}
|
||||
|
||||
func getConfigValueFromXpath(path, xpath string) (string, error) {
|
||||
file, err := os.Open(path + "/config.pvs")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
xpathComp := xmlpath.MustCompile(xpath)
|
||||
root, err := xmlpath.Parse(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
value, _ := xpathComp.String(root)
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Finds an application bundle by identifier (for "darwin" platform only)
|
||||
func getAppPath(bundleId string) (string, error) {
|
||||
var stdout bytes.Buffer
|
||||
|
||||
cmd := exec.Command("mdfind", "kMDItemCFBundleIdentifier ==", bundleId)
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pathOutput := strings.TrimSpace(stdout.String())
|
||||
if pathOutput == "" {
|
||||
return "", fmt.Errorf(
|
||||
"Could not detect Parallels Desktop! Make sure it is properly installed.")
|
||||
}
|
||||
|
||||
return pathOutput, nil
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) DeviceAddCdRom(name string, image string) (string, error) {
|
||||
command := []string{
|
||||
"set", name,
|
||||
"--device-add", "cdrom",
|
||||
"--image", image,
|
||||
}
|
||||
|
||||
out, err := exec.Command(d.PrlctlPath, command...).Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
deviceRe := regexp.MustCompile(`\s+(cdrom\d+)\s+`)
|
||||
matches := deviceRe.FindStringSubmatch(string(out))
|
||||
if matches == nil {
|
||||
return "", fmt.Errorf(
|
||||
"Could not determine cdrom device name in the output:\n%s", string(out))
|
||||
}
|
||||
|
||||
device_name := matches[1]
|
||||
return device_name, nil
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) IsRunning(name string) (bool, error) {
|
||||
var stdout bytes.Buffer
|
||||
|
||||
cmd := exec.Command(d.PrlctlPath, "list", name, "--no-header", "--output", "status")
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
log.Printf("Checking VM state: %s\n", strings.TrimSpace(stdout.String()))
|
||||
|
||||
for _, line := range strings.Split(stdout.String(), "\n") {
|
||||
if line == "running" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if line == "suspended" {
|
||||
return true, nil
|
||||
}
|
||||
if line == "paused" {
|
||||
return true, nil
|
||||
}
|
||||
if line == "stopping" {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) Stop(name string) error {
|
||||
if err := d.Prlctl("stop", name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We sleep here for a little bit to let the session "unlock"
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) Prlctl(args ...string) error {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
log.Printf("Executing prlctl: %#v", args)
|
||||
cmd := exec.Command(d.PrlctlPath, args...)
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
|
||||
stdoutString := strings.TrimSpace(stdout.String())
|
||||
stderrString := strings.TrimSpace(stderr.String())
|
||||
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
err = fmt.Errorf("prlctl error: %s", stderrString)
|
||||
}
|
||||
|
||||
log.Printf("stdout: %s", stdoutString)
|
||||
log.Printf("stderr: %s", stderrString)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) Verify() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) Version() (string, error) {
|
||||
out, err := exec.Command(d.PrlctlPath, "--version").Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
versionRe := regexp.MustCompile(`prlctl version (\d+\.\d+.\d+)`)
|
||||
matches := versionRe.FindStringSubmatch(string(out))
|
||||
if matches == nil {
|
||||
return "", fmt.Errorf(
|
||||
"Could not find Parallels Desktop version in output:\n%s", string(out))
|
||||
}
|
||||
|
||||
version := matches[1]
|
||||
log.Printf("Parallels Desktop version: %s", version)
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) SendKeyScanCodes(vmName string, codes ...string) error {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
if codes == nil || len(codes) == 0 {
|
||||
log.Printf("No scan codes to send")
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := ioutil.TempFile("", "prltype")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
script := []byte(Prltype)
|
||||
_, err = f.Write(script)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := prepend(vmName, codes)
|
||||
args = prepend(f.Name(), args)
|
||||
cmd := exec.Command("/usr/bin/python", args...)
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err = cmd.Run()
|
||||
|
||||
stdoutString := strings.TrimSpace(stdout.String())
|
||||
stderrString := strings.TrimSpace(stderr.String())
|
||||
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
err = fmt.Errorf("prltype error: %s", stderrString)
|
||||
}
|
||||
|
||||
log.Printf("stdout: %s", stdoutString)
|
||||
log.Printf("stderr: %s", stderrString)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func prepend(head string, tail []string) []string {
|
||||
tmp := make([]string, len(tail)+1)
|
||||
for i := 0; i < len(tail); i++ {
|
||||
tmp[i+1] = tail[i]
|
||||
}
|
||||
tmp[0] = head
|
||||
return tmp
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) Mac(vmName string) (string, error) {
|
||||
var stdout bytes.Buffer
|
||||
|
||||
cmd := exec.Command(d.PrlctlPath, "list", "-i", vmName)
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Printf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
|
||||
return "", err
|
||||
}
|
||||
|
||||
stdoutString := strings.TrimSpace(stdout.String())
|
||||
re := regexp.MustCompile("net0.* mac=([0-9A-F]{12}) card=.*")
|
||||
macMatch := re.FindAllStringSubmatch(stdoutString, 1)
|
||||
|
||||
if len(macMatch) != 1 {
|
||||
return "", fmt.Errorf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
|
||||
}
|
||||
|
||||
mac := macMatch[0][1]
|
||||
log.Printf("Found MAC address for NIC: net0 - %s\n", mac)
|
||||
return mac, nil
|
||||
}
|
||||
|
||||
// Finds the IP address of a VM connected that uses DHCP by its MAC address
|
||||
func (d *Parallels9Driver) IpAddress(mac string) (string, error) {
|
||||
var stdout bytes.Buffer
|
||||
dhcp_lease_file := "/Library/Preferences/Parallels/parallels_dhcp_leases"
|
||||
|
||||
if len(mac) != 12 {
|
||||
return "", fmt.Errorf("Not a valid MAC address: %s. It should be exactly 12 digits.", mac)
|
||||
}
|
||||
|
||||
cmd := exec.Command("grep", "-i", mac, dhcp_lease_file)
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
stdoutString := strings.TrimSpace(stdout.String())
|
||||
re := regexp.MustCompile("(.*)=.*")
|
||||
ipMatch := re.FindAllStringSubmatch(stdoutString, 1)
|
||||
|
||||
if len(ipMatch) != 1 {
|
||||
return "", fmt.Errorf("IP lease not found for MAC address %s in: %s\n", mac, dhcp_lease_file)
|
||||
}
|
||||
|
||||
ip := ipMatch[0][1]
|
||||
log.Printf("Found IP lease: %s for MAC address %s\n", ip, mac)
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) ToolsIsoPath(k string) (string, error) {
|
||||
appPath, err := getAppPath("com.parallels.desktop.console")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
toolsPath := filepath.Join(appPath, "Contents", "Resources", "Tools", "prl-tools-"+k+".iso")
|
||||
log.Printf("Parallels Tools path: '%s'", toolsPath)
|
||||
return toolsPath, nil
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParallels9Driver_impl(t *testing.T) {
|
||||
var _ Driver = new(Parallels9Driver)
|
||||
}
|
||||
@ -0,0 +1,124 @@
|
||||
package common
|
||||
|
||||
import "sync"
|
||||
|
||||
type DriverMock struct {
|
||||
sync.Mutex
|
||||
|
||||
DeviceAddCdRomCalled bool
|
||||
DeviceAddCdRomName string
|
||||
DeviceAddCdRomImage string
|
||||
DeviceAddCdRomResult string
|
||||
DeviceAddCdRomErr error
|
||||
|
||||
ImportCalled bool
|
||||
ImportName string
|
||||
ImportSrcPath string
|
||||
ImportDstPath string
|
||||
ImportErr error
|
||||
|
||||
IsRunningName string
|
||||
IsRunningReturn bool
|
||||
IsRunningErr error
|
||||
|
||||
StopName string
|
||||
StopErr error
|
||||
|
||||
PrlctlCalls [][]string
|
||||
PrlctlErrs []error
|
||||
|
||||
VerifyCalled bool
|
||||
VerifyErr error
|
||||
|
||||
VersionCalled bool
|
||||
VersionResult string
|
||||
VersionErr error
|
||||
|
||||
SendKeyScanCodesCalls [][]string
|
||||
SendKeyScanCodesErrs []error
|
||||
|
||||
ToolsIsoPathCalled bool
|
||||
ToolsIsoPathFlavor string
|
||||
ToolsIsoPathResult string
|
||||
ToolsIsoPathErr error
|
||||
|
||||
MacName string
|
||||
MacReturn string
|
||||
MacError error
|
||||
|
||||
IpAddressMac string
|
||||
IpAddressReturn string
|
||||
IpAddressError error
|
||||
}
|
||||
|
||||
func (d *DriverMock) DeviceAddCdRom(name string, image string) (string, error) {
|
||||
d.DeviceAddCdRomCalled = true
|
||||
d.DeviceAddCdRomName = name
|
||||
d.DeviceAddCdRomImage = image
|
||||
return d.DeviceAddCdRomResult, d.DeviceAddCdRomErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) Import(name, srcPath, dstPath string, reassignMac bool) error {
|
||||
d.ImportCalled = true
|
||||
d.ImportName = name
|
||||
d.ImportSrcPath = srcPath
|
||||
d.ImportDstPath = dstPath
|
||||
return d.ImportErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) IsRunning(name string) (bool, error) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.IsRunningName = name
|
||||
return d.IsRunningReturn, d.IsRunningErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) Stop(name string) error {
|
||||
d.StopName = name
|
||||
return d.StopErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) Prlctl(args ...string) error {
|
||||
d.PrlctlCalls = append(d.PrlctlCalls, args)
|
||||
|
||||
if len(d.PrlctlErrs) >= len(d.PrlctlCalls) {
|
||||
return d.PrlctlErrs[len(d.PrlctlCalls)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DriverMock) Verify() error {
|
||||
d.VerifyCalled = true
|
||||
return d.VerifyErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) Version() (string, error) {
|
||||
d.VersionCalled = true
|
||||
return d.VersionResult, d.VersionErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) SendKeyScanCodes(name string, scancodes ...string) error {
|
||||
d.SendKeyScanCodesCalls = append(d.SendKeyScanCodesCalls, scancodes)
|
||||
|
||||
if len(d.SendKeyScanCodesErrs) >= len(d.SendKeyScanCodesCalls) {
|
||||
return d.SendKeyScanCodesErrs[len(d.SendKeyScanCodesCalls)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DriverMock) Mac(name string) (string, error) {
|
||||
d.MacName = name
|
||||
return d.MacReturn, d.MacError
|
||||
}
|
||||
|
||||
func (d *DriverMock) IpAddress(mac string) (string, error) {
|
||||
d.IpAddressMac = mac
|
||||
return d.IpAddressReturn, d.IpAddressError
|
||||
}
|
||||
|
||||
func (d *DriverMock) ToolsIsoPath(flavor string) (string, error) {
|
||||
d.ToolsIsoPathCalled = true
|
||||
d.ToolsIsoPathFlavor = flavor
|
||||
return d.ToolsIsoPathResult, d.ToolsIsoPathErr
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// FloppyConfig is configuration related to created floppy disks and attaching
|
||||
// them to a Parallels virtual machine.
|
||||
type FloppyConfig struct {
|
||||
FloppyFiles []string `mapstructure:"floppy_files"`
|
||||
}
|
||||
|
||||
func (c *FloppyConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||
if c.FloppyFiles == nil {
|
||||
c.FloppyFiles = make([]string, 0)
|
||||
}
|
||||
|
||||
errs := make([]error, 0)
|
||||
for i, file := range c.FloppyFiles {
|
||||
var err error
|
||||
c.FloppyFiles[i], err = t.Process(file, nil)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Error processing floppy_files[%d]: %s", i, err))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue