Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9907933bf8 |
+1
-3
@@ -5,9 +5,7 @@ go:
|
||||
- tip
|
||||
|
||||
install: make deps
|
||||
script:
|
||||
- go test ./...
|
||||
- go test -race ./...
|
||||
script: make test
|
||||
|
||||
notifications:
|
||||
flowdock:
|
||||
|
||||
@@ -1,85 +1,3 @@
|
||||
## 0.3.5 (August 28, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* **NEW BUILDER:** `openstack`. You can now build on OpenStack. [GH-155]
|
||||
* **NEW PROVISIONER:** `chef-solo`. You can now provision with Chef
|
||||
using `chef-solo` from local cookbooks.
|
||||
* builder/amazon: Copy AMI to multiple regions with `ami_regions`. [GH-322]
|
||||
* builder/virtualbox,vmware: Can now use SSH keys as an auth mechanism for
|
||||
SSH using `ssh_key_path`. [GH-70]
|
||||
* builder/virtualbox,vmware: Support SHA512 as a checksum type. [GH-356]
|
||||
* builder/vmware: The root hard drive type can now be specified with
|
||||
"disk_type_id" for advanced users. [GH-328]
|
||||
* provisioner/salt-masterless: Ability to specfy a minion config. [GH-264]
|
||||
* provisioner/salt-masterless: Ability to upload pillars. [GH-353]
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* core: Output message when Ctrl-C received that we're cleaning up. [GH-338]
|
||||
* builder/amazon: Tagging now works with all amazon builder types.
|
||||
* builder/vmware: Option `ssh_skip_request_pty` for not requesting a PTY
|
||||
for the SSH connection. [GH-270]
|
||||
* builder/vmware: Specify a `vmx_template_path` in order to customize
|
||||
the generated VMX. [GH-270]
|
||||
* command/build: Machine-readable output now contains build errors, if any.
|
||||
* command/build: An "end" sentinel is outputted in machine-readable output
|
||||
for artifact listing so it is easier to know when it is over.
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: Fixed a couple cases where a double ctrl-C could panic.
|
||||
* core: Template validation fails if an override is specified for a
|
||||
non-existent builder. [GH-336]
|
||||
* core: The SSH connection is heartbeated so that drops can be
|
||||
detected. [GH-200]
|
||||
* builder/amazon/instance: Remove check for ec2-ami-tools because it
|
||||
didn't allow absolute paths to work properly. [GH-330]
|
||||
* builder/digitalocean: Send a soft shutdown request so that files
|
||||
are properly synced before shutdown. [GH-332]
|
||||
* command/build,command/validate: If a non-existent build is specified to
|
||||
'-only' or '-except', it is now an error. [GH-326]
|
||||
* post-processor/vagrant: Setting OutputPath with a timestamp now
|
||||
always works properly. [GH-324]
|
||||
* post-processor/vagrant: VirtualBox OVA formats now turn into
|
||||
Vagrant boxes properly. [GH-331]
|
||||
* provisioner/shell: Retry upload if start command fails, making reboot
|
||||
handling much more robust.
|
||||
|
||||
## 0.3.4 (August 21, 2013)
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* post-processor/vagrant: the file being compressed will be shown
|
||||
in the UI [GH-314]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: Avoid panics when double-interrupting Packer.
|
||||
* provisioner/shell: Retry shell script uploads, making reboots more
|
||||
robust if they happen to fail in this stage. [GH-282]
|
||||
|
||||
## 0.3.3 (August 19, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* builder/virtualbox: support exporting in OVA format. [GH-309]
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* core: All HTTP downloads across Packer now support the standard
|
||||
proxy environmental variables (`HTTP_PROXY`, `NO_PROXY`, etc.) [GH-252]
|
||||
* builder/amazon: API requests will use HTTP proxy if specified by
|
||||
enviromental variables.
|
||||
* builder/digitalocean: API requests will use HTTP proxy if specified
|
||||
by environmental variables.
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: TCP connection between plugin processes will keep-alive. [GH-312]
|
||||
* core: No more "unused key keep_input_artifact" for post processors [GH-310]
|
||||
* post-processor/vagrant: `output_path` templates now work again.
|
||||
|
||||
## 0.3.2 (August 18, 2013)
|
||||
|
||||
FEATURES:
|
||||
|
||||
@@ -194,13 +194,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
Users: b.config.AMIUsers,
|
||||
Groups: b.config.AMIGroups,
|
||||
},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
Regions: b.config.AMIRegions,
|
||||
Tags: b.config.AMITags,
|
||||
},
|
||||
&awscommon.StepCreateTags{
|
||||
Tags: b.config.AMITags,
|
||||
},
|
||||
}
|
||||
|
||||
// Run!
|
||||
|
||||
@@ -70,38 +70,6 @@ func (c *Communicator) Upload(dst string, r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
|
||||
walkFn := func(fullPath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path, err := filepath.Rel(src, fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, e := range exclude {
|
||||
if e == path {
|
||||
log.Printf("Skipping excluded file: %s", path)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
dstPath := filepath.Join(dst, path)
|
||||
f, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return c.Upload(dstPath, f)
|
||||
}
|
||||
|
||||
log.Printf("Uploading directory '%s' to '%s'", src, dst)
|
||||
return filepath.Walk(src, walkFn)
|
||||
}
|
||||
|
||||
func (c *Communicator) Download(src string, w io.Writer) error {
|
||||
src = filepath.Join(c.Chroot, src)
|
||||
log.Printf("Downloading from chroot dir: %s", src)
|
||||
|
||||
@@ -2,19 +2,16 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// AMIConfig is for common configuration related to creating AMIs.
|
||||
type AMIConfig struct {
|
||||
AMIName string `mapstructure:"ami_name"`
|
||||
AMIDescription string `mapstructure:"ami_description"`
|
||||
AMIUsers []string `mapstructure:"ami_users"`
|
||||
AMIGroups []string `mapstructure:"ami_groups"`
|
||||
AMIProductCodes []string `mapstructure:"ami_product_codes"`
|
||||
AMIRegions []string `mapstructure:"ami_regions"`
|
||||
AMITags map[string]string `mapstructure:"tags"`
|
||||
AMIName string `mapstructure:"ami_name"`
|
||||
AMIDescription string `mapstructure:"ami_description"`
|
||||
AMIUsers []string `mapstructure:"ami_users"`
|
||||
AMIGroups []string `mapstructure:"ami_groups"`
|
||||
AMIProductCodes []string `mapstructure:"ami_product_codes"`
|
||||
}
|
||||
|
||||
func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||
@@ -45,7 +42,6 @@ func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||
"ami_users": c.AMIUsers,
|
||||
"ami_groups": c.AMIGroups,
|
||||
"ami_product_codes": c.AMIProductCodes,
|
||||
"ami_regions": c.AMIRegions,
|
||||
}
|
||||
|
||||
for n, slice := range sliceTemplates {
|
||||
@@ -63,52 +59,6 @@ func (c *AMIConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||
errs = append(errs, fmt.Errorf("ami_name must be specified"))
|
||||
}
|
||||
|
||||
if len(c.AMIRegions) > 0 {
|
||||
regionSet := make(map[string]struct{})
|
||||
regions := make([]string, 0, len(c.AMIRegions))
|
||||
|
||||
for _, region := range c.AMIRegions {
|
||||
// If we already saw the region, then don't look again
|
||||
if _, ok := regionSet[region]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Mark that we saw the region
|
||||
regionSet[region] = struct{}{}
|
||||
|
||||
// Verify the region is real
|
||||
if _, ok := aws.Regions[region]; !ok {
|
||||
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
|
||||
continue
|
||||
}
|
||||
|
||||
regions = append(regions, region)
|
||||
}
|
||||
|
||||
c.AMIRegions = regions
|
||||
}
|
||||
|
||||
newTags := make(map[string]string)
|
||||
for k, v := range c.AMITags {
|
||||
k, err := t.Process(k, nil)
|
||||
if err != nil {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("Error processing tag key %s: %s", k, err))
|
||||
continue
|
||||
}
|
||||
|
||||
v, err := t.Process(v, nil)
|
||||
if err != nil {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("Error processing tag value '%s': %s", v, err))
|
||||
continue
|
||||
}
|
||||
|
||||
newTags[k] = v
|
||||
}
|
||||
|
||||
c.AMITags = newTags
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -11,7 +10,7 @@ func testAMIConfig() *AMIConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMIConfigPrepare_name(t *testing.T) {
|
||||
func TestAMIConfigPrepare_Region(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
@@ -22,26 +21,3 @@ func TestAMIConfigPrepare_name(t *testing.T) {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMIConfigPrepare_regions(t *testing.T) {
|
||||
c := testAMIConfig()
|
||||
c.AMIRegions = nil
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.AMIRegions = []string{"foo"}
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
|
||||
expected := []string{"us-east-1", "us-west-1"}
|
||||
if !reflect.DeepEqual(c.AMIRegions, expected) {
|
||||
t.Fatalf("bad: %#v", c.AMIRegions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type StepAMIRegionCopy struct {
|
||||
Regions []string
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
func (s *StepAMIRegionCopy) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ec2conn := state["ec2"].(*ec2.EC2)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
amis := state["amis"].(map[string]string)
|
||||
ami := amis[ec2conn.Region.Name]
|
||||
|
||||
if len(s.Regions) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Copying AMI (%s) to other regions...", ami))
|
||||
for _, region := range s.Regions {
|
||||
ui.Message(fmt.Sprintf("Copying to: %s", region))
|
||||
|
||||
// Connect to the region where the AMI will be copied to
|
||||
regionconn := ec2.New(ec2conn.Auth, aws.Regions[region])
|
||||
resp, err := regionconn.CopyImage(&ec2.CopyImage{
|
||||
SourceRegion: ec2conn.Region.Name,
|
||||
SourceImageId: ami,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error Copying AMI (%s) to region (%s): %s", ami, region, err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Waiting for AMI (%s) in region (%s) to become ready...", resp.ImageId, region))
|
||||
if err := WaitForAMI(regionconn, resp.ImageId); err != nil {
|
||||
err := fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s", resp.ImageId, region, err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Need to re-apply Tags since they are not copied with the AMI
|
||||
if len(s.Tags) > 0 {
|
||||
ui.Say(fmt.Sprintf("Adding tags to AMI (%s)...", resp.ImageId))
|
||||
|
||||
var ec2Tags []ec2.Tag
|
||||
for key, value := range s.Tags {
|
||||
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"", key, value))
|
||||
ec2Tags = append(ec2Tags, ec2.Tag{key, value})
|
||||
}
|
||||
|
||||
_, err := regionconn.CreateTags([]string{resp.ImageId}, ec2Tags)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error adding tags to AMI (%s): %s", resp.ImageId, err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
amis[region] = resp.ImageId
|
||||
}
|
||||
|
||||
state["amis"] = amis
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepAMIRegionCopy) Cleanup(state map[string]interface{}) {
|
||||
// No cleanup...
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
package ebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
@@ -24,6 +25,9 @@ type config struct {
|
||||
awscommon.BlockDevices `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
|
||||
// Tags for the AMI
|
||||
Tags map[string]string
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
@@ -50,6 +54,28 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(b.config.tpl)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
|
||||
|
||||
// Accumulate any errors
|
||||
newTags := make(map[string]string)
|
||||
for k, v := range b.config.Tags {
|
||||
k, err = b.config.tpl.Process(k, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Error processing tag key %s: %s", k, err))
|
||||
continue
|
||||
}
|
||||
|
||||
v, err = b.config.tpl.Process(v, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Error processing tag value '%s': %s", v, err))
|
||||
continue
|
||||
}
|
||||
|
||||
newTags[k] = v
|
||||
}
|
||||
|
||||
b.config.Tags = newTags
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
@@ -104,18 +130,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&common.StepProvision{},
|
||||
&stepStopInstance{},
|
||||
&stepCreateAMI{},
|
||||
&awscommon.StepCreateTags{Tags: b.config.Tags},
|
||||
&awscommon.StepModifyAMIAttributes{
|
||||
Description: b.config.AMIDescription,
|
||||
Users: b.config.AMIUsers,
|
||||
Groups: b.config.AMIGroups,
|
||||
},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
Regions: b.config.AMIRegions,
|
||||
Tags: b.config.AMITags,
|
||||
},
|
||||
&awscommon.StepCreateTags{
|
||||
Tags: b.config.AMITags,
|
||||
},
|
||||
}
|
||||
|
||||
// Run!
|
||||
|
||||
@@ -33,6 +33,7 @@ type Config struct {
|
||||
BundleUploadCommand string `mapstructure:"bundle_upload_command"`
|
||||
BundleVolCommand string `mapstructure:"bundle_vol_command"`
|
||||
S3Bucket string `mapstructure:"s3_bucket"`
|
||||
Tags map[string]string
|
||||
X509CertPath string `mapstructure:"x509_cert_path"`
|
||||
X509KeyPath string `mapstructure:"x509_key_path"`
|
||||
X509UploadPath string `mapstructure:"x509_upload_path"`
|
||||
@@ -210,19 +211,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
&StepBundleVolume{},
|
||||
&StepUploadBundle{},
|
||||
&StepRegisterAMI{},
|
||||
&awscommon.StepCreateTags{Tags: b.config.Tags},
|
||||
&awscommon.StepModifyAMIAttributes{
|
||||
Description: b.config.AMIDescription,
|
||||
Users: b.config.AMIUsers,
|
||||
Groups: b.config.AMIGroups,
|
||||
ProductCodes: b.config.AMIProductCodes,
|
||||
},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
Regions: b.config.AMIRegions,
|
||||
Tags: b.config.AMITags,
|
||||
},
|
||||
&awscommon.StepCreateTags{
|
||||
Tags: b.config.AMITags,
|
||||
},
|
||||
}
|
||||
|
||||
// Run!
|
||||
|
||||
@@ -27,6 +27,25 @@ func (s *StepBundleVolume) Run(state map[string]interface{}) multistep.StepActio
|
||||
x509RemoteCertPath := state["x509RemoteCertPath"].(string)
|
||||
x509RemoteKeyPath := state["x509RemoteKeyPath"].(string)
|
||||
|
||||
// Verify the AMI tools are available
|
||||
ui.Say("Checking for EC2 AMI tools...")
|
||||
cmd := &packer.RemoteCmd{Command: "ec2-ami-tools-version"}
|
||||
if err := comm.Start(cmd); err != nil {
|
||||
state["error"] = fmt.Errorf("Error checking for AMI tools: %s", err)
|
||||
ui.Error(state["error"].(error).Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
cmd.Wait()
|
||||
|
||||
if cmd.ExitStatus != 0 {
|
||||
state["error"] = fmt.Errorf(
|
||||
"The EC2 AMI tools could not be detected. These must be manually\n" +
|
||||
"via a provisioner or some other means and are required for Packer\n" +
|
||||
"to create an instance-store AMI.")
|
||||
ui.Error(state["error"].(error).Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Bundle the volume
|
||||
var err error
|
||||
config.BundleVolCommand, err = config.tpl.Process(config.BundleVolCommand, bundleCmdData{
|
||||
@@ -46,7 +65,7 @@ func (s *StepBundleVolume) Run(state map[string]interface{}) multistep.StepActio
|
||||
}
|
||||
|
||||
ui.Say("Bundling the volume...")
|
||||
cmd := new(packer.RemoteCmd)
|
||||
cmd = new(packer.RemoteCmd)
|
||||
cmd.Command = config.BundleVolCommand
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
state["error"] = fmt.Errorf("Error bundling volume: %s", err)
|
||||
|
||||
@@ -43,11 +43,7 @@ type DigitalOceanClient struct {
|
||||
// Creates a new client for communicating with DO
|
||||
func (d DigitalOceanClient) New(client string, key string) *DigitalOceanClient {
|
||||
c := &DigitalOceanClient{
|
||||
client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
},
|
||||
client: http.DefaultClient,
|
||||
BaseURL: DIGITALOCEAN_API_URL,
|
||||
ClientID: client,
|
||||
APIKey: key,
|
||||
@@ -115,15 +111,6 @@ func (d DigitalOceanClient) PowerOffDroplet(id uint) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Shutsdown a droplet. This is a "soft" shutdown.
|
||||
func (d DigitalOceanClient) ShutdownDroplet(id uint) error {
|
||||
path := fmt.Sprintf("droplets/%v/shutdown", id)
|
||||
|
||||
_, err := NewRequest(d, path, url.Values{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Creates a snaphot of a droplet by it's ID
|
||||
func (d DigitalOceanClient) CreateSnapshot(id uint, name string) error {
|
||||
path := fmt.Sprintf("droplets/%v/snapshot", id)
|
||||
|
||||
@@ -206,7 +206,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
SSHWaitTimeout: 5 * time.Minute,
|
||||
},
|
||||
new(common.StepProvision),
|
||||
new(stepShutdown),
|
||||
new(stepPowerOff),
|
||||
new(stepSnapshot),
|
||||
}
|
||||
|
||||
@@ -32,14 +32,15 @@ func (s *stepPowerOff) Run(state map[string]interface{}) multistep.StepAction {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Println("Waiting for poweroff event to complete...")
|
||||
ui.Say("Waiting for droplet to power off...")
|
||||
|
||||
// This arbitrary sleep is because we can't wait for the state
|
||||
// of the droplet to be 'off', as stepShutdown should already
|
||||
// have accomplished that, and the state indicator is the same.
|
||||
// We just have to assume that this event will process quickly.
|
||||
log.Printf("Sleeping for %v, event_delay", c.RawEventDelay)
|
||||
time.Sleep(c.eventDelay)
|
||||
err = waitForDropletState("off", dropletId, client, c)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stepShutdown struct{}
|
||||
|
||||
func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
|
||||
client := state["client"].(*DigitalOceanClient)
|
||||
c := state["config"].(config)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
dropletId := state["droplet_id"].(uint)
|
||||
|
||||
// Sleep arbitrarily before sending the request
|
||||
// Otherwise we get "pending event" errors, even though there isn't
|
||||
// one.
|
||||
log.Printf("Sleeping for %v, event_delay", c.RawEventDelay)
|
||||
time.Sleep(c.eventDelay)
|
||||
|
||||
err := client.ShutdownDroplet(dropletId)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error shutting down droplet: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Waiting for droplet to shutdown...")
|
||||
|
||||
err = waitForDropletState("off", dropletId, client, c)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepShutdown) Cleanup(state map[string]interface{}) {
|
||||
// no cleanup
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"os"
|
||||
)
|
||||
|
||||
// AccessConfig is for common configuration related to openstack access
|
||||
type AccessConfig struct {
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
Provider string `mapstructure:"provider"`
|
||||
}
|
||||
|
||||
// Auth returns a valid Auth object for access to openstack services, or
|
||||
// an error if the authentication couldn't be resolved.
|
||||
func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) {
|
||||
username := c.Username
|
||||
password := c.Password
|
||||
provider := c.Provider
|
||||
|
||||
if username == "" {
|
||||
username = os.Getenv("SDK_USERNAME")
|
||||
}
|
||||
if password == "" {
|
||||
password = os.Getenv("SDK_PASSWORD")
|
||||
}
|
||||
if provider == "" {
|
||||
provider = os.Getenv("SDK_PROVIDER")
|
||||
}
|
||||
|
||||
authoptions := gophercloud.AuthOptions{
|
||||
Username: username,
|
||||
Password: password,
|
||||
AllowReauth: true,
|
||||
}
|
||||
|
||||
return gophercloud.Authenticate(provider, authoptions)
|
||||
}
|
||||
|
||||
func (c *AccessConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||
if t == nil {
|
||||
var err error
|
||||
t, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
}
|
||||
|
||||
templates := map[string]*string{
|
||||
"username": &c.Username,
|
||||
"password": &c.Password,
|
||||
"provider": &c.Provider,
|
||||
}
|
||||
|
||||
errs := make([]error, 0)
|
||||
for n, ptr := range templates {
|
||||
var err error
|
||||
*ptr, err = t.Process(*ptr, nil)
|
||||
if err != nil {
|
||||
errs = append(
|
||||
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testAccessConfig() *AccessConfig {
|
||||
return &AccessConfig{}
|
||||
}
|
||||
|
||||
func TestAccessConfigPrepare_Region(t *testing.T) {
|
||||
c := testAccessConfig()
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Artifact is an artifact implementation that contains built images.
|
||||
type Artifact struct {
|
||||
// ImageId of built image
|
||||
ImageId string
|
||||
|
||||
// BuilderId is the unique ID for the builder that created this image
|
||||
BuilderIdValue string
|
||||
|
||||
// OpenStack connection for performing API stuff.
|
||||
Conn gophercloud.CloudServersProvider
|
||||
}
|
||||
|
||||
func (a *Artifact) BuilderId() string {
|
||||
return a.BuilderIdValue
|
||||
}
|
||||
|
||||
func (*Artifact) Files() []string {
|
||||
// We have no files
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
return a.ImageId
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
return fmt.Sprintf("An image was created: %v", a.ImageId)
|
||||
}
|
||||
|
||||
func (a *Artifact) Destroy() error {
|
||||
log.Printf("Destroying image: %d", a.ImageId)
|
||||
return a.Conn.DeleteImageById(a.ImageId)
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"cgl.tideland.biz/asserts"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestArtifact_Impl(t *testing.T) {
|
||||
assert := asserts.NewTestingAsserts(t, true)
|
||||
|
||||
var actual packer.Artifact
|
||||
assert.Implementor(&Artifact{}, &actual, "should be an Artifact")
|
||||
}
|
||||
|
||||
func TestArtifactId(t *testing.T) {
|
||||
assert := asserts.NewTestingAsserts(t, true)
|
||||
|
||||
expected := `b8cdf55b-c916-40bd-b190-389ec144c4ed`
|
||||
|
||||
a := &Artifact{
|
||||
ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed",
|
||||
}
|
||||
|
||||
result := a.Id()
|
||||
assert.Equal(result, expected, "should match output")
|
||||
}
|
||||
|
||||
func TestArtifactString(t *testing.T) {
|
||||
assert := asserts.NewTestingAsserts(t, true)
|
||||
|
||||
expected := "An image was created: b8cdf55b-c916-40bd-b190-389ec144c4ed"
|
||||
|
||||
a := &Artifact{
|
||||
ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed",
|
||||
}
|
||||
result := a.String()
|
||||
assert.Equal(result, expected, "should match output")
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
// The openstack package contains a packer.Builder implementation that
|
||||
// builds Images for openstack.
|
||||
|
||||
package openstack
|
||||
|
||||
import (
|
||||
//"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"log"
|
||||
)
|
||||
|
||||
// The unique ID for this builder
|
||||
const BuilderId = "mitchellh.openstack"
|
||||
|
||||
type config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
AccessConfig `mapstructure:",squash"`
|
||||
ImageConfig `mapstructure:",squash"`
|
||||
RunConfig `mapstructure:",squash"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
config config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
md, err := common.DecodeConfig(&b.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.tpl.UserVars = b.config.PackerUserVars
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(b.config.tpl)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.ImageConfig.Prepare(b.config.tpl)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(b.config.tpl)...)
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
log.Printf("Config: %+v", b.config)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
auth, err := b.config.AccessConfig.Auth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
api := &gophercloud.ApiCriteria{
|
||||
Name: "cloudServersOpenStack",
|
||||
Region: "DFW",
|
||||
VersionId: "2",
|
||||
UrlChoice: gophercloud.PublicURL,
|
||||
}
|
||||
csp, err := gophercloud.ServersApi(auth, *api)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := make(map[string]interface{})
|
||||
state["config"] = b.config
|
||||
state["csp"] = csp
|
||||
state["hook"] = hook
|
||||
state["ui"] = ui
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&StepKeyPair{},
|
||||
&StepRunSourceServer{
|
||||
Name: b.config.ImageName,
|
||||
Flavor: b.config.Flavor,
|
||||
SourceImage: b.config.SourceImage,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: SSHAddress(csp, b.config.SSHPort),
|
||||
SSHConfig: SSHConfig(b.config.SSHUsername),
|
||||
SSHWaitTimeout: b.config.SSHTimeout(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&stepCreateImage{},
|
||||
}
|
||||
|
||||
// Run!
|
||||
if b.config.PackerDebug {
|
||||
b.runner = &multistep.DebugRunner{
|
||||
Steps: steps,
|
||||
PauseFn: common.MultistepDebugFn(ui),
|
||||
}
|
||||
} else {
|
||||
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||
}
|
||||
|
||||
b.runner.Run(state)
|
||||
|
||||
// If there was an error, return that
|
||||
if rawErr, ok := state["error"]; ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// Build the artifact and return it
|
||||
artifact := &Artifact{
|
||||
ImageId: state["image"].(string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Conn: csp,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"username": "foo",
|
||||
"password": "bar",
|
||||
"provider": "foo",
|
||||
"image_name": "foo",
|
||||
"source_image": "foo",
|
||||
"flavor": "foo",
|
||||
"ssh_username": "root",
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packer.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_Prepare_BadType(t *testing.T) {
|
||||
b := &Builder{}
|
||||
c := map[string]interface{}{
|
||||
"password": []string{},
|
||||
}
|
||||
|
||||
err := b.Prepare(c)
|
||||
if err == nil {
|
||||
t.Fatalf("prepare should fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ImageName(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test good
|
||||
config["image_name"] = "foo"
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Test bad
|
||||
config["image_name"] = "foo {{"
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test bad
|
||||
delete(config, "image_name")
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// ImageConfig is for common configuration related to creating Images.
|
||||
type ImageConfig struct {
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
}
|
||||
|
||||
func (c *ImageConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||
if t == nil {
|
||||
var err error
|
||||
t, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
}
|
||||
|
||||
templates := map[string]*string{
|
||||
"image_name": &c.ImageName,
|
||||
}
|
||||
|
||||
errs := make([]error, 0)
|
||||
for n, ptr := range templates {
|
||||
var err error
|
||||
*ptr, err = t.Process(*ptr, nil)
|
||||
if err != nil {
|
||||
errs = append(
|
||||
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
if c.ImageName == "" {
|
||||
errs = append(errs, fmt.Errorf("An image_name must be specified"))
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testImageConfig() *ImageConfig {
|
||||
return &ImageConfig{
|
||||
ImageName: "foo",
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageConfigPrepare_Region(t *testing.T) {
|
||||
c := testImageConfig()
|
||||
if err := c.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %s", err)
|
||||
}
|
||||
|
||||
c.ImageName = ""
|
||||
if err := c.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RunConfig contains configuration for running an instance from a source
|
||||
// image and details on how to access that launched image.
|
||||
type RunConfig struct {
|
||||
SourceImage string `mapstructure:"source_image"`
|
||||
Flavor string `mapstructure:"flavor"`
|
||||
RawSSHTimeout string `mapstructure:"ssh_timeout"`
|
||||
SSHUsername string `mapstructure:"ssh_username"`
|
||||
SSHPort int `mapstructure:"ssh_port"`
|
||||
|
||||
// Unexported fields that are calculated from others
|
||||
sshTimeout time.Duration
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error {
|
||||
if t == nil {
|
||||
var err error
|
||||
t, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if c.SSHUsername == "" {
|
||||
c.SSHUsername = "root"
|
||||
}
|
||||
|
||||
if c.SSHPort == 0 {
|
||||
c.SSHPort = 22
|
||||
}
|
||||
|
||||
if c.RawSSHTimeout == "" {
|
||||
c.RawSSHTimeout = "1m"
|
||||
}
|
||||
|
||||
// Validation
|
||||
var err error
|
||||
errs := make([]error, 0)
|
||||
if c.SourceImage == "" {
|
||||
errs = append(errs, errors.New("A source_image must be specified"))
|
||||
}
|
||||
|
||||
if c.Flavor == "" {
|
||||
errs = append(errs, errors.New("A flavor must be specified"))
|
||||
}
|
||||
|
||||
if c.SSHUsername == "" {
|
||||
errs = append(errs, errors.New("An ssh_username must be specified"))
|
||||
}
|
||||
|
||||
templates := map[string]*string{
|
||||
"flavlor": &c.Flavor,
|
||||
"ssh_timeout": &c.RawSSHTimeout,
|
||||
"ssh_username": &c.SSHUsername,
|
||||
"source_image": &c.SourceImage,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
var err error
|
||||
*ptr, err = t.Process(*ptr, nil)
|
||||
if err != nil {
|
||||
errs = append(
|
||||
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (c *RunConfig) SSHTimeout() time.Duration {
|
||||
return c.sshTimeout
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Clear out the openstack env vars so they don't
|
||||
// affect our tests.
|
||||
os.Setenv("SDK_USERNAME", "")
|
||||
os.Setenv("SDK_PASSWORD", "")
|
||||
os.Setenv("SDK_PROVIDER", "")
|
||||
}
|
||||
|
||||
func testRunConfig() *RunConfig {
|
||||
return &RunConfig{
|
||||
SourceImage: "abcd",
|
||||
Flavor: "m1.small",
|
||||
SSHUsername: "root",
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare(t *testing.T) {
|
||||
c := testRunConfig()
|
||||
err := c.Prepare(nil)
|
||||
if len(err) > 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_InstanceType(t *testing.T) {
|
||||
c := testRunConfig()
|
||||
c.Flavor = ""
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SourceImage(t *testing.T) {
|
||||
c := testRunConfig()
|
||||
c.SourceImage = ""
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHPort(t *testing.T) {
|
||||
c := testRunConfig()
|
||||
c.SSHPort = 0
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.SSHPort != 22 {
|
||||
t.Fatalf("invalid value: %d", c.SSHPort)
|
||||
}
|
||||
|
||||
c.SSHPort = 44
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.SSHPort != 44 {
|
||||
t.Fatalf("invalid value: %d", c.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHTimeout(t *testing.T) {
|
||||
c := testRunConfig()
|
||||
c.RawSSHTimeout = ""
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c.RawSSHTimeout = "bad"
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHUsername(t *testing.T) {
|
||||
c := testRunConfig()
|
||||
c.SSHUsername = ""
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StateRefreshFunc is a function type used for StateChangeConf that is
|
||||
// responsible for refreshing the item being watched for a state change.
|
||||
//
|
||||
// It returns three results. `result` is any object that will be returned
|
||||
// as the final object after waiting for state change. This allows you to
|
||||
// return the final updated object, for example an openstack instance after
|
||||
// refreshing it.
|
||||
//
|
||||
// `state` is the latest state of that object. And `err` is any error that
|
||||
// may have happened while refreshing the state.
|
||||
type StateRefreshFunc func() (result interface{}, state string, progress int, err error)
|
||||
|
||||
// StateChangeConf is the configuration struct used for `WaitForState`.
|
||||
type StateChangeConf struct {
|
||||
Pending []string
|
||||
Refresh StateRefreshFunc
|
||||
StepState map[string]interface{}
|
||||
Target string
|
||||
}
|
||||
|
||||
// ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch
|
||||
// an openstacn server.
|
||||
func ServerStateRefreshFunc(csp gophercloud.CloudServersProvider, s *gophercloud.Server) StateRefreshFunc {
|
||||
return func() (interface{}, string, int, error) {
|
||||
resp, err := csp.ServerById(s.Id)
|
||||
if err != nil {
|
||||
log.Printf("Error on ServerStateRefresh: %s", err)
|
||||
return nil, "", 0, err
|
||||
}
|
||||
|
||||
return resp, resp.Status, resp.Progress, nil
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForState watches an object and waits for it to achieve a certain
|
||||
// state.
|
||||
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
|
||||
log.Printf("Waiting for state to become: %s", conf.Target)
|
||||
|
||||
for {
|
||||
var currentProgress int
|
||||
var currentState string
|
||||
i, currentState, currentProgress, err = conf.Refresh()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if currentState == conf.Target {
|
||||
return
|
||||
}
|
||||
|
||||
if conf.StepState != nil {
|
||||
if _, ok := conf.StepState[multistep.StateCancelled]; ok {
|
||||
return nil, errors.New("interrupted")
|
||||
}
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, allowed := range conf.Pending {
|
||||
if currentState == allowed {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Waiting for state to become: %s currently %s (%d%%)", conf.Target, currentState, currentProgress)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SSHAddress returns a function that can be given to the SSH communicator
|
||||
// for determining the SSH address based on the server AccessIPv4 setting..
|
||||
func SSHAddress(csp gophercloud.CloudServersProvider, port int) func(map[string]interface{}) (string, error) {
|
||||
return func(state map[string]interface{}) (string, error) {
|
||||
for j := 0; j < 2; j++ {
|
||||
s := state["server"].(*gophercloud.Server)
|
||||
if s.AccessIPv4 != "" {
|
||||
return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil
|
||||
}
|
||||
serverState, err := csp.ServerById(s.Id)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
state["server"] = serverState
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
return "", errors.New("couldn't determine IP address for server")
|
||||
}
|
||||
}
|
||||
|
||||
// SSHConfig returns a function that can be used for the SSH communicator
|
||||
// config for connecting to the instance created over SSH using the generated
|
||||
// private key.
|
||||
func SSHConfig(username string) func(map[string]interface{}) (*gossh.ClientConfig, error) {
|
||||
return func(state map[string]interface{}) (*gossh.ClientConfig, error) {
|
||||
privateKey := state["privateKey"].(string)
|
||||
|
||||
keyring := new(ssh.SimpleKeychain)
|
||||
if err := keyring.AddPEMKey(privateKey); err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthKeyring(keyring),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stepCreateImage struct{}
|
||||
|
||||
func (s *stepCreateImage) Run(state map[string]interface{}) multistep.StepAction {
|
||||
csp := state["csp"].(gophercloud.CloudServersProvider)
|
||||
config := state["config"].(config)
|
||||
server := state["server"].(*gophercloud.Server)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
// Create the image
|
||||
ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName))
|
||||
createOpts := gophercloud.CreateImage{
|
||||
Name: config.ImageName,
|
||||
}
|
||||
imageId, err := csp.CreateImage(server.Id, createOpts)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating image: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the Image ID in the state
|
||||
ui.Say(fmt.Sprintf("Image: %s", imageId))
|
||||
state["image"] = imageId
|
||||
|
||||
// Wait for the image to become ready
|
||||
ui.Say("Waiting for image to become ready...")
|
||||
if err := WaitForImage(csp, imageId); err != nil {
|
||||
err := fmt.Errorf("Error waiting for image: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateImage) Cleanup(map[string]interface{}) {
|
||||
// No cleanup...
|
||||
}
|
||||
|
||||
// WaitForImage waits for the given Image ID to become ready.
|
||||
func WaitForImage(csp gophercloud.CloudServersProvider, imageId string) error {
|
||||
for {
|
||||
image, err := csp.ImageById(imageId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if image.Status == "ACTIVE" {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Waiting for image creation status: %s (%d%%)", image.Status, image.Progress)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"cgl.tideland.biz/identifier"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"log"
|
||||
)
|
||||
|
||||
type StepKeyPair struct {
|
||||
keyName string
|
||||
}
|
||||
|
||||
func (s *StepKeyPair) Run(state map[string]interface{}) multistep.StepAction {
|
||||
csp := state["csp"].(gophercloud.CloudServersProvider)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say("Creating temporary keypair for this instance...")
|
||||
keyName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw()))
|
||||
log.Printf("temporary keypair name: %s", keyName)
|
||||
keyResp, err := csp.CreateKeyPair(gophercloud.NewKeyPair{Name: keyName})
|
||||
if err != nil {
|
||||
state["error"] = fmt.Errorf("Error creating temporary keypair: %s", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the keyname so we know to delete it later
|
||||
s.keyName = keyName
|
||||
|
||||
// Set some state data for use in future steps
|
||||
state["keyPair"] = keyName
|
||||
state["privateKey"] = keyResp.PrivateKey
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepKeyPair) Cleanup(state map[string]interface{}) {
|
||||
// If no key name is set, then we never created it, so just return
|
||||
if s.keyName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
csp := state["csp"].(gophercloud.CloudServersProvider)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say("Deleting temporary keypair...")
|
||||
err := csp.DeleteKeyPair(s.keyName)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error cleaning up keypair. Please delete the key manually: %s", s.keyName))
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"log"
|
||||
)
|
||||
|
||||
type StepRunSourceServer struct {
|
||||
Flavor string
|
||||
Name string
|
||||
SourceImage string
|
||||
|
||||
server *gophercloud.Server
|
||||
}
|
||||
|
||||
func (s *StepRunSourceServer) Run(state map[string]interface{}) multistep.StepAction {
|
||||
csp := state["csp"].(gophercloud.CloudServersProvider)
|
||||
keyName := state["keyPair"].(string)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
// XXX - validate image and flavor is available
|
||||
|
||||
server := gophercloud.NewServer{
|
||||
Name: s.Name,
|
||||
ImageRef: s.SourceImage,
|
||||
FlavorRef: s.Flavor,
|
||||
KeyPairName: keyName,
|
||||
}
|
||||
|
||||
serverResp, err := csp.CreateServer(server)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error launching source server: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.server, err = csp.ServerById(serverResp.Id)
|
||||
log.Printf("server id: %s", s.server.Id)
|
||||
|
||||
ui.Say(fmt.Sprintf("Waiting for server (%s) to become ready...", s.server.Id))
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"BUILD"},
|
||||
Target: "ACTIVE",
|
||||
Refresh: ServerStateRefreshFunc(csp, s.server),
|
||||
StepState: state,
|
||||
}
|
||||
latestServer, err := WaitForState(&stateChange)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for server (%s) to become ready: %s", s.server.Id, err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.server = latestServer.(*gophercloud.Server)
|
||||
state["server"] = s.server
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepRunSourceServer) Cleanup(state map[string]interface{}) {
|
||||
if s.server == nil {
|
||||
return
|
||||
}
|
||||
|
||||
csp := state["csp"].(gophercloud.CloudServersProvider)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
|
||||
ui.Say("Terminating the source server...")
|
||||
if err := csp.DeleteServerById(s.server.Id); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
stateChange := StateChangeConf{
|
||||
Pending: []string{"ACTIVE", "BUILD", "REBUILD", "SUSPENDED"},
|
||||
Refresh: ServerStateRefreshFunc(csp, s.server),
|
||||
Target: "DELETED",
|
||||
}
|
||||
|
||||
WaitForState(&stateChange)
|
||||
}
|
||||
@@ -27,7 +27,6 @@ type config struct {
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
FloppyFiles []string `mapstructure:"floppy_files"`
|
||||
Format string `mapstructure:"format"`
|
||||
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
|
||||
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
|
||||
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
|
||||
@@ -43,7 +42,6 @@ type config struct {
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
|
||||
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
|
||||
SSHKeyPath string `mapstructure:"ssh_key_path"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
@@ -133,10 +131,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
|
||||
}
|
||||
|
||||
if b.config.Format == "" {
|
||||
b.config.Format = "ovf"
|
||||
}
|
||||
|
||||
// Errors
|
||||
templates := map[string]*string{
|
||||
"guest_additions_sha256": &b.config.GuestAdditionsSHA256,
|
||||
@@ -151,7 +145,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
"ssh_username": &b.config.SSHUser,
|
||||
"virtualbox_version_file": &b.config.VBoxVersionFile,
|
||||
"vm_name": &b.config.VMName,
|
||||
"format": &b.config.Format,
|
||||
"boot_wait": &b.config.RawBootWait,
|
||||
"shutdown_timeout": &b.config.RawShutdownTimeout,
|
||||
"ssh_wait_timeout": &b.config.RawSSHWaitTimeout,
|
||||
@@ -204,11 +197,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
if !(b.config.Format == "ovf" || b.config.Format == "ova") {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("invalid format, only 'ovf' or 'ova' are allowed"))
|
||||
}
|
||||
|
||||
if b.config.HTTPPortMin > b.config.HTTPPortMax {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("http_port_min must be less than http_port_max"))
|
||||
@@ -283,16 +271,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
|
||||
}
|
||||
|
||||
if b.config.SSHKeyPath != "" {
|
||||
if _, err := os.Stat(b.config.SSHKeyPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
||||
} else if _, err := sshKeyToKeyring(b.config.SSHKeyPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.SSHHostPortMin > b.config.SSHHostPortMax {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
|
||||
|
||||
@@ -8,36 +8,6 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testPem = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
|
||||
hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW
|
||||
LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN
|
||||
AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD
|
||||
2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH
|
||||
uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3
|
||||
5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV
|
||||
BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG
|
||||
E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko
|
||||
9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF
|
||||
K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3
|
||||
/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+
|
||||
2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa
|
||||
nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn
|
||||
kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6
|
||||
hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC
|
||||
v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl
|
||||
b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR
|
||||
v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3
|
||||
uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1
|
||||
9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR
|
||||
lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc
|
||||
eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa
|
||||
1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG
|
||||
3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"iso_checksum": "foo",
|
||||
@@ -88,10 +58,6 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
||||
if b.config.VMName != "packer-foo" {
|
||||
t.Errorf("bad vm name: %s", b.config.VMName)
|
||||
}
|
||||
|
||||
if b.config.Format != "ovf" {
|
||||
t.Errorf("bad format: %s", b.config.Format)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_BootWait(t *testing.T) {
|
||||
@@ -282,34 +248,6 @@ func TestBuilderPrepare_HTTPPort(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_Format(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Bad
|
||||
config["format"] = "illegal value"
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Good
|
||||
config["format"] = "ova"
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Good
|
||||
config["format"] = "ovf"
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
@@ -514,55 +452,6 @@ func TestBuilderPrepare_SSHHostPort(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_sshKeyPath(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
config["ssh_key_path"] = ""
|
||||
b = Builder{}
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
config["ssh_key_path"] = "/i/dont/exist"
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test bad contents
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
if _, err := tf.Write([]byte("HELLO!")); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config["ssh_key_path"] = tf.Name()
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test good contents
|
||||
tf.Seek(0, 0)
|
||||
tf.Truncate(0)
|
||||
tf.Write([]byte(testPem))
|
||||
config["ssh_key_path"] = tf.Name()
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SSHUser(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
gossh "code.google.com/p/go.crypto/ssh"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func sshAddress(state map[string]interface{}) (string, error) {
|
||||
@@ -16,43 +14,12 @@ func sshAddress(state map[string]interface{}) (string, error) {
|
||||
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
|
||||
config := state["config"].(*config)
|
||||
|
||||
auth := []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
}
|
||||
|
||||
if config.SSHKeyPath != "" {
|
||||
keyring, err := sshKeyToKeyring(config.SSHKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth = append(auth, gossh.ClientAuthKeyring(keyring))
|
||||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: auth,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
keyBytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyring := new(ssh.SimpleKeychain)
|
||||
if err := keyring.AddPEMKey(string(keyBytes)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return keyring, nil
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (s *stepExport) Run(state map[string]interface{}) multistep.StepAction {
|
||||
}
|
||||
|
||||
// Export the VM to an OVF
|
||||
outputPath := filepath.Join(config.OutputDir, "packer."+config.Format)
|
||||
outputPath := filepath.Join(config.OutputDir, "packer.ovf")
|
||||
|
||||
command = []string{
|
||||
"export",
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
@@ -28,7 +27,6 @@ type config struct {
|
||||
|
||||
DiskName string `mapstructure:"vmdk_name"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
DiskTypeId string `mapstructure:"disk_type_id"`
|
||||
FloppyFiles []string `mapstructure:"floppy_files"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
ISOChecksum string `mapstructure:"iso_checksum"`
|
||||
@@ -44,14 +42,11 @@ type config struct {
|
||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
SSHKeyPath string `mapstructure:"ssh_key_path"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
SSHSkipRequestPty bool `mapstructure:"ssh_skip_request_pty"`
|
||||
ToolsUploadFlavor string `mapstructure:"tools_upload_flavor"`
|
||||
ToolsUploadPath string `mapstructure:"tools_upload_path"`
|
||||
VMXData map[string]string `mapstructure:"vmx_data"`
|
||||
VMXTemplatePath string `mapstructure:"vmx_template_path"`
|
||||
VNCPortMin uint `mapstructure:"vnc_port_min"`
|
||||
VNCPortMax uint `mapstructure:"vnc_port_max"`
|
||||
|
||||
@@ -89,11 +84,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
b.config.DiskSize = 40000
|
||||
}
|
||||
|
||||
if b.config.DiskTypeId == "" {
|
||||
// Default is growable virtual disk split in 2GB files.
|
||||
b.config.DiskTypeId = "1"
|
||||
}
|
||||
|
||||
if b.config.FloppyFiles == nil {
|
||||
b.config.FloppyFiles = make([]string, 0)
|
||||
}
|
||||
@@ -155,7 +145,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
"boot_wait": &b.config.RawBootWait,
|
||||
"shutdown_timeout": &b.config.RawShutdownTimeout,
|
||||
"ssh_wait_timeout": &b.config.RawSSHWaitTimeout,
|
||||
"vmx_template_path": &b.config.VMXTemplatePath,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
@@ -264,16 +253,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.SSHKeyPath != "" {
|
||||
if _, err := os.Stat(b.config.SSHKeyPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
||||
} else if _, err := sshKeyToKeyring(b.config.SSHKeyPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.SSHUser == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("An ssh_username must be specified."))
|
||||
@@ -312,14 +291,6 @@ func (b *Builder) Prepare(raws ...interface{}) error {
|
||||
errs, fmt.Errorf("tools_upload_path invalid: %s", err))
|
||||
}
|
||||
|
||||
if b.config.VMXTemplatePath != "" {
|
||||
if err := b.validateVMXTemplatePath(); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("vmx_template_path is invalid: %s", err))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if b.config.VNCPortMin > b.config.VNCPortMax {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
|
||||
@@ -365,7 +336,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: b.config.sshWaitTimeout,
|
||||
NoPty: b.config.SSHSkipRequestPty,
|
||||
},
|
||||
&stepUploadTools{},
|
||||
&common.StepProvision{},
|
||||
@@ -436,18 +406,3 @@ func (b *Builder) Cancel() {
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) validateVMXTemplatePath() error {
|
||||
f, err := os.Open(b.config.VMXTemplatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.config.tpl.Validate(string(data))
|
||||
}
|
||||
|
||||
@@ -9,36 +9,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var testPem = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
|
||||
hh+rDzFYAIIzlmrJopvOe0clqmi3mIP9dtkjPFrYflq52a2CF5q+BdwsJXuRHbJW
|
||||
LmStZUwW1khSz93DhvhmK50nIaczW63u4EO/jJb3xj+wxR1Nkk9bxi3DDsYFt8SN
|
||||
AzYx9kjlEYQ/+sI4/ATfmdV9h78SVotjScupd9KFzzi76gWq9gwyCBLRynTUWlyD
|
||||
2UOfJRkOvhN6/jKzvYfVVwjPSfA9IMuooHdScmC4F6KBKJl/zf/zETM0XyzIDNmH
|
||||
uOPbCiljq2WoRM+rY6ET84EO0kVXbfx8uxUsqQIDAQABAoIBAQCkPj9TF0IagbM3
|
||||
5BSs/CKbAWS4dH/D4bPlxx4IRCNirc8GUg+MRb04Xz0tLuajdQDqeWpr6iLZ0RKV
|
||||
BvreLF+TOdV7DNQ4XE4gSdJyCtCaTHeort/aordL3l0WgfI7mVk0L/yfN1PEG4YG
|
||||
E9q1TYcyrB3/8d5JwIkjabxERLglCcP+geOEJp+QijbvFIaZR/n2irlKW4gSy6ko
|
||||
9B0fgUnhkHysSg49ChHQBPQ+o5BbpuLrPDFMiTPTPhdfsvGGcyCGeqfBA56oHcSF
|
||||
K02Fg8OM+Bd1lb48LAN9nWWY4WbwV+9bkN3Ym8hO4c3a/Dxf2N7LtAQqWZzFjvM3
|
||||
/AaDvAgBAoGBAPLD+Xn1IYQPMB2XXCXfOuJewRY7RzoVWvMffJPDfm16O7wOiW5+
|
||||
2FmvxUDayk4PZy6wQMzGeGKnhcMMZTyaq2g/QtGfrvy7q1Lw2fB1VFlVblvqhoJa
|
||||
nMJojjC4zgjBkXMHsRLeTmgUKyGs+fdFbfI6uejBnnf+eMVUMIdJ+6I9AoGBANCn
|
||||
kWO9640dttyXURxNJ3lBr2H3dJOkmD6XS+u+LWqCSKQe691Y/fZ/ZL0Oc4Mhy7I6
|
||||
hsy3kDQ5k2V0fkaNODQIFJvUqXw2pMewUk8hHc9403f4fe9cPrL12rQ8WlQw4yoC
|
||||
v2B61vNczCCUDtGxlAaw8jzSRaSI5s6ax3K7enbdAoGBAJB1WYDfA2CoAQO6y9Sl
|
||||
b07A/7kQ8SN5DbPaqrDrBdJziBQxukoMJQXJeGFNUFD/DXFU5Fp2R7C86vXT7HIR
|
||||
v6m66zH+CYzOx/YE6EsUJms6UP9VIVF0Rg/RU7teXQwM01ZV32LQ8mswhTH20o/3
|
||||
uqMHmxUMEhZpUMhrfq0isyApAoGAe1UxGTXfj9AqkIVYylPIq2HqGww7+jFmVEj1
|
||||
9Wi6S6Sq72ffnzzFEPkIQL/UA4TsdHMnzsYKFPSbbXLIWUeMGyVTmTDA5c0e5XIR
|
||||
lPhMOKCAzv8w4VUzMnEkTzkFY5JqFCD/ojW57KvDdNZPVB+VEcdxyAW6aKELXMAc
|
||||
eHLc1nkCgYEApm/motCTPN32nINZ+Vvywbv64ZD+gtpeMNP3CLrbe1X9O+H52AXa
|
||||
1jCoOldWR8i2bs2NVPcKZgdo6fFULqE4dBX7Te/uYEIuuZhYLNzRO1IKU/YaqsXG
|
||||
3bfQ8hKYcSnTfE0gPtLDnqCIxTocaGLSHeG3TH9fTw+dA8FvWpUztI4=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"iso_checksum": "foo",
|
||||
@@ -369,55 +339,6 @@ func TestBuilderPrepare_ShutdownTimeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_sshKeyPath(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
config["ssh_key_path"] = ""
|
||||
b = Builder{}
|
||||
err := b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
config["ssh_key_path"] = "/i/dont/exist"
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test bad contents
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
if _, err := tf.Write([]byte("HELLO!")); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config["ssh_key_path"] = tf.Name()
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test good contents
|
||||
tf.Seek(0, 0)
|
||||
tf.Truncate(0)
|
||||
tf.Write([]byte(testPem))
|
||||
config["ssh_key_path"] = tf.Name()
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SSHUser(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
@@ -516,56 +437,6 @@ func TestBuilderPrepare_ToolsUploadPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_VMXTemplatePath(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test bad
|
||||
config["vmx_template_path"] = "/i/dont/exist/forreal"
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test good
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
if _, err := tf.Write([]byte("HELLO!")); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config["vmx_template_path"] = tf.Name()
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Bad template
|
||||
tf2, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf2.Name())
|
||||
defer tf2.Close()
|
||||
|
||||
if _, err := tf2.Write([]byte("{{foo}")); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config["vmx_template_path"] = tf2.Name()
|
||||
b = Builder{}
|
||||
err = b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_VNCPort(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
@@ -15,7 +15,7 @@ type Driver interface {
|
||||
CompactDisk(string) error
|
||||
|
||||
// CreateDisk creates a virtual disk with the given size.
|
||||
CreateDisk(string, string, string) error
|
||||
CreateDisk(string, string) error
|
||||
|
||||
// Checks if the VMX file at the given path is running.
|
||||
IsRunning(string) (bool, error)
|
||||
|
||||
@@ -28,8 +28,8 @@ func (d *Fusion5Driver) CompactDisk(diskPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) CreateDisk(output string, size string, type_id string) error {
|
||||
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
|
||||
func (d *Fusion5Driver) CreateDisk(output string, size string) error {
|
||||
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -51,12 +51,12 @@ func (d *Player5LinuxDriver) qemuCompactDisk(diskPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5LinuxDriver) CreateDisk(output string, size string, type_id string) error {
|
||||
func (d *Player5LinuxDriver) CreateDisk(output string, size string) error {
|
||||
var cmd *exec.Cmd
|
||||
if d.QemuImgPath != "" {
|
||||
cmd = exec.Command(d.QemuImgPath, "create", "-f", "vmdk", "-o", "compat6", output, size)
|
||||
} else {
|
||||
cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
|
||||
cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
|
||||
}
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
|
||||
@@ -31,8 +31,8 @@ func (d *Workstation9Driver) CompactDisk(diskPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9Driver) CreateDisk(output string, size string, type_id string) error {
|
||||
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
|
||||
func (d *Workstation9Driver) CreateDisk(output string, size string) error {
|
||||
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", "1", output)
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+5
-36
@@ -61,43 +61,12 @@ func sshAddress(state map[string]interface{}) (string, error) {
|
||||
func sshConfig(state map[string]interface{}) (*gossh.ClientConfig, error) {
|
||||
config := state["config"].(*config)
|
||||
|
||||
auth := []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
}
|
||||
|
||||
if config.SSHKeyPath != "" {
|
||||
keyring, err := sshKeyToKeyring(config.SSHKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth = append(auth, gossh.ClientAuthKeyring(keyring))
|
||||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
Auth: auth,
|
||||
Auth: []gossh.ClientAuth{
|
||||
gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)),
|
||||
gossh.ClientAuthKeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
keyBytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyring := new(ssh.SimpleKeychain)
|
||||
if err := keyring.AddPEMKey(string(keyBytes)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return keyring, nil
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func (stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction {
|
||||
|
||||
ui.Say("Creating virtual machine disk")
|
||||
full_disk_path := filepath.Join(config.OutputDir, config.DiskName+".vmdk")
|
||||
if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize), config.DiskTypeId); err != nil {
|
||||
if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize)); err != nil {
|
||||
err := fmt.Errorf("Error creating disk: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package vmware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type vmxTemplateData struct {
|
||||
@@ -42,37 +42,11 @@ func (stepCreateVMX) Run(state map[string]interface{}) multistep.StepAction {
|
||||
ISOPath: isoPath,
|
||||
}
|
||||
|
||||
vmxTemplate := DefaultVMXTemplate
|
||||
if config.VMXTemplatePath != "" {
|
||||
f, err := os.Open(config.VMXTemplatePath)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error reading VMX template: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer f.Close()
|
||||
var buf bytes.Buffer
|
||||
t := template.Must(template.New("vmx").Parse(DefaultVMXTemplate))
|
||||
t.Execute(&buf, tplData)
|
||||
|
||||
rawBytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error reading VMX template: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
vmxTemplate = string(rawBytes)
|
||||
}
|
||||
|
||||
vmxContents, err := config.tpl.Process(vmxTemplate, tplData)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error procesing VMX template: %s", err)
|
||||
state["error"] = err
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
vmxData := ParseVMX(vmxContents)
|
||||
vmxData := ParseVMX(buf.String())
|
||||
if config.VMXData != nil {
|
||||
log.Println("Setting custom VMX data...")
|
||||
for k, v := range config.VMXData {
|
||||
|
||||
@@ -187,18 +187,8 @@ func (c Command) Run(env packer.Environment, args []string) int {
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
env.Ui().Machine("error-count", strconv.FormatInt(int64(len(errors)), 10))
|
||||
|
||||
env.Ui().Error("\n==> Some builds didn't complete successfully and had errors:")
|
||||
for name, err := range errors {
|
||||
// Create a UI for the machine readable stuff to be targetted
|
||||
ui := &packer.TargettedUi{
|
||||
Target: name,
|
||||
Ui: env.Ui(),
|
||||
}
|
||||
|
||||
ui.Machine("error", err.Error())
|
||||
|
||||
env.Ui().Error(fmt.Sprintf("--> %s: %s", name, err))
|
||||
}
|
||||
}
|
||||
@@ -243,7 +233,6 @@ func (c Command) Run(env packer.Environment, args []string) int {
|
||||
ui.Machine("artifact", iStr, "nil")
|
||||
}
|
||||
|
||||
ui.Machine("artifact", iStr, "end")
|
||||
env.Ui().Say(message.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,27 +65,6 @@ func (f *BuildOptions) AllUserVars() (map[string]string, error) {
|
||||
// configured options.
|
||||
func (f *BuildOptions) Builds(t *packer.Template, cf *packer.ComponentFinder) ([]packer.Build, error) {
|
||||
buildNames := t.BuildNames()
|
||||
|
||||
checks := make(map[string][]string)
|
||||
checks["except"] = f.Except
|
||||
checks["only"] = f.Only
|
||||
for t, ns := range checks {
|
||||
for _, n := range ns {
|
||||
found := false
|
||||
for _, actual := range buildNames {
|
||||
if actual == n {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf(
|
||||
"Unknown build in '%s' flag: %s", t, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builds := make([]packer.Build, 0, len(buildNames))
|
||||
for _, buildName := range buildNames {
|
||||
if len(f.Except) > 0 {
|
||||
|
||||
@@ -1,102 +1,9 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testTemplate() (*packer.Template, *packer.ComponentFinder) {
|
||||
tplData := `{
|
||||
"builders": [
|
||||
{
|
||||
"type": "foo"
|
||||
},
|
||||
{
|
||||
"type": "bar"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
tpl, err := packer.ParseTemplate([]byte(tplData))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cf := &packer.ComponentFinder{
|
||||
Builder: func(string) (packer.Builder, error) { return new(packer.MockBuilder), nil },
|
||||
}
|
||||
|
||||
return tpl, cf
|
||||
}
|
||||
|
||||
func TestBuildOptionsBuilds(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
bs, err := opts.Builds(testTemplate())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(bs) != 2 {
|
||||
t.Fatalf("bad: %d", len(bs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOptionsBuilds_except(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Except = []string{"foo"}
|
||||
|
||||
bs, err := opts.Builds(testTemplate())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(bs) != 1 {
|
||||
t.Fatalf("bad: %d", len(bs))
|
||||
}
|
||||
|
||||
if bs[0].Name() != "bar" {
|
||||
t.Fatalf("bad: %s", bs[0].Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOptionsBuilds_only(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Only = []string{"foo"}
|
||||
|
||||
bs, err := opts.Builds(testTemplate())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(bs) != 1 {
|
||||
t.Fatalf("bad: %d", len(bs))
|
||||
}
|
||||
|
||||
if bs[0].Name() != "foo" {
|
||||
t.Fatalf("bad: %s", bs[0].Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOptionsBuilds_exceptNonExistent(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Except = []string{"i-dont-exist"}
|
||||
|
||||
_, err := opts.Builds(testTemplate())
|
||||
if err == nil {
|
||||
t.Fatal("err should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOptionsBuilds_onlyNonExistent(t *testing.T) {
|
||||
opts := new(BuildOptions)
|
||||
opts.Only = []string{"i-dont-exist"}
|
||||
|
||||
_, err := opts.Builds(testTemplate())
|
||||
if err == nil {
|
||||
t.Fatal("err should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOptionsValidate(t *testing.T) {
|
||||
bf := new(BuildOptions)
|
||||
|
||||
|
||||
+1
-15
@@ -5,7 +5,6 @@ import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -61,8 +60,6 @@ func HashForType(t string) hash.Hash {
|
||||
return sha1.New()
|
||||
case "sha256":
|
||||
return sha256.New()
|
||||
case "sha512":
|
||||
return sha512.New()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -191,18 +188,7 @@ func (*HTTPDownloader) Cancel() {
|
||||
|
||||
func (d *HTTPDownloader) Download(dst io.Writer, src *url.URL) error {
|
||||
log.Printf("Starting download: %s", src.String())
|
||||
req, err := http.NewRequest("GET", src.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
resp, err := http.Get(src.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -81,19 +81,6 @@ func TestHashForType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if h := HashForType("sha512"); h == nil {
|
||||
t.Fatalf("sha512 hash is nil")
|
||||
} else {
|
||||
h.Write([]byte("foo"))
|
||||
result := h.Sum(nil)
|
||||
|
||||
expected := "f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7"
|
||||
actual := hex.EncodeToString(result)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad hash: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
if HashForType("fake") != nil {
|
||||
t.Fatalf("fake hash is not nil")
|
||||
}
|
||||
|
||||
@@ -34,9 +34,6 @@ type StepConnectSSH struct {
|
||||
// SSHWaitTimeout is the total timeout to wait for SSH to become available.
|
||||
SSHWaitTimeout time.Duration
|
||||
|
||||
// NoPty, if true, will not request a Pty from the remote end.
|
||||
NoPty bool
|
||||
|
||||
comm packer.Communicator
|
||||
}
|
||||
|
||||
@@ -119,7 +116,7 @@ func (s *StepConnectSSH) waitForSSH(state map[string]interface{}, cancel <-chan
|
||||
}
|
||||
|
||||
// Attempt to connect to SSH port
|
||||
connFunc := ssh.ConnectFunc("tcp", address)
|
||||
connFunc := ssh.ConnectFunc("tcp", address, 5*time.Minute)
|
||||
nc, err := connFunc()
|
||||
if err != nil {
|
||||
log.Printf("TCP connection to SSH ip/port failed: %s", err)
|
||||
@@ -131,7 +128,6 @@ func (s *StepConnectSSH) waitForSSH(state map[string]interface{}, cancel <-chan
|
||||
config := &ssh.Config{
|
||||
Connection: connFunc,
|
||||
SSHConfig: sshConfig,
|
||||
NoPty: s.NoPty,
|
||||
}
|
||||
|
||||
log.Println("Attempting SSH connection...")
|
||||
|
||||
@@ -10,10 +10,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type comm struct {
|
||||
@@ -31,9 +28,6 @@ type Config struct {
|
||||
// in use will be closed as part of the Close method, or in the
|
||||
// case an error occurs.
|
||||
Connection func() (net.Conn, error)
|
||||
|
||||
// NoPty, if true, will not request a pty from the remote end.
|
||||
NoPty bool
|
||||
}
|
||||
|
||||
// Creates a new packer.Communicator implementation over SSH. This takes
|
||||
@@ -63,17 +57,15 @@ func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
|
||||
session.Stdout = cmd.Stdout
|
||||
session.Stderr = cmd.Stderr
|
||||
|
||||
if !c.config.NoPty {
|
||||
// Request a PTY
|
||||
termModes := ssh.TerminalModes{
|
||||
ssh.ECHO: 0, // do not echo
|
||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
// Request a PTY
|
||||
termModes := ssh.TerminalModes{
|
||||
ssh.ECHO: 0, // do not echo
|
||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
|
||||
if err = session.RequestPty("xterm", 80, 40, termModes); err != nil {
|
||||
return
|
||||
}
|
||||
if err = session.RequestPty("xterm", 80, 40, termModes); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("starting remote command: %s", cmd.Command)
|
||||
@@ -82,16 +74,10 @@ func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// A channel to keep track of our done state
|
||||
doneCh := make(chan struct{})
|
||||
sessionLock := new(sync.Mutex)
|
||||
timedOut := false
|
||||
|
||||
// Start a goroutine to wait for the session to end and set the
|
||||
// exit boolean and status.
|
||||
go func() {
|
||||
defer session.Close()
|
||||
|
||||
err := session.Wait()
|
||||
exitStatus := 0
|
||||
if err != nil {
|
||||
@@ -101,61 +87,46 @@ func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
sessionLock.Lock()
|
||||
defer sessionLock.Unlock()
|
||||
|
||||
if timedOut {
|
||||
// We timed out, so set the exit status to -1
|
||||
exitStatus = -1
|
||||
}
|
||||
|
||||
log.Printf("remote command exited with '%d': %s", exitStatus, cmd.Command)
|
||||
cmd.SetExited(exitStatus)
|
||||
close(doneCh)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
failures := 0
|
||||
for {
|
||||
dummy, err := c.config.Connection()
|
||||
if err == nil {
|
||||
failures = 0
|
||||
dummy.Close()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-doneCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("background SSH connection checker failure: %s", err)
|
||||
failures += 1
|
||||
}
|
||||
|
||||
if failures < 5 {
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
// Acquire a lock in order to modify session state
|
||||
sessionLock.Lock()
|
||||
defer sessionLock.Unlock()
|
||||
|
||||
// Kill the connection and mark that we timed out.
|
||||
log.Printf("Too many SSH connection failures. Killing it!")
|
||||
c.conn.Close()
|
||||
timedOut = true
|
||||
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *comm) Upload(path string, input io.Reader) error {
|
||||
session, err := c.newSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer session.Close()
|
||||
|
||||
// Get a pipe to stdin so that we can send data down
|
||||
w, err := session.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We only want to close once, so we nil w after we close it,
|
||||
// and only close in the defer if it hasn't been closed already.
|
||||
defer func() {
|
||||
if w != nil {
|
||||
w.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Get a pipe to stdout so that we can get responses back
|
||||
stdoutPipe, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdoutR := bufio.NewReader(stdoutPipe)
|
||||
|
||||
// Set stderr to a bytes buffer
|
||||
stderr := new(bytes.Buffer)
|
||||
session.Stderr = stderr
|
||||
|
||||
// The target directory and file for talking the SCP protocol
|
||||
target_dir := filepath.Dir(path)
|
||||
target_file := filepath.Base(path)
|
||||
@@ -165,41 +136,69 @@ func (c *comm) Upload(path string, input io.Reader) error {
|
||||
// which works for unix and windows
|
||||
target_dir = filepath.ToSlash(target_dir)
|
||||
|
||||
scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error {
|
||||
return scpUploadFile(target_file, input, w, stdoutR)
|
||||
// Start the sink mode on the other side
|
||||
// TODO(mitchellh): There are probably issues with shell escaping the path
|
||||
log.Println("Starting remote scp process in sink mode")
|
||||
if err = session.Start("scp -vt " + target_dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.scpSession("scp -vt "+target_dir, scpFunc)
|
||||
}
|
||||
|
||||
func (c *comm) UploadDir(dst string, src string, excl []string) error {
|
||||
log.Printf("Upload dir '%s' to '%s'", src, dst)
|
||||
scpFunc := func(w io.Writer, r *bufio.Reader) error {
|
||||
uploadEntries := func() error {
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
entries, err := f.Readdir(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return scpUploadDir(src, entries, w, r)
|
||||
}
|
||||
|
||||
if src[len(src)-1] != '/' {
|
||||
log.Printf("No trailing slash, creating the source directory name")
|
||||
return scpUploadDirProtocol(filepath.Base(src), w, r, uploadEntries)
|
||||
} else {
|
||||
// Trailing slash, so only upload the contents
|
||||
return uploadEntries()
|
||||
}
|
||||
// Determine the length of the upload content by copying it
|
||||
// into an in-memory buffer. Note that this means what we upload
|
||||
// must fit into memory.
|
||||
log.Println("Copying input data into in-memory buffer so we can get the length")
|
||||
input_memory := new(bytes.Buffer)
|
||||
if _, err = io.Copy(input_memory, input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.scpSession("scp -rvt "+dst, scpFunc)
|
||||
// Start the protocol
|
||||
log.Println("Beginning file upload...")
|
||||
fmt.Fprintln(w, "C0644", input_memory.Len(), target_file)
|
||||
err = checkSCPStatus(stdoutR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
io.Copy(w, input_memory)
|
||||
fmt.Fprint(w, "\x00")
|
||||
err = checkSCPStatus(stdoutR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Close the stdin, which sends an EOF, and then set w to nil so that
|
||||
// our defer func doesn't close it again since that is unsafe with
|
||||
// the Go SSH package.
|
||||
log.Println("Upload complete, closing stdin pipe")
|
||||
w.Close()
|
||||
w = nil
|
||||
|
||||
// Wait for the SCP connection to close, meaning it has consumed all
|
||||
// our data and has completed. Or has errored.
|
||||
log.Println("Waiting for SSH session to complete")
|
||||
err = session.Wait()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*ssh.ExitError); ok {
|
||||
// Otherwise, we have an ExitErorr, meaning we can just read
|
||||
// the exit status
|
||||
log.Printf("non-zero exit status: %d", exitErr.ExitStatus())
|
||||
|
||||
// If we exited with status 127, it means SCP isn't available.
|
||||
// Return a more descriptive error for that.
|
||||
if exitErr.ExitStatus() == 127 {
|
||||
return errors.New(
|
||||
"SCP failed to start. This usually means that SCP is not\n" +
|
||||
"properly installed on the remote system.")
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("scp stderr (length %d): %s", stderr.Len(), stderr.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *comm) Download(string, io.Writer) error {
|
||||
@@ -251,84 +250,6 @@ func (c *comm) reconnect() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *comm) scpSession(scpCommand string, f func(io.Writer, *bufio.Reader) error) error {
|
||||
session, err := c.newSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// Get a pipe to stdin so that we can send data down
|
||||
stdinW, err := session.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We only want to close once, so we nil w after we close it,
|
||||
// and only close in the defer if it hasn't been closed already.
|
||||
defer func() {
|
||||
if stdinW != nil {
|
||||
stdinW.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Get a pipe to stdout so that we can get responses back
|
||||
stdoutPipe, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdoutR := bufio.NewReader(stdoutPipe)
|
||||
|
||||
// Set stderr to a bytes buffer
|
||||
stderr := new(bytes.Buffer)
|
||||
session.Stderr = stderr
|
||||
|
||||
// Start the sink mode on the other side
|
||||
// TODO(mitchellh): There are probably issues with shell escaping the path
|
||||
log.Println("Starting remote scp process: %s", scpCommand)
|
||||
if err := session.Start(scpCommand); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Call our callback that executes in the context of SCP
|
||||
log.Println("Started SCP session, beginning transfers...")
|
||||
if err := f(stdinW, stdoutR); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Close the stdin, which sends an EOF, and then set w to nil so that
|
||||
// our defer func doesn't close it again since that is unsafe with
|
||||
// the Go SSH package.
|
||||
log.Println("SCP session complete, closing stdin pipe.")
|
||||
stdinW.Close()
|
||||
stdinW = nil
|
||||
|
||||
// Wait for the SCP connection to close, meaning it has consumed all
|
||||
// our data and has completed. Or has errored.
|
||||
log.Println("Waiting for SSH session to complete.")
|
||||
err = session.Wait()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*ssh.ExitError); ok {
|
||||
// Otherwise, we have an ExitErorr, meaning we can just read
|
||||
// the exit status
|
||||
log.Printf("non-zero exit status: %d", exitErr.ExitStatus())
|
||||
|
||||
// If we exited with status 127, it means SCP isn't available.
|
||||
// Return a more descriptive error for that.
|
||||
if exitErr.ExitStatus() == 127 {
|
||||
return errors.New(
|
||||
"SCP failed to start. This usually means that SCP is not\n" +
|
||||
"properly installed on the remote system.")
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("scp stderr (length %d): %s", stderr.Len(), stderr.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkSCPStatus checks that a prior command sent to SCP completed
|
||||
// successfully. If it did not complete successfully, an error will
|
||||
// be returned.
|
||||
@@ -350,100 +271,3 @@ func checkSCPStatus(r *bufio.Reader) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func scpUploadFile(dst string, src io.Reader, w io.Writer, r *bufio.Reader) error {
|
||||
// Determine the length of the upload content by copying it
|
||||
// into an in-memory buffer. Note that this means what we upload
|
||||
// must fit into memory.
|
||||
log.Println("Copying input data into in-memory buffer so we can get the length")
|
||||
inputBuf := new(bytes.Buffer)
|
||||
if _, err := io.Copy(inputBuf, src); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the protocol
|
||||
log.Println("Beginning file upload...")
|
||||
fmt.Fprintln(w, "C0644", inputBuf.Len(), dst)
|
||||
err := checkSCPStatus(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(w, inputBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(w, "\x00")
|
||||
err = checkSCPStatus(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func scpUploadDirProtocol(name string, w io.Writer, r *bufio.Reader, f func() error) error {
|
||||
log.Printf("SCP: starting directory upload: %s", name)
|
||||
fmt.Fprintln(w, "D0755 0", name)
|
||||
err := checkSCPStatus(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, "E")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func scpUploadDir(root string, fs []os.FileInfo, w io.Writer, r *bufio.Reader) error {
|
||||
for _, fi := range fs {
|
||||
realPath := filepath.Join(root, fi.Name())
|
||||
|
||||
if !fi.IsDir() {
|
||||
// It is a regular file, just upload it
|
||||
f, err := os.Open(realPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
defer f.Close()
|
||||
return scpUploadFile(fi.Name(), f, w, r)
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// It is a directory, recursively upload
|
||||
err := scpUploadDirProtocol(fi.Name(), w, r, func() error {
|
||||
f, err := os.Open(realPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
entries, err := f.Readdir(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return scpUploadDir(realPath, entries, w, r)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// +build !race
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
@@ -9,9 +10,24 @@ import (
|
||||
// ConnectFunc is a convenience method for returning a function
|
||||
// that just uses net.Dial to communicate with the remote end that
|
||||
// is suitable for use with the SSH communicator configuration.
|
||||
func ConnectFunc(network, addr string) func() (net.Conn, error) {
|
||||
func ConnectFunc(network, addr string, timeout time.Duration) func() (net.Conn, error) {
|
||||
return func() (net.Conn, error) {
|
||||
log.Printf("Opening conn for SSH to %s %s", network, addr)
|
||||
return net.DialTimeout(network, addr, 15*time.Second)
|
||||
timeoutCh := time.After(timeout)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
return nil, errors.New("timeout connecting to remote machine")
|
||||
default:
|
||||
}
|
||||
|
||||
log.Printf("Opening conn for SSH to %s %s", network, addr)
|
||||
nc, err := net.DialTimeout(network, addr, 15*time.Second)
|
||||
if err == nil {
|
||||
return nc, nil
|
||||
}
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,6 @@ type SimpleKeychain struct {
|
||||
// AddPEMKey adds a simple PEM encoded private key to the keychain.
|
||||
func (k *SimpleKeychain) AddPEMKey(key string) (err error) {
|
||||
block, _ := pem.Decode([]byte(key))
|
||||
if block == nil {
|
||||
return errors.New("no block in key")
|
||||
}
|
||||
|
||||
rsakey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -23,7 +23,6 @@ const defaultConfig = `
|
||||
"amazon-chroot": "packer-builder-amazon-chroot",
|
||||
"amazon-instance": "packer-builder-amazon-instance",
|
||||
"digitalocean": "packer-builder-digitalocean",
|
||||
"openstack": "packer-builder-openstack",
|
||||
"virtualbox": "packer-builder-virtualbox",
|
||||
"vmware": "packer-builder-vmware"
|
||||
},
|
||||
@@ -40,7 +39,6 @@ const defaultConfig = `
|
||||
},
|
||||
|
||||
"provisioners": {
|
||||
"chef-solo": "packer-provisioner-chef-solo",
|
||||
"file": "packer-provisioner-file",
|
||||
"shell": "packer-provisioner-shell",
|
||||
"salt-masterless": "packer-provisioner-salt-masterless"
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package packer
|
||||
|
||||
// MockArtifact is an implementation of Artifact that can be used for tests.
|
||||
type MockArtifact struct {
|
||||
IdValue string
|
||||
DestroyCalled bool
|
||||
}
|
||||
|
||||
func (*MockArtifact) BuilderId() string {
|
||||
return "bid"
|
||||
}
|
||||
|
||||
func (*MockArtifact) Files() []string {
|
||||
return []string{"a", "b"}
|
||||
}
|
||||
|
||||
func (a *MockArtifact) Id() string {
|
||||
id := a.IdValue
|
||||
if id == "" {
|
||||
id = "id"
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func (*MockArtifact) String() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
func (a *MockArtifact) Destroy() error {
|
||||
a.DestroyCalled = true
|
||||
return nil
|
||||
}
|
||||
+1
-1
@@ -90,7 +90,7 @@ type coreBuild struct {
|
||||
type coreBuildPostProcessor struct {
|
||||
processor PostProcessor
|
||||
processorType string
|
||||
config map[string]interface{}
|
||||
config interface{}
|
||||
keepInputArtifact bool
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -20,7 +20,7 @@ func testBuild() *coreBuild {
|
||||
},
|
||||
postProcessors: [][]coreBuildPostProcessor{
|
||||
[]coreBuildPostProcessor{
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp"}, "testPP", make(map[string]interface{}), true},
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp"}, "testPP", 42, true},
|
||||
},
|
||||
},
|
||||
variables: make(map[string]string),
|
||||
@@ -66,7 +66,7 @@ func TestBuild_Prepare(t *testing.T) {
|
||||
corePP := build.postProcessors[0][0]
|
||||
pp := corePP.processor.(*TestPostProcessor)
|
||||
assert.True(pp.configCalled, "config should be called")
|
||||
assert.Equal(pp.configVal, []interface{}{make(map[string]interface{}), packerConfig}, "config should have right value")
|
||||
assert.Equal(pp.configVal, []interface{}{42, packerConfig}, "config should have right value")
|
||||
}
|
||||
|
||||
func TestBuild_Prepare_Twice(t *testing.T) {
|
||||
@@ -231,7 +231,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
|
||||
build = testBuild()
|
||||
build.postProcessors = [][]coreBuildPostProcessor{
|
||||
[]coreBuildPostProcessor{
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp"}, "pp", make(map[string]interface{}), false},
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp"}, "pp", 42, false},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -256,10 +256,10 @@ func TestBuild_Run_Artifacts(t *testing.T) {
|
||||
build = testBuild()
|
||||
build.postProcessors = [][]coreBuildPostProcessor{
|
||||
[]coreBuildPostProcessor{
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1"}, "pp", make(map[string]interface{}), false},
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1"}, "pp", 42, false},
|
||||
},
|
||||
[]coreBuildPostProcessor{
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2"}, "pp", make(map[string]interface{}), true},
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2"}, "pp", 42, true},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -284,12 +284,12 @@ func TestBuild_Run_Artifacts(t *testing.T) {
|
||||
build = testBuild()
|
||||
build.postProcessors = [][]coreBuildPostProcessor{
|
||||
[]coreBuildPostProcessor{
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1a"}, "pp", make(map[string]interface{}), false},
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1b"}, "pp", make(map[string]interface{}), true},
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1a"}, "pp", 42, false},
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp1b"}, "pp", 42, true},
|
||||
},
|
||||
[]coreBuildPostProcessor{
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2a"}, "pp", make(map[string]interface{}), false},
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2b"}, "pp", make(map[string]interface{}), false},
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2a"}, "pp", 42, false},
|
||||
coreBuildPostProcessor{&TestPostProcessor{artifactId: "pp2b"}, "pp", 42, false},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
|
||||
build.postProcessors = [][]coreBuildPostProcessor{
|
||||
[]coreBuildPostProcessor{
|
||||
coreBuildPostProcessor{
|
||||
&TestPostProcessor{artifactId: "pp", keep: true}, "pp", make(map[string]interface{}), false,
|
||||
&TestPostProcessor{artifactId: "pp", keep: true}, "pp", 42, false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package packer
|
||||
|
||||
// MockBuilder is an implementation of Builder that can be used for tests.
|
||||
// You can set some fake return values and you can keep track of what
|
||||
// methods were called on the builder. It is fairly basic.
|
||||
type MockBuilder struct {
|
||||
ArtifactId string
|
||||
|
||||
PrepareCalled bool
|
||||
PrepareConfig []interface{}
|
||||
RunCalled bool
|
||||
RunCache Cache
|
||||
RunHook Hook
|
||||
RunUi Ui
|
||||
CancelCalled bool
|
||||
}
|
||||
|
||||
func (tb *MockBuilder) Prepare(config ...interface{}) error {
|
||||
tb.PrepareCalled = true
|
||||
tb.PrepareConfig = config
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tb *MockBuilder) Run(ui Ui, h Hook, c Cache) (Artifact, error) {
|
||||
tb.RunCalled = true
|
||||
tb.RunHook = h
|
||||
tb.RunUi = ui
|
||||
tb.RunCache = c
|
||||
return &MockArtifact{
|
||||
IdValue: tb.ArtifactId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tb *MockBuilder) Cancel() {
|
||||
tb.CancelCalled = true
|
||||
}
|
||||
+6
-21
@@ -33,11 +33,9 @@ type RemoteCmd struct {
|
||||
// Once Exited is true, this will contain the exit code of the process.
|
||||
ExitStatus int
|
||||
|
||||
// Internal fields
|
||||
// Internal locks and such used for safely setting some shared variables
|
||||
l sync.Mutex
|
||||
exitCh chan struct{}
|
||||
|
||||
// This thing is a mutex, lock when making modifications concurrently
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// A Communicator is the interface used to communicate with the machine
|
||||
@@ -59,16 +57,6 @@ type Communicator interface {
|
||||
// it completes.
|
||||
Upload(string, io.Reader) error
|
||||
|
||||
// UploadDir uploads the contents of a directory recursively to
|
||||
// the remote path. It also takes an optional slice of paths to
|
||||
// ignore when uploading.
|
||||
//
|
||||
// The folder name of the source folder should be created unless there
|
||||
// is a trailing slash on the source "/". For example: "/tmp/src" as
|
||||
// the source will create a "src" directory in the destination unless
|
||||
// a trailing slash is added. This is identical behavior to rsync(1).
|
||||
UploadDir(string, string, []string) error
|
||||
|
||||
// Download downloads a file from the machine from the given remote path
|
||||
// with the contents writing to the given writer. This method will
|
||||
// block until it completes.
|
||||
@@ -88,9 +76,6 @@ func (r *RemoteCmd) StartWithUi(c Communicator, ui Ui) error {
|
||||
originalStdout := r.Stdout
|
||||
originalStderr := r.Stderr
|
||||
defer func() {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
r.Stdout = originalStdout
|
||||
r.Stderr = originalStderr
|
||||
}()
|
||||
@@ -156,8 +141,8 @@ OutputLoop:
|
||||
// should be called by communicators who are running a remote command in
|
||||
// order to set that the command is done.
|
||||
func (r *RemoteCmd) SetExited(status int) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
r.l.Lock()
|
||||
defer r.l.Unlock()
|
||||
|
||||
if r.exitCh == nil {
|
||||
r.exitCh = make(chan struct{})
|
||||
@@ -171,11 +156,11 @@ func (r *RemoteCmd) SetExited(status int) {
|
||||
// Wait waits for the remote command to complete.
|
||||
func (r *RemoteCmd) Wait() {
|
||||
// Make sure our condition variable is initialized.
|
||||
r.Lock()
|
||||
r.l.Lock()
|
||||
if r.exitCh == nil {
|
||||
r.exitCh = make(chan struct{})
|
||||
}
|
||||
r.Unlock()
|
||||
r.l.Unlock()
|
||||
|
||||
<-r.exitCh
|
||||
}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package packer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MockCommunicator is a valid Communicator implementation that can be
|
||||
// used for tests.
|
||||
type MockCommunicator struct {
|
||||
StartCalled bool
|
||||
StartCmd *RemoteCmd
|
||||
StartStderr string
|
||||
StartStdout string
|
||||
StartStdin string
|
||||
StartExitStatus int
|
||||
|
||||
UploadCalled bool
|
||||
UploadPath string
|
||||
UploadData string
|
||||
|
||||
UploadDirDst string
|
||||
UploadDirSrc string
|
||||
UploadDirExclude []string
|
||||
|
||||
DownloadCalled bool
|
||||
DownloadPath string
|
||||
DownloadData string
|
||||
}
|
||||
|
||||
func (c *MockCommunicator) Start(rc *RemoteCmd) error {
|
||||
c.StartCalled = true
|
||||
c.StartCmd = rc
|
||||
|
||||
go func() {
|
||||
var wg sync.WaitGroup
|
||||
if rc.Stdout != nil && c.StartStdout != "" {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
rc.Stdout.Write([]byte(c.StartStdout))
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
if rc.Stderr != nil && c.StartStderr != "" {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
rc.Stderr.Write([]byte(c.StartStderr))
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
if rc.Stdin != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var data bytes.Buffer
|
||||
io.Copy(&data, rc.Stdin)
|
||||
c.StartStdin = data.String()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
rc.SetExited(c.StartExitStatus)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MockCommunicator) Upload(path string, r io.Reader) error {
|
||||
c.UploadCalled = true
|
||||
c.UploadPath = path
|
||||
|
||||
var data bytes.Buffer
|
||||
if _, err := io.Copy(&data, r); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.UploadData = data.String()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MockCommunicator) UploadDir(dst string, src string, excl []string) error {
|
||||
c.UploadDirDst = dst
|
||||
c.UploadDirSrc = src
|
||||
c.UploadDirExclude = excl
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MockCommunicator) Download(path string, w io.Writer) error {
|
||||
c.DownloadCalled = true
|
||||
c.DownloadPath = path
|
||||
w.Write([]byte(c.DownloadData))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package packer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMockCommunicator_impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(MockCommunicator)
|
||||
if _, ok := raw.(Communicator); !ok {
|
||||
t.Fatal("should be a communicator")
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,51 @@ package packer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TestCommunicator struct {
|
||||
Stderr io.Reader
|
||||
Stdout io.Reader
|
||||
}
|
||||
|
||||
func (c *TestCommunicator) Start(rc *RemoteCmd) error {
|
||||
go func() {
|
||||
if rc.Stdout != nil && c.Stdout != nil {
|
||||
io.Copy(rc.Stdout, c.Stdout)
|
||||
}
|
||||
|
||||
if rc.Stderr != nil && c.Stderr != nil {
|
||||
io.Copy(rc.Stderr, c.Stderr)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TestCommunicator) Upload(string, io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TestCommunicator) Download(string, io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRemoteCmd_StartWithUi(t *testing.T) {
|
||||
data := "hello\nworld\nthere"
|
||||
|
||||
originalOutput := new(bytes.Buffer)
|
||||
rcOutput := new(bytes.Buffer)
|
||||
uiOutput := new(bytes.Buffer)
|
||||
rcOutput.WriteString(data)
|
||||
|
||||
testComm := &TestCommunicator{
|
||||
Stdout: rcOutput,
|
||||
}
|
||||
|
||||
testComm := new(MockCommunicator)
|
||||
testComm.StartStdout = data
|
||||
testUi := &BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: uiOutput,
|
||||
@@ -25,20 +57,22 @@ func TestRemoteCmd_StartWithUi(t *testing.T) {
|
||||
Stdout: originalOutput,
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
rc.SetExited(0)
|
||||
}()
|
||||
|
||||
err := rc.StartWithUi(testComm, testUi)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
rc.Wait()
|
||||
|
||||
expected := strings.TrimSpace(data)
|
||||
if uiOutput.String() != expected+"\n" {
|
||||
if uiOutput.String() != strings.TrimSpace(data)+"\n" {
|
||||
t.Fatalf("bad output: '%s'", uiOutput.String())
|
||||
}
|
||||
|
||||
if originalOutput.String() != expected {
|
||||
t.Fatalf("bad: %#v", originalOutput.String())
|
||||
if originalOutput.String() != data {
|
||||
t.Fatalf("original is bad: '%s'", originalOutput.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,10 @@ func MultiErrorAppend(err error, errs ...error) *MultiError {
|
||||
err = new(MultiError)
|
||||
}
|
||||
|
||||
if err.Errors == nil {
|
||||
err.Errors = make([]error, 0, len(errs))
|
||||
}
|
||||
|
||||
err.Errors = append(err.Errors, errs...)
|
||||
return err
|
||||
default:
|
||||
|
||||
@@ -40,7 +40,7 @@ func (b *cmdBuilder) Cancel() {
|
||||
func (c *cmdBuilder) checkExit(p interface{}, cb func()) {
|
||||
if c.client.Exited() && cb != nil {
|
||||
cb()
|
||||
} else if p != nil && !Killed {
|
||||
} else if p != nil {
|
||||
log.Panic(p)
|
||||
}
|
||||
}
|
||||
|
||||
+36
-65
@@ -2,6 +2,7 @@ package plugin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
@@ -9,7 +10,6 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/rpc"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -19,10 +19,6 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// If this is true, then the "unexpected EOF" panic will not be
|
||||
// raised throughout the clients.
|
||||
var Killed = false
|
||||
|
||||
// This is a slice of the "managed" clients which are cleaned up when
|
||||
// calling Cleanup
|
||||
var managedClients = make([]*Client, 0, 5)
|
||||
@@ -72,9 +68,6 @@ type ClientConfig struct {
|
||||
//
|
||||
// This must only be called _once_.
|
||||
func CleanupClients() {
|
||||
// Set the killed to true so that we don't get unexpected panics
|
||||
Killed = true
|
||||
|
||||
// Kill all the managed clients in parallel and use a WaitGroup
|
||||
// to wait for them all to finish up.
|
||||
var wg sync.WaitGroup
|
||||
@@ -122,8 +115,6 @@ func NewClient(config *ClientConfig) (c *Client) {
|
||||
|
||||
// Tells whether or not the underlying process has exited.
|
||||
func (c *Client) Exited() bool {
|
||||
c.l.Lock()
|
||||
defer c.l.Unlock()
|
||||
return c.exited
|
||||
}
|
||||
|
||||
@@ -223,7 +214,7 @@ func (c *Client) Start() (address string, err error) {
|
||||
fmt.Sprintf("PACKER_PLUGIN_MAX_PORT=%d", c.config.MaxPort),
|
||||
}
|
||||
|
||||
stdout_r, stdout_w := io.Pipe()
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr_r, stderr_w := io.Pipe()
|
||||
|
||||
cmd := c.config.Cmd
|
||||
@@ -231,7 +222,7 @@ func (c *Client) Start() (address string, err error) {
|
||||
cmd.Env = append(cmd.Env, env...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = stderr_w
|
||||
cmd.Stdout = stdout_w
|
||||
cmd.Stdout = stdout
|
||||
|
||||
log.Printf("Starting plugin: %s %#v", cmd.Path, cmd.Args)
|
||||
err = cmd.Start()
|
||||
@@ -253,12 +244,10 @@ func (c *Client) Start() (address string, err error) {
|
||||
}()
|
||||
|
||||
// Start goroutine to wait for process to exit
|
||||
exitCh := make(chan struct{})
|
||||
go func() {
|
||||
// Make sure we close the write end of our stderr/stdout so
|
||||
// that the readers send EOF properly.
|
||||
// Make sure we close the write end of our stderr listener so
|
||||
// that the log goroutine ends properly.
|
||||
defer stderr_w.Close()
|
||||
defer stdout_w.Close()
|
||||
|
||||
// Wait for the command to end.
|
||||
cmd.Wait()
|
||||
@@ -268,60 +257,46 @@ func (c *Client) Start() (address string, err error) {
|
||||
os.Stderr.Sync()
|
||||
|
||||
// Mark that we exited
|
||||
close(exitCh)
|
||||
|
||||
// Set that we exited, which takes a lock
|
||||
c.l.Lock()
|
||||
defer c.l.Unlock()
|
||||
c.exited = true
|
||||
}()
|
||||
|
||||
// Start goroutine that logs the stderr
|
||||
go c.logStderr(stderr_r)
|
||||
|
||||
// Start a goroutine that is going to be reading the lines
|
||||
// out of stdout
|
||||
linesCh := make(chan []byte)
|
||||
go func() {
|
||||
defer close(linesCh)
|
||||
|
||||
buf := bufio.NewReader(stdout_r)
|
||||
for {
|
||||
line, err := buf.ReadBytes('\n')
|
||||
if line != nil {
|
||||
linesCh <- line
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Make sure after we exit we read the lines from stdout forever
|
||||
// so they dont' block since it is an io.Pipe
|
||||
defer func() {
|
||||
go func() {
|
||||
for _ = range linesCh {
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
// Some channels for the next step
|
||||
timeout := time.After(c.config.StartTimeout)
|
||||
|
||||
// Start looking for the address
|
||||
log.Printf("Waiting for RPC address for: %s", cmd.Path)
|
||||
select {
|
||||
case <-timeout:
|
||||
err = errors.New("timeout while waiting for plugin to start")
|
||||
case <-exitCh:
|
||||
err = errors.New("plugin exited before we could connect")
|
||||
case line := <-linesCh:
|
||||
// Trim the address and reset the err since we were able
|
||||
// to read some sort of address.
|
||||
c.address = strings.TrimSpace(string(line))
|
||||
address = c.address
|
||||
for done := false; !done; {
|
||||
select {
|
||||
case <-timeout:
|
||||
err = errors.New("timeout while waiting for plugin to start")
|
||||
done = true
|
||||
default:
|
||||
}
|
||||
|
||||
if err == nil && c.Exited() {
|
||||
err = errors.New("plugin exited before we could connect")
|
||||
done = true
|
||||
}
|
||||
|
||||
if line, lerr := stdout.ReadBytes('\n'); lerr == nil {
|
||||
// Trim the address and reset the err since we were able
|
||||
// to read some sort of address.
|
||||
c.address = strings.TrimSpace(string(line))
|
||||
address = c.address
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
|
||||
// If error is nil from previously, return now
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Wait a bit
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
return
|
||||
@@ -353,14 +328,10 @@ func (c *Client) rpcClient() (*rpc.Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", address)
|
||||
client, err := rpc.Dial("tcp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure to set keep alive so that the connection doesn't die
|
||||
tcpConn := conn.(*net.TCPConn)
|
||||
tcpConn.SetKeepAlive(true)
|
||||
|
||||
return rpc.NewClient(tcpConn), nil
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (c *cmdCommand) Synopsis() (result string) {
|
||||
func (c *cmdCommand) checkExit(p interface{}, cb func()) {
|
||||
if c.client.Exited() {
|
||||
cb()
|
||||
} else if p != nil && !Killed {
|
||||
} else if p != nil {
|
||||
log.Panic(p)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func (c *cmdHook) Run(name string, ui packer.Ui, comm packer.Communicator, data
|
||||
func (c *cmdHook) checkExit(p interface{}, cb func()) {
|
||||
if c.client.Exited() {
|
||||
cb()
|
||||
} else if p != nil && !Killed {
|
||||
} else if p != nil {
|
||||
log.Panic(p)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,10 +94,8 @@ func swallowInterrupts() {
|
||||
signal.Notify(ch, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
<-ch
|
||||
log.Println("Received interrupt signal. Ignoring.")
|
||||
}
|
||||
<-ch
|
||||
log.Println("Received interrupt signal. Ignoring.")
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ func (c *cmdPostProcessor) PostProcess(ui packer.Ui, a packer.Artifact) (packer.
|
||||
func (c *cmdPostProcessor) checkExit(p interface{}, cb func()) {
|
||||
if c.client.Exited() {
|
||||
cb()
|
||||
} else if p != nil && !Killed {
|
||||
} else if p != nil {
|
||||
log.Panic(p)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func (c *cmdProvisioner) Provision(ui packer.Ui, comm packer.Communicator) error
|
||||
func (c *cmdProvisioner) checkExit(p interface{}, cb func()) {
|
||||
if c.client.Exited() {
|
||||
cb()
|
||||
} else if p != nil && !Killed {
|
||||
} else if p != nil {
|
||||
log.Panic(p)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package rpc
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"net"
|
||||
@@ -61,20 +60,20 @@ func (b *builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
go func() {
|
||||
defer responseL.Close()
|
||||
|
||||
var response BuilderRunResponse
|
||||
defer func() { runResponseCh <- &response }()
|
||||
|
||||
conn, err := responseL.Accept()
|
||||
if err != nil {
|
||||
response.Err = err
|
||||
return
|
||||
log.Panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
decoder := gob.NewDecoder(conn)
|
||||
|
||||
var response BuilderRunResponse
|
||||
if err := decoder.Decode(&response); err != nil {
|
||||
response.Err = fmt.Errorf("Error waiting for Run: %s", err)
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
runResponseCh <- &response
|
||||
}()
|
||||
|
||||
args := &BuilderRunArgs{
|
||||
@@ -105,7 +104,7 @@ func (b *builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||
|
||||
func (b *builder) Cancel() {
|
||||
if err := b.client.Call("Builder.Cancel", new(interface{}), new(interface{})); err != nil {
|
||||
log.Printf("Error cancelling builder: %s", err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +153,7 @@ func (b *BuilderServer) Run(args *BuilderRunArgs, reply *interface{}) error {
|
||||
|
||||
err := responseWriter.Encode(&BuilderRunResponse{responseErr, responseAddress})
|
||||
if err != nil {
|
||||
log.Printf("BuildServer.Run error: %s", err)
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -44,12 +44,6 @@ type CommunicatorUploadArgs struct {
|
||||
ReaderAddress string
|
||||
}
|
||||
|
||||
type CommunicatorUploadDirArgs struct {
|
||||
Dst string
|
||||
Src string
|
||||
Exclude []string
|
||||
}
|
||||
|
||||
func Communicator(client *rpc.Client) *communicator {
|
||||
return &communicator{client}
|
||||
}
|
||||
@@ -84,8 +78,7 @@ func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
|
||||
|
||||
conn, err := responseL.Accept()
|
||||
if err != nil {
|
||||
cmd.SetExited(123)
|
||||
return
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
@@ -94,8 +87,7 @@ func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
|
||||
|
||||
var finished CommandFinished
|
||||
if err := decoder.Decode(&finished); err != nil {
|
||||
cmd.SetExited(123)
|
||||
return
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
cmd.SetExited(finished.ExitStatus)
|
||||
@@ -129,22 +121,6 @@ func (c *communicator) Upload(path string, r io.Reader) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *communicator) UploadDir(dst string, src string, exclude []string) error {
|
||||
args := &CommunicatorUploadDirArgs{
|
||||
Dst: dst,
|
||||
Src: src,
|
||||
Exclude: exclude,
|
||||
}
|
||||
|
||||
var reply error
|
||||
err := c.client.Call("Communicator.UploadDir", args, &reply)
|
||||
if err == nil {
|
||||
err = reply
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *communicator) Download(path string, w io.Writer) (err error) {
|
||||
// We need to create a server that can proxy that data downloaded
|
||||
// into the writer because we can't gob encode a writer directly.
|
||||
@@ -245,10 +221,6 @@ func (c *CommunicatorServer) Upload(args *CommunicatorUploadArgs, reply *interfa
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CommunicatorServer) UploadDir(args *CommunicatorUploadDirArgs, reply *error) error {
|
||||
return c.c.UploadDir(args.Dst, args.Src, args.Exclude)
|
||||
}
|
||||
|
||||
func (c *CommunicatorServer) Download(args *CommunicatorDownloadArgs, reply *interface{}) (err error) {
|
||||
writerC, err := net.Dial("tcp", args.WriterAddress)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,16 +2,52 @@ package rpc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"cgl.tideland.biz/asserts"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"net/rpc"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testCommunicator struct {
|
||||
startCalled bool
|
||||
startCmd *packer.RemoteCmd
|
||||
|
||||
uploadCalled bool
|
||||
uploadPath string
|
||||
uploadData string
|
||||
|
||||
downloadCalled bool
|
||||
downloadPath string
|
||||
}
|
||||
|
||||
func (t *testCommunicator) Start(cmd *packer.RemoteCmd) error {
|
||||
t.startCalled = true
|
||||
t.startCmd = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *testCommunicator) Upload(path string, reader io.Reader) (err error) {
|
||||
t.uploadCalled = true
|
||||
t.uploadPath = path
|
||||
t.uploadData, err = bufio.NewReader(reader).ReadString('\n')
|
||||
return
|
||||
}
|
||||
|
||||
func (t *testCommunicator) Download(path string, writer io.Writer) error {
|
||||
t.downloadCalled = true
|
||||
t.downloadPath = path
|
||||
writer.Write([]byte("download\n"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCommunicatorRPC(t *testing.T) {
|
||||
assert := asserts.NewTestingAsserts(t, true)
|
||||
|
||||
// Create the interface to test
|
||||
c := new(packer.MockCommunicator)
|
||||
c := new(testCommunicator)
|
||||
|
||||
// Start the server
|
||||
server := rpc.NewServer()
|
||||
@@ -20,9 +56,7 @@ func TestCommunicatorRPC(t *testing.T) {
|
||||
|
||||
// Create the client over RPC and run some methods to verify it works
|
||||
client, err := rpc.Dial("tcp", address)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assert.Nil(err, "should be able to connect")
|
||||
remote := Communicator(client)
|
||||
|
||||
// The remote command we'll use
|
||||
@@ -36,95 +70,53 @@ func TestCommunicatorRPC(t *testing.T) {
|
||||
cmd.Stdout = stdout_w
|
||||
cmd.Stderr = stderr_w
|
||||
|
||||
// Send some data on stdout and stderr from the mock
|
||||
c.StartStdout = "outfoo\n"
|
||||
c.StartStderr = "errfoo\n"
|
||||
c.StartExitStatus = 42
|
||||
|
||||
// Test Start
|
||||
err = remote.Start(&cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assert.Nil(err, "should not have an error")
|
||||
|
||||
// Test that we can read from stdout
|
||||
c.startCmd.Stdout.Write([]byte("outfoo\n"))
|
||||
bufOut := bufio.NewReader(stdout_r)
|
||||
data, err := bufOut.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if data != "outfoo\n" {
|
||||
t.Fatalf("bad data: %s", data)
|
||||
}
|
||||
assert.Nil(err, "should have no problem reading stdout")
|
||||
assert.Equal(data, "outfoo\n", "should be correct stdout")
|
||||
|
||||
// Test that we can read from stderr
|
||||
c.startCmd.Stderr.Write([]byte("errfoo\n"))
|
||||
bufErr := bufio.NewReader(stderr_r)
|
||||
data, err = bufErr.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if data != "errfoo\n" {
|
||||
t.Fatalf("bad data: %s", data)
|
||||
}
|
||||
assert.Nil(err, "should have no problem reading stderr")
|
||||
assert.Equal(data, "errfoo\n", "should be correct stderr")
|
||||
|
||||
// Test that we can write to stdin
|
||||
stdin_w.Write([]byte("info\n"))
|
||||
stdin_w.Close()
|
||||
cmd.Wait()
|
||||
if c.StartStdin != "info\n" {
|
||||
t.Fatalf("bad data: %s", data)
|
||||
}
|
||||
stdin_w.Write([]byte("infoo\n"))
|
||||
bufIn := bufio.NewReader(c.startCmd.Stdin)
|
||||
data, err = bufIn.ReadString('\n')
|
||||
assert.Nil(err, "should have no problem reading stdin")
|
||||
assert.Equal(data, "infoo\n", "should be correct stdin")
|
||||
|
||||
// Test that we can get the exit status properly
|
||||
if cmd.ExitStatus != 42 {
|
||||
t.Fatalf("bad exit: %d", cmd.ExitStatus)
|
||||
c.startCmd.SetExited(42)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
if cmd.Exited {
|
||||
assert.Equal(cmd.ExitStatus, 42, "should have proper exit status")
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
assert.True(cmd.Exited, "should have exited")
|
||||
|
||||
// Test that we can upload things
|
||||
uploadR, uploadW := io.Pipe()
|
||||
go func() {
|
||||
defer uploadW.Close()
|
||||
uploadW.Write([]byte("uploadfoo\n"))
|
||||
}()
|
||||
go uploadW.Write([]byte("uploadfoo\n"))
|
||||
err = remote.Upload("foo", uploadR)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !c.UploadCalled {
|
||||
t.Fatal("should have uploaded")
|
||||
}
|
||||
|
||||
if c.UploadPath != "foo" {
|
||||
t.Fatalf("path: %s", c.UploadPath)
|
||||
}
|
||||
|
||||
if c.UploadData != "uploadfoo\n" {
|
||||
t.Fatalf("bad: %s", c.UploadData)
|
||||
}
|
||||
|
||||
// Test that we can upload directories
|
||||
dirDst := "foo"
|
||||
dirSrc := "bar"
|
||||
dirExcl := []string{"foo"}
|
||||
err = remote.UploadDir(dirDst, dirSrc, dirExcl)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.UploadDirDst != dirDst {
|
||||
t.Fatalf("bad: %s", c.UploadDirDst)
|
||||
}
|
||||
|
||||
if c.UploadDirSrc != dirSrc {
|
||||
t.Fatalf("bad: %s", c.UploadDirSrc)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(c.UploadDirExclude, dirExcl) {
|
||||
t.Fatalf("bad: %#v", c.UploadDirExclude)
|
||||
}
|
||||
assert.Nil(err, "should not error")
|
||||
assert.True(c.uploadCalled, "should be called")
|
||||
assert.Equal(c.uploadPath, "foo", "should be correct path")
|
||||
assert.Equal(c.uploadData, "uploadfoo\n", "should have the proper data")
|
||||
|
||||
// Test that we can download things
|
||||
downloadR, downloadW := io.Pipe()
|
||||
@@ -138,34 +130,21 @@ func TestCommunicatorRPC(t *testing.T) {
|
||||
downloadDone <- true
|
||||
}()
|
||||
|
||||
c.DownloadData = "download\n"
|
||||
err = remote.Download("bar", downloadW)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !c.DownloadCalled {
|
||||
t.Fatal("download should be called")
|
||||
}
|
||||
|
||||
if c.DownloadPath != "bar" {
|
||||
t.Fatalf("bad: %s", c.DownloadPath)
|
||||
}
|
||||
assert.Nil(err, "should not error")
|
||||
assert.True(c.downloadCalled, "should have called download")
|
||||
assert.Equal(c.downloadPath, "bar", "should have correct download path")
|
||||
|
||||
<-downloadDone
|
||||
if downloadErr != nil {
|
||||
t.Fatalf("err: %s", downloadErr)
|
||||
}
|
||||
|
||||
if downloadData != "download\n" {
|
||||
t.Fatalf("bad: %s", downloadData)
|
||||
}
|
||||
assert.Nil(downloadErr, "should not error reading download data")
|
||||
assert.Equal(downloadData, "download\n", "should have the proper data")
|
||||
}
|
||||
|
||||
func TestCommunicator_ImplementsCommunicator(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = Communicator(nil)
|
||||
if _, ok := raw.(packer.Communicator); !ok {
|
||||
t.Fatal("should be a Communicator")
|
||||
}
|
||||
assert := asserts.NewTestingAsserts(t, true)
|
||||
|
||||
var r packer.Communicator
|
||||
c := Communicator(nil)
|
||||
|
||||
assert.Implementor(c, &r, "should be a Communicator")
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestProvisionerRPC(t *testing.T) {
|
||||
|
||||
// Test Provision
|
||||
ui := &testUi{}
|
||||
comm := new(packer.MockCommunicator)
|
||||
comm := &testCommunicator{}
|
||||
pClient.Provision(ui, comm)
|
||||
assert.True(p.provCalled, "provision should be called")
|
||||
|
||||
|
||||
+4
-5
@@ -2,7 +2,6 @@ package rpc
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"net/rpc"
|
||||
)
|
||||
|
||||
@@ -31,7 +30,7 @@ func (u *Ui) Ask(query string) (result string, err error) {
|
||||
|
||||
func (u *Ui) Error(message string) {
|
||||
if err := u.client.Call("Ui.Error", message, new(interface{})); err != nil {
|
||||
log.Printf("Error in Ui RPC call: %s", err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,19 +41,19 @@ func (u *Ui) Machine(t string, args ...string) {
|
||||
}
|
||||
|
||||
if err := u.client.Call("Ui.Machine", rpcArgs, new(interface{})); err != nil {
|
||||
log.Printf("Error in Ui RPC call: %s", err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Ui) Message(message string) {
|
||||
if err := u.client.Call("Ui.Message", message, new(interface{})); err != nil {
|
||||
log.Printf("Error in Ui RPC call: %s", err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Ui) Say(message string) {
|
||||
if err := u.client.Call("Ui.Say", message, new(interface{})); err != nil {
|
||||
log.Printf("Error in Ui RPC call: %s", err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-14
@@ -50,7 +50,7 @@ type RawBuilderConfig struct {
|
||||
type RawPostProcessorConfig struct {
|
||||
Type string
|
||||
KeepInputArtifact bool `mapstructure:"keep_input_artifact"`
|
||||
RawConfig map[string]interface{}
|
||||
RawConfig interface{}
|
||||
}
|
||||
|
||||
// RawProvisionerConfig represents a raw, unprocessed provisioner configuration.
|
||||
@@ -162,7 +162,7 @@ func ParseTemplate(data []byte) (t *Template, err error) {
|
||||
// are actually three different formats that the user can use to define
|
||||
// a post-processor.
|
||||
for i, rawV := range rawTpl.PostProcessors {
|
||||
rawPP, err := parsePostProcessor(i, rawV)
|
||||
rawPP, err := parsePostProvisioner(i, rawV)
|
||||
if err != nil {
|
||||
errors = append(errors, err...)
|
||||
continue
|
||||
@@ -189,9 +189,6 @@ func ParseTemplate(data []byte) (t *Template, err error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove the input keep_input_artifact option
|
||||
delete(pp, "keep_input_artifact")
|
||||
|
||||
config.RawConfig = pp
|
||||
}
|
||||
}
|
||||
@@ -221,14 +218,6 @@ func ParseTemplate(data []byte) (t *Template, err error) {
|
||||
// actively reject them as invalid configuration.
|
||||
delete(v, "override")
|
||||
|
||||
// Verify that the override keys exist...
|
||||
for name, _ := range raw.Override {
|
||||
if _, ok := t.Builders[name]; !ok {
|
||||
errors = append(
|
||||
errors, fmt.Errorf("provisioner %d: build '%s' not found for override", i+1, name))
|
||||
}
|
||||
}
|
||||
|
||||
raw.RawConfig = v
|
||||
}
|
||||
|
||||
@@ -271,7 +260,7 @@ func ParseTemplateFile(path string) (*Template, error) {
|
||||
return ParseTemplate(data)
|
||||
}
|
||||
|
||||
func parsePostProcessor(i int, rawV interface{}) (result []map[string]interface{}, errors []error) {
|
||||
func parsePostProvisioner(i int, rawV interface{}) (result []map[string]interface{}, errors []error) {
|
||||
switch v := rawV.(type) {
|
||||
case string:
|
||||
result = []map[string]interface{}{
|
||||
|
||||
@@ -623,11 +623,6 @@ func TestTemplate_Build(t *testing.T) {
|
||||
assert.Equal(len(coreBuild.postProcessors[1]), 2, "should have correct number")
|
||||
assert.False(coreBuild.postProcessors[1][0].keepInputArtifact, "shoule be correct")
|
||||
assert.True(coreBuild.postProcessors[1][1].keepInputArtifact, "shoule be correct")
|
||||
|
||||
config := coreBuild.postProcessors[1][1].config
|
||||
if _, ok := config["keep_input_artifact"]; ok {
|
||||
t.Fatal("should not have keep_input_artifact")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplate_Build_ProvisionerOverride(t *testing.T) {
|
||||
@@ -700,34 +695,6 @@ func TestTemplate_Build_ProvisionerOverride(t *testing.T) {
|
||||
assert.Equal(len(coreBuild.provisioners[0].config), 2, "should have two configs on the provisioner")
|
||||
}
|
||||
|
||||
func TestTemplate_Build_ProvisionerOverrideBad(t *testing.T) {
|
||||
data := `
|
||||
{
|
||||
"builders": [
|
||||
{
|
||||
"name": "test1",
|
||||
"type": "test-builder"
|
||||
}
|
||||
],
|
||||
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "test-prov",
|
||||
|
||||
"override": {
|
||||
"testNope": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
_, err := ParseTemplate([]byte(data))
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateBuild_variables(t *testing.T) {
|
||||
data := `
|
||||
{
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
var GitCommit string
|
||||
|
||||
// The version of packer.
|
||||
const Version = "0.3.5"
|
||||
const Version = "0.3.2"
|
||||
|
||||
// Any pre-release marker for the version. If this is "" (empty string),
|
||||
// then it means that it is a final release. Otherwise, this is the
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/builder/openstack"
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.ServeBuilder(new(openstack.Builder))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package main
|
||||
@@ -1,10 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
"github.com/mitchellh/packer/provisioner/chef-solo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.ServeProvisioner(new(chefsolo.Provisioner))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package main
|
||||
@@ -43,8 +43,20 @@ func (p *AWSBoxPostProcessor) Configure(raws ...interface{}) error {
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
templates := map[string]*string{
|
||||
"output": &p.config.OutputPath,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
var err error
|
||||
*ptr, err = p.config.tpl.Process(*ptr, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
validates := map[string]*string{
|
||||
"output": &p.config.OutputPath,
|
||||
"vagrantfile_template": &p.config.VagrantfileTemplate,
|
||||
}
|
||||
|
||||
@@ -78,11 +90,8 @@ func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact
|
||||
}
|
||||
|
||||
// Compile the output path
|
||||
outputPath, err := p.config.tpl.Process(p.config.OutputPath, &OutputPathTemplate{
|
||||
ArtifactId: artifact.Id(),
|
||||
BuildName: p.config.PackerBuildName,
|
||||
Provider: "aws",
|
||||
})
|
||||
outputPath, err := ProcessOutputPath(p.config.OutputPath,
|
||||
p.config.PackerBuildName, "aws", artifact)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -134,7 +143,7 @@ func (p *AWSBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact
|
||||
}
|
||||
|
||||
// Compress the directory to the given output path
|
||||
if err := DirToBox(outputPath, dir, ui); err != nil {
|
||||
if err := DirToBox(outputPath, dir); err != nil {
|
||||
err = fmt.Errorf("error creating box: %s", err)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ package vagrant
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var builtins = map[string]string{
|
||||
@@ -18,8 +18,6 @@ var builtins = map[string]string{
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
OutputPath string `mapstructure:"output"`
|
||||
}
|
||||
|
||||
@@ -33,29 +31,22 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||
// Store the raw configs for usage later
|
||||
p.rawConfigs = raws
|
||||
|
||||
_, err := common.DecodeConfig(&p.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, raw := range raws {
|
||||
err := mapstructure.Decode(raw, &p.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tpl, err := packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tpl.UserVars = p.config.PackerUserVars
|
||||
|
||||
// Defaults
|
||||
ppExtraConfig := make(map[string]interface{})
|
||||
if p.config.OutputPath == "" {
|
||||
p.config.OutputPath = "packer_{{ .BuildName }}_{{.Provider}}.box"
|
||||
ppExtraConfig["output"] = p.config.OutputPath
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := new(packer.MultiError)
|
||||
if err := tpl.Validate(p.config.OutputPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error parsing output template: %s", err))
|
||||
_, err := template.New("output").Parse(p.config.OutputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("output invalid template: %s", err)
|
||||
}
|
||||
|
||||
// Store the extra configuration for post-processors
|
||||
@@ -64,12 +55,11 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||
// TODO(mitchellh): Properly handle multiple raw configs
|
||||
var mapConfig map[string]interface{}
|
||||
if err := mapstructure.Decode(raws[0], &mapConfig); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Failed to decode config: %s", err))
|
||||
return errs
|
||||
return err
|
||||
}
|
||||
|
||||
p.premade = make(map[string]packer.PostProcessor)
|
||||
errors := make([]error, 0)
|
||||
for k, raw := range mapConfig {
|
||||
pp := keyToPostProcessor(k)
|
||||
if pp == nil {
|
||||
@@ -82,14 +72,14 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||
ppConfigs = append(ppConfigs, raw)
|
||||
|
||||
if err := pp.Configure(ppConfigs...); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
p.premade[k] = pp
|
||||
}
|
||||
|
||||
if len(errs.Errors) > 0 {
|
||||
return errs
|
||||
if len(errors) > 0 {
|
||||
return &packer.MultiError{errors}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2,14 +2,15 @@ package vagrant
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// OutputPathTemplate is the structure that is availalable within the
|
||||
@@ -44,7 +45,7 @@ func CopyContents(dst, src string) error {
|
||||
// DirToBox takes the directory and compresses it into a Vagrant-compatible
|
||||
// box. This function does not perform checks to verify that dir is
|
||||
// actually a proper box. This is an expected precondition.
|
||||
func DirToBox(dst, dir string, ui packer.Ui) error {
|
||||
func DirToBox(dst, dir string) error {
|
||||
log.Printf("Turning dir into box: %s => %s", dir, dst)
|
||||
dstF, err := os.Create(dst)
|
||||
if err != nil {
|
||||
@@ -92,10 +93,6 @@ func DirToBox(dst, dir string, ui packer.Ui) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if ui != nil {
|
||||
ui.Message(fmt.Sprintf("Compressing: %s", header.Name))
|
||||
}
|
||||
|
||||
if err := tarWriter.WriteHeader(header); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -111,6 +108,26 @@ func DirToBox(dst, dir string, ui packer.Ui) error {
|
||||
return filepath.Walk(dir, tarWalk)
|
||||
}
|
||||
|
||||
// ProcessOutputPath takes an output path template and executes it,
|
||||
// replacing variables with their respective values.
|
||||
func ProcessOutputPath(path string, buildName string, provider string, artifact packer.Artifact) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
tplData := &OutputPathTemplate{
|
||||
ArtifactId: artifact.Id(),
|
||||
BuildName: buildName,
|
||||
Provider: provider,
|
||||
}
|
||||
|
||||
t, err := template.New("output").Parse(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = t.Execute(&buf, tplData)
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
// WriteMetadata writes the "metadata.json" file for a Vagrant box.
|
||||
func WriteMetadata(dir string, contents interface{}) error {
|
||||
f, err := os.Create(filepath.Join(dir, "metadata.json"))
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type VBoxBoxConfig struct {
|
||||
@@ -46,8 +45,20 @@ func (p *VBoxBoxPostProcessor) Configure(raws ...interface{}) error {
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
templates := map[string]*string{
|
||||
"output": &p.config.OutputPath,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
var err error
|
||||
*ptr, err = p.config.tpl.Process(*ptr, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
validates := map[string]*string{
|
||||
"output": &p.config.OutputPath,
|
||||
"vagrantfile_template": &p.config.VagrantfileTemplate,
|
||||
}
|
||||
|
||||
@@ -67,13 +78,15 @@ func (p *VBoxBoxPostProcessor) Configure(raws ...interface{}) error {
|
||||
|
||||
func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
var err error
|
||||
tplData := &VBoxVagrantfileTemplate{}
|
||||
tplData.BaseMacAddress, err = p.findBaseMacAddress(artifact)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Compile the output path
|
||||
outputPath, err := p.config.tpl.Process(p.config.OutputPath, &OutputPathTemplate{
|
||||
ArtifactId: artifact.Id(),
|
||||
BuildName: p.config.PackerBuildName,
|
||||
Provider: "virtualbox",
|
||||
})
|
||||
outputPath, err := ProcessOutputPath(p.config.OutputPath,
|
||||
p.config.PackerBuildName, "virtualbox", artifact)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -87,31 +100,15 @@ func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifac
|
||||
|
||||
// Copy all of the original contents into the temporary directory
|
||||
for _, path := range artifact.Files() {
|
||||
ui.Message(fmt.Sprintf("Copying: %s", path))
|
||||
|
||||
// We treat OVA files specially, we unpack those into the temporary
|
||||
// directory so we can get the resulting disk and OVF.
|
||||
if extension := filepath.Ext(path); extension == ".ova" {
|
||||
ui.Message(fmt.Sprintf("Unpacking OVA: %s", path))
|
||||
if err := DecompressOva(dir, filepath.Base(path)); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
} else {
|
||||
ui.Message(fmt.Sprintf("Copying: %s", path))
|
||||
dstPath := filepath.Join(dir, filepath.Base(path))
|
||||
if err := CopyContents(dstPath, path); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
dstPath := filepath.Join(dir, filepath.Base(path))
|
||||
if err := CopyContents(dstPath, path); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Create the Vagrantfile from the template
|
||||
tplData := &VBoxVagrantfileTemplate{}
|
||||
tplData.BaseMacAddress, err = p.findBaseMacAddress(dir)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
vf, err := os.Create(filepath.Join(dir, "Vagrantfile"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@@ -155,47 +152,26 @@ func (p *VBoxBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifac
|
||||
|
||||
// Compress the directory to the given output path
|
||||
ui.Message(fmt.Sprintf("Compressing box..."))
|
||||
if err := DirToBox(outputPath, dir, ui); err != nil {
|
||||
if err := DirToBox(outputPath, dir); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return NewArtifact("virtualbox", outputPath), false, nil
|
||||
}
|
||||
|
||||
func (p *VBoxBoxPostProcessor) findOvf(dir string) (string, error) {
|
||||
log.Println("Looking for OVF in artifact...")
|
||||
file_matches, err := filepath.Glob(filepath.Join(dir, "*.ovf"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(file_matches) > 1 {
|
||||
return "", errors.New("More than one OVF file in VirtualBox artifact.")
|
||||
}
|
||||
|
||||
if len(file_matches) < 1 {
|
||||
return "", errors.New("ovf file couldn't be found")
|
||||
}
|
||||
|
||||
return file_matches[0], err
|
||||
}
|
||||
|
||||
func (p *VBoxBoxPostProcessor) renameOVF(dir string) error {
|
||||
log.Println("Looking for OVF to rename...")
|
||||
ovf, err := p.findOvf(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Renaming: '%s' => box.ovf", ovf)
|
||||
return os.Rename(ovf, filepath.Join(dir, "box.ovf"))
|
||||
}
|
||||
|
||||
func (p *VBoxBoxPostProcessor) findBaseMacAddress(dir string) (string, error) {
|
||||
func (p *VBoxBoxPostProcessor) findBaseMacAddress(a packer.Artifact) (string, error) {
|
||||
log.Println("Looking for OVF for base mac address...")
|
||||
ovf, err := p.findOvf(dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
var ovf string
|
||||
for _, f := range a.Files() {
|
||||
if strings.HasSuffix(f, ".ovf") {
|
||||
log.Printf("OVF found: %s", f)
|
||||
ovf = f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ovf == "" {
|
||||
return "", errors.New("ovf file couldn't be found")
|
||||
}
|
||||
|
||||
f, err := os.Open(ovf)
|
||||
@@ -219,51 +195,19 @@ func (p *VBoxBoxPostProcessor) findBaseMacAddress(dir string) (string, error) {
|
||||
return string(matches[1]), nil
|
||||
}
|
||||
|
||||
// DecompressOva takes an ova file and decompresses it into the target
|
||||
// directory.
|
||||
func DecompressOva(dir, src string) error {
|
||||
log.Printf("Turning ova to dir: %s => %s", src, dir)
|
||||
srcF, err := os.Open(src)
|
||||
func (p *VBoxBoxPostProcessor) renameOVF(dir string) error {
|
||||
log.Println("Looking for OVF to rename...")
|
||||
matches, err := filepath.Glob(filepath.Join(dir, "*.ovf"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcF.Close()
|
||||
|
||||
tarReader := tar.NewReader(srcF)
|
||||
for {
|
||||
hdr, err := tarReader.Next()
|
||||
if hdr == nil || err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
info := hdr.FileInfo()
|
||||
|
||||
// Shouldn't be any directories, skip them
|
||||
if info.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
// We wrap this in an anonymous function so that the defers
|
||||
// inside are handled more quickly so we can give up file handles.
|
||||
err = func() error {
|
||||
path := filepath.Join(dir, info.Name())
|
||||
output, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.Close()
|
||||
|
||||
os.Chmod(path, info.Mode())
|
||||
os.Chtimes(path, hdr.AccessTime, hdr.ModTime)
|
||||
_, err = io.Copy(output, tarReader)
|
||||
return err
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(matches) > 1 {
|
||||
return errors.New("More than one OVF file in VirtualBox artifact.")
|
||||
}
|
||||
|
||||
return nil
|
||||
log.Printf("Renaming: '%s' => box.ovf", matches[0])
|
||||
return os.Rename(matches[0], filepath.Join(dir, "box.ovf"))
|
||||
}
|
||||
|
||||
var defaultVBoxVagrantfile = `
|
||||
|
||||
@@ -37,8 +37,20 @@ func (p *VMwareBoxPostProcessor) Configure(raws ...interface{}) error {
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
templates := map[string]*string{
|
||||
"output": &p.config.OutputPath,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
var err error
|
||||
*ptr, err = p.config.tpl.Process(*ptr, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
validates := map[string]*string{
|
||||
"output": &p.config.OutputPath,
|
||||
"vagrantfile_template": &p.config.VagrantfileTemplate,
|
||||
}
|
||||
|
||||
@@ -58,11 +70,8 @@ func (p *VMwareBoxPostProcessor) Configure(raws ...interface{}) error {
|
||||
|
||||
func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
// Compile the output path
|
||||
outputPath, err := p.config.tpl.Process(p.config.OutputPath, &OutputPathTemplate{
|
||||
ArtifactId: artifact.Id(),
|
||||
BuildName: p.config.PackerBuildName,
|
||||
Provider: "vmware",
|
||||
})
|
||||
outputPath, err := ProcessOutputPath(p.config.OutputPath,
|
||||
p.config.PackerBuildName, "vmware", artifact)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -119,7 +128,7 @@ func (p *VMwareBoxPostProcessor) PostProcess(ui packer.Ui, artifact packer.Artif
|
||||
|
||||
// Compress the directory to the given output path
|
||||
ui.Message(fmt.Sprintf("Compressing box..."))
|
||||
if err := DirToBox(outputPath, dir, ui); err != nil {
|
||||
if err := DirToBox(outputPath, dir); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,318 +0,0 @@
|
||||
// This package implements a provisioner for Packer that uses
|
||||
// Chef to provision the remote machine, specifically with chef-solo (that is,
|
||||
// without a Chef server).
|
||||
package chefsolo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
CookbookPaths []string `mapstructure:"cookbook_paths"`
|
||||
ExecuteCommand string `mapstructure:"execute_command"`
|
||||
InstallCommand string `mapstructure:"install_command"`
|
||||
RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths"`
|
||||
Json map[string]interface{}
|
||||
PreventSudo bool `mapstructure:"prevent_sudo"`
|
||||
RunList []string `mapstructure:"run_list"`
|
||||
SkipInstall bool `mapstructure:"skip_install"`
|
||||
StagingDir string `mapstructure:"staging_directory"`
|
||||
|
||||
tpl *packer.ConfigTemplate
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
type ConfigTemplate struct {
|
||||
CookbookPaths string
|
||||
}
|
||||
|
||||
type ExecuteTemplate struct {
|
||||
ConfigPath string
|
||||
JsonPath string
|
||||
Sudo bool
|
||||
}
|
||||
|
||||
type InstallChefTemplate struct {
|
||||
Sudo bool
|
||||
}
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
md, err := common.DecodeConfig(&p.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.config.tpl.UserVars = p.config.PackerUserVars
|
||||
|
||||
if p.config.ExecuteCommand == "" {
|
||||
p.config.ExecuteCommand = "{{if .Sudo}}sudo {{end}}chef-solo --no-color -c {{.ConfigPath}} -j {{.JsonPath}}"
|
||||
}
|
||||
|
||||
if p.config.InstallCommand == "" {
|
||||
p.config.InstallCommand = "curl -L https://www.opscode.com/chef/install.sh | {{if .Sudo}}sudo {{end}}bash"
|
||||
}
|
||||
|
||||
if p.config.RunList == nil {
|
||||
p.config.RunList = make([]string, 0)
|
||||
}
|
||||
|
||||
if p.config.StagingDir == "" {
|
||||
p.config.StagingDir = "/tmp/packer-chef-solo"
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
templates := map[string]*string{
|
||||
"staging_dir": &p.config.StagingDir,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
var err error
|
||||
*ptr, err = p.config.tpl.Process(*ptr, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
sliceTemplates := map[string][]string{
|
||||
"cookbook_paths": p.config.CookbookPaths,
|
||||
"remote_cookbook_paths": p.config.RemoteCookbookPaths,
|
||||
"run_list": p.config.RunList,
|
||||
}
|
||||
|
||||
for n, slice := range sliceTemplates {
|
||||
for i, elem := range slice {
|
||||
var err error
|
||||
slice[i], err = p.config.tpl.Process(elem, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validates := map[string]*string{
|
||||
"execute_command": &p.config.ExecuteCommand,
|
||||
"install_command": &p.config.InstallCommand,
|
||||
}
|
||||
|
||||
for n, ptr := range validates {
|
||||
if err := p.config.tpl.Validate(*ptr); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error parsing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range p.config.CookbookPaths {
|
||||
pFileInfo, err := os.Stat(path)
|
||||
|
||||
if err != nil || !pFileInfo.IsDir() {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Bad cookbook path '%s': %s", path, err))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
if !p.config.SkipInstall {
|
||||
if err := p.installChef(ui, comm); err != nil {
|
||||
return fmt.Errorf("Error installing Chef: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
|
||||
return fmt.Errorf("Error creating staging directory: %s", err)
|
||||
}
|
||||
|
||||
cookbookPaths := make([]string, 0, len(p.config.CookbookPaths))
|
||||
for i, path := range p.config.CookbookPaths {
|
||||
targetPath := fmt.Sprintf("%s/cookbooks-%d", p.config.StagingDir, i)
|
||||
if err := p.uploadDirectory(ui, comm, targetPath, path); err != nil {
|
||||
return fmt.Errorf("Error uploading cookbooks: %s", err)
|
||||
}
|
||||
|
||||
cookbookPaths = append(cookbookPaths, targetPath)
|
||||
}
|
||||
|
||||
configPath, err := p.createConfig(ui, comm, cookbookPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating Chef config file: %s", err)
|
||||
}
|
||||
|
||||
jsonPath, err := p.createJson(ui, comm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating JSON attributes: %s", err)
|
||||
}
|
||||
|
||||
if err := p.executeChef(ui, comm, configPath, jsonPath); err != nil {
|
||||
return fmt.Errorf("Error executing Chef: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
|
||||
if err := p.createDir(ui, comm, dst); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure there is a trailing "/" so that the directory isn't
|
||||
// created on the other side.
|
||||
if src[len(src)-1] != '/' {
|
||||
src = src + "/"
|
||||
}
|
||||
|
||||
return comm.UploadDir(dst, src, nil)
|
||||
}
|
||||
|
||||
func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string) (string, error) {
|
||||
ui.Message("Creating configuration file 'solo.rb'")
|
||||
|
||||
cookbook_paths := make([]string, len(p.config.RemoteCookbookPaths)+len(localCookbooks))
|
||||
for i, path := range p.config.RemoteCookbookPaths {
|
||||
cookbook_paths[i] = fmt.Sprintf(`"%s"`, path)
|
||||
}
|
||||
|
||||
for i, path := range localCookbooks {
|
||||
i = len(p.config.RemoteCookbookPaths) + i
|
||||
cookbook_paths[i] = fmt.Sprintf(`"%s"`, path)
|
||||
}
|
||||
|
||||
configString, err := p.config.tpl.Process(DefaultConfigTemplate, &ConfigTemplate{
|
||||
CookbookPaths: strings.Join(cookbook_paths, ","),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
remotePath := filepath.Join(p.config.StagingDir, "solo.rb")
|
||||
if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString))); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return remotePath, nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) createJson(ui packer.Ui, comm packer.Communicator) (string, error) {
|
||||
ui.Message("Creating JSON attribute file")
|
||||
|
||||
jsonData := make(map[string]interface{})
|
||||
|
||||
// Copy the configured JSON
|
||||
for k, v := range p.config.Json {
|
||||
jsonData[k] = v
|
||||
}
|
||||
|
||||
// Set the run list if it was specified
|
||||
if len(p.config.RunList) > 0 {
|
||||
jsonData["run_list"] = p.config.RunList
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(jsonData, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Upload the bytes
|
||||
remotePath := filepath.Join(p.config.StagingDir, "node.json")
|
||||
if err := comm.Upload(remotePath, bytes.NewReader(jsonBytes)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return remotePath, nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
|
||||
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
|
||||
cmd := &packer.RemoteCmd{
|
||||
Command: fmt.Sprintf("mkdir -p '%s'", dir),
|
||||
}
|
||||
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.ExitStatus != 0 {
|
||||
return fmt.Errorf("Non-zero exit status.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config string, json string) error {
|
||||
command, err := p.config.tpl.Process(p.config.ExecuteCommand, &ExecuteTemplate{
|
||||
ConfigPath: config,
|
||||
JsonPath: json,
|
||||
Sudo: !p.config.PreventSudo,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Executing Chef: %s", command))
|
||||
|
||||
cmd := &packer.RemoteCmd{
|
||||
Command: command,
|
||||
}
|
||||
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.ExitStatus != 0 {
|
||||
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error {
|
||||
ui.Message("Installing Chef...")
|
||||
|
||||
command, err := p.config.tpl.Process(p.config.InstallCommand, &InstallChefTemplate{
|
||||
Sudo: !p.config.PreventSudo,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &packer.RemoteCmd{Command: command}
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.ExitStatus != 0 {
|
||||
return fmt.Errorf(
|
||||
"Install script exited with non-zero exit status %d", cmd.ExitStatus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var DefaultConfigTemplate = `
|
||||
cookbook_path [{{.CookbookPaths}}]
|
||||
`
|
||||
@@ -1,53 +0,0 @@
|
||||
package chefsolo
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
func TestProvisioner_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Provisioner{}
|
||||
if _, ok := raw.(packer.Provisioner); !ok {
|
||||
t.Fatalf("must be a Provisioner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_cookbookPaths(t *testing.T) {
|
||||
var p Provisioner
|
||||
|
||||
path1, err := ioutil.TempDir("", "cookbooks_one")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
path2, err := ioutil.TempDir("", "cookbooks_two")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
defer os.Remove(path1)
|
||||
defer os.Remove(path2)
|
||||
|
||||
config := testConfig()
|
||||
config["cookbook_paths"] = []string{path1, path2}
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(p.config.CookbookPaths) != 2 {
|
||||
t.Fatalf("unexpected: %#v", p.config.CookbookPaths)
|
||||
}
|
||||
|
||||
if p.config.CookbookPaths[0] != path1 || p.config.CookbookPaths[1] != path2 {
|
||||
t.Fatalf("unexpected: %#v", p.config.CookbookPaths)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package file
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -74,6 +75,26 @@ func TestProvisionerPrepare_EmptyDestination(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type stubUploadCommunicator struct {
|
||||
dest string
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (suc *stubUploadCommunicator) Download(src string, data io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suc *stubUploadCommunicator) Upload(dest string, data io.Reader) error {
|
||||
var err error
|
||||
suc.dest = dest
|
||||
suc.data, err = ioutil.ReadAll(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (suc *stubUploadCommunicator) Start(cmd *packer.RemoteCmd) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type stubUi struct {
|
||||
sayMessages string
|
||||
}
|
||||
@@ -117,7 +138,7 @@ func TestProvisionerProvision_SendsFile(t *testing.T) {
|
||||
}
|
||||
|
||||
ui := &stubUi{}
|
||||
comm := &packer.MockCommunicator{}
|
||||
comm := &stubUploadCommunicator{}
|
||||
err = p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatalf("should successfully provision: %s", err)
|
||||
@@ -131,11 +152,11 @@ func TestProvisionerProvision_SendsFile(t *testing.T) {
|
||||
t.Fatalf("should print destination filename")
|
||||
}
|
||||
|
||||
if comm.UploadPath != "something" {
|
||||
if comm.dest != "something" {
|
||||
t.Fatalf("should upload to configured destination")
|
||||
}
|
||||
|
||||
if comm.UploadData != "hello" {
|
||||
if string(comm.data) != "hello" {
|
||||
t.Fatalf("should upload with source file's data")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,15 +21,9 @@ type Config struct {
|
||||
SkipBootstrap bool `mapstructure:"skip_bootstrap"`
|
||||
BootstrapArgs string `mapstructure:"bootstrap_args"`
|
||||
|
||||
// Local path to the minion config
|
||||
MinionConfig string `mapstructure:"minion_config"`
|
||||
|
||||
// Local path to the salt state tree
|
||||
LocalStateTree string `mapstructure:"local_state_tree"`
|
||||
|
||||
// Local path to the salt pillar roots
|
||||
LocalPillarRoots string `mapstructure:"local_pillar_roots"`
|
||||
|
||||
// Where files will be copied before moving to the /srv/salt directory
|
||||
TempConfigDir string `mapstructure:"temp_config_dir"`
|
||||
|
||||
@@ -60,11 +54,9 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
templates := map[string]*string{
|
||||
"bootstrap_args": &p.config.BootstrapArgs,
|
||||
"minion_config": &p.config.MinionConfig,
|
||||
"local_state_tree": &p.config.LocalStateTree,
|
||||
"local_pillar_roots": &p.config.LocalPillarRoots,
|
||||
"temp_config_dir": &p.config.TempConfigDir,
|
||||
"bootstrap_args": &p.config.BootstrapArgs,
|
||||
"local_state_tree": &p.config.LocalStateTree,
|
||||
"temp_config_dir": &p.config.TempConfigDir,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
@@ -83,20 +75,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.LocalPillarRoots != "" {
|
||||
if _, err := os.Stat(p.config.LocalPillarRoots); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
errors.New("local_pillar_roots must exist and be accessible"))
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.MinionConfig != "" {
|
||||
if _, err := os.Stat(p.config.MinionConfig); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
errors.New("minion_config must exist and be accessible"))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
@@ -128,63 +106,19 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
return fmt.Errorf("Error creating remote salt state directory: %s", err)
|
||||
}
|
||||
|
||||
if p.config.MinionConfig != "" {
|
||||
ui.Message(fmt.Sprintf("Uploading minion config: %s", p.config.MinionConfig))
|
||||
if err = uploadMinionConfig(comm, fmt.Sprintf("%s/minion", p.config.TempConfigDir), p.config.MinionConfig); err != nil {
|
||||
return fmt.Errorf("Error uploading local minion config file to remote: %s", err)
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Moving %s/minion to /etc/salt/minion", p.config.TempConfigDir))
|
||||
cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/minion /etc/salt/minion", p.config.TempConfigDir)}
|
||||
if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unable to move %s/minion to /etc/salt/minion: %d", p.config.TempConfigDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Uploading local state tree: %s", p.config.LocalStateTree))
|
||||
if err = UploadLocalDirectory(p.config.LocalStateTree, fmt.Sprintf("%s/states", p.config.TempConfigDir), comm, ui); err != nil {
|
||||
if err = UploadLocalDirectory(p.config.LocalStateTree, p.config.TempConfigDir, comm, ui); err != nil {
|
||||
return fmt.Errorf("Error uploading local state tree to remote: %s", err)
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Moving %s to /srv/salt", p.config.TempConfigDir))
|
||||
cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/states /srv/salt/", p.config.TempConfigDir)}
|
||||
cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s /srv/salt", p.config.TempConfigDir)}
|
||||
if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unable to move %s/states to /srv/salt: %d", p.config.TempConfigDir, err)
|
||||
}
|
||||
|
||||
if p.config.LocalPillarRoots != "" {
|
||||
ui.Message(fmt.Sprintf("Creating remote pillar directory: %s/pillar", p.config.TempConfigDir))
|
||||
cmd := &packer.RemoteCmd{Command: fmt.Sprintf("mkdir -p %s/pillar", p.config.TempConfigDir)}
|
||||
if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
|
||||
return fmt.Errorf("Error creating remote pillar directory: %s", err)
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Uploading local pillar roots: %s", p.config.LocalPillarRoots))
|
||||
if err = UploadLocalDirectory(p.config.LocalPillarRoots, fmt.Sprintf("%s/pillar", p.config.TempConfigDir), comm, ui); err != nil {
|
||||
return fmt.Errorf("Error uploading local pillar roots to remote: %s", err)
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Moving %s/pillar to /srv/pillar", p.config.TempConfigDir))
|
||||
cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/pillar /srv/pillar", p.config.TempConfigDir)}
|
||||
if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unable to move %s/pillar to /srv/pillar: %d", p.config.TempConfigDir, err)
|
||||
}
|
||||
return fmt.Errorf("Unable to move %s to /srv/salt: %d", p.config.TempConfigDir, err)
|
||||
}
|
||||
|
||||
ui.Message("Running highstate")
|
||||
@@ -237,17 +171,3 @@ func UploadLocalDirectory(localDir string, remoteDir string, comm packer.Communi
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func uploadMinionConfig(comm packer.Communicator, dst string, src string) error {
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error opening minion config: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err = comm.Upload(dst, f); err != nil {
|
||||
return fmt.Errorf("Error uploading minion config: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package saltmasterless
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
@@ -47,29 +46,6 @@ func TestProvisionerPrepare_InvalidKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_MinionConfig(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["minion_config"] = "/i/dont/exist/i/think"
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "minion")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["minion_config"] = tf.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_LocalStateTree(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
@@ -86,20 +62,3 @@ func TestProvisionerPrepare_LocalStateTree(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_LocalPillarRoots(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
config["local_pillar_roots"] = "/i/dont/exist/i/think"
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["local_pillar_roots"] = os.TempDir()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +236,15 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
log.Printf("Uploading %s => %s", path, p.config.RemotePath)
|
||||
err = comm.Upload(p.config.RemotePath, f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error uploading shell script: %s", err)
|
||||
}
|
||||
|
||||
// Close the original file since we copied it
|
||||
f.Close()
|
||||
|
||||
// Flatten the environment variables
|
||||
flattendVars := strings.Join(envVars, " ")
|
||||
|
||||
@@ -248,31 +257,29 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
return fmt.Errorf("Error processing command: %s", err)
|
||||
}
|
||||
|
||||
// Upload the file and run the command. Do this in the context of
|
||||
// a single retryable function so that we don't end up with
|
||||
// the case that the upload succeeded, a restart is initiated,
|
||||
// and then the command is executed but the file doesn't exist
|
||||
// any longer.
|
||||
var cmd *packer.RemoteCmd
|
||||
err = p.retryable(func() error {
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
cmd := &packer.RemoteCmd{Command: command}
|
||||
startTimeout := time.After(p.config.startRetryTimeout)
|
||||
log.Printf("Executing command: %s", cmd.Command)
|
||||
for {
|
||||
if err := cmd.StartWithUi(comm, ui); err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Create an error and log it
|
||||
err = fmt.Errorf("Error executing command: %s", err)
|
||||
log.Printf(err.Error())
|
||||
|
||||
// Check if we timed out, otherwise we retry. It is safe to
|
||||
// retry since the only error case above is if the command
|
||||
// failed to START.
|
||||
select {
|
||||
case <-startTimeout:
|
||||
return err
|
||||
default:
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
if err := comm.Upload(p.config.RemotePath, f); err != nil {
|
||||
return fmt.Errorf("Error uploading script: %s", err)
|
||||
}
|
||||
|
||||
cmd = &packer.RemoteCmd{Command: command}
|
||||
return cmd.StartWithUi(comm, ui)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Close the original file since we copied it
|
||||
f.Close()
|
||||
|
||||
if cmd.ExitStatus != 0 {
|
||||
return fmt.Errorf("Script exited with non-zero exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
@@ -280,29 +287,3 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// retryable will retry the given function over and over until a
|
||||
// non-error is returned.
|
||||
func (p *Provisioner) retryable(f func() error) error {
|
||||
startTimeout := time.After(p.config.startRetryTimeout)
|
||||
for {
|
||||
var err error
|
||||
if err = f(); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create an error and log it
|
||||
err = fmt.Errorf("Retryable error: %s", err)
|
||||
log.Printf(err.Error())
|
||||
|
||||
// Check if we timed out, otherwise we retry. It is safe to
|
||||
// retry since the only error case above is if the command
|
||||
// failed to START.
|
||||
select {
|
||||
case <-startTimeout:
|
||||
return err
|
||||
default:
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,19 +20,9 @@ cd $DIR
|
||||
GIT_COMMIT=$(git rev-parse HEAD)
|
||||
GIT_DIRTY=$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||
|
||||
# If we're building a race-enabled build, then set that up.
|
||||
if [ ! -z $PACKER_RACE ]; then
|
||||
echo -e "${OK_COLOR}--> Building with race detection enabled${NO_COLOR}"
|
||||
PACKER_RACE="-race"
|
||||
fi
|
||||
|
||||
echo -e "${OK_COLOR}--> Installing dependencies to speed up builds...${NO_COLOR}"
|
||||
go get ./...
|
||||
|
||||
# Compile the main Packer app
|
||||
echo -e "${OK_COLOR}--> Compiling Packer${NO_COLOR}"
|
||||
go build \
|
||||
${PACKER_RACE} \
|
||||
-ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
|
||||
-v \
|
||||
-o bin/packer .
|
||||
@@ -42,7 +32,6 @@ for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do
|
||||
PLUGIN_NAME=$(basename ${PLUGIN})
|
||||
echo -e "${OK_COLOR}--> Compiling Plugin: ${PLUGIN_NAME}${NO_COLOR}"
|
||||
go build \
|
||||
${PACKER_RACE} \
|
||||
-ldflags "-X github.com/mitchellh/packer/packer.GitCommit ${GIT_COMMIT}${GIT_DIRTY}" \
|
||||
-v \
|
||||
-o bin/packer-${PLUGIN_NAME} ${PLUGIN}
|
||||
|
||||
+8
-1
@@ -60,11 +60,15 @@ waitAll() {
|
||||
trap "kill 0" SIGINT SIGTERM EXIT
|
||||
|
||||
# Build our root project
|
||||
xc
|
||||
xc &
|
||||
|
||||
# Build all the plugins
|
||||
for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do
|
||||
PLUGIN_NAME=$(basename ${PLUGIN})
|
||||
(
|
||||
pushd ${PLUGIN}
|
||||
xc
|
||||
popd
|
||||
find ./pkg \
|
||||
-type f \
|
||||
-name ${PLUGIN_NAME} \
|
||||
@@ -73,8 +77,11 @@ for PLUGIN in $(find ./plugin -mindepth 1 -maxdepth 1 -type d); do
|
||||
-type f \
|
||||
-name ${PLUGIN_NAME}.exe \
|
||||
-execdir mv ${PLUGIN_NAME}.exe packer-${PLUGIN_NAME}.exe ';'
|
||||
) &
|
||||
done
|
||||
|
||||
waitAll
|
||||
|
||||
# Zip all the packages
|
||||
mkdir -p ./pkg/${VERSIONDIR}/dist
|
||||
for PLATFORM in $(find ./pkg/${VERSIONDIR} -mindepth 1 -maxdepth 1 -type d); do
|
||||
|
||||
@@ -20,16 +20,13 @@ func setupSignalHandlers(env packer.Environment) {
|
||||
<-ch
|
||||
log.Println("First interrupt. Ignoring to allow plugins to clean up.")
|
||||
|
||||
env.Ui().Error("Interrupt signal received. Cleaning up...")
|
||||
|
||||
// Second interrupt. Go down hard.
|
||||
<-ch
|
||||
log.Println("Second interrupt. Exiting now.")
|
||||
|
||||
env.Ui().Error("Interrupt signal received twice. Forcefully exiting now.")
|
||||
|
||||
// Force kill all the plugins, but mark that we're killing them
|
||||
// first so that we don't get panics everywhere.
|
||||
// Force kill all the plugins
|
||||
plugin.CleanupClients()
|
||||
os.Exit(1)
|
||||
}()
|
||||
|
||||
+1
-3
@@ -45,9 +45,7 @@ set :images_dir, 'images'
|
||||
|
||||
# Use the RedCarpet Markdown engine
|
||||
set :markdown_engine, :redcarpet
|
||||
set :markdown,
|
||||
:fenced_code_blocks => true,
|
||||
:with_toc_data => true
|
||||
set :markdown, :fenced_code_blocks => true
|
||||
|
||||
# Build-specific configuration
|
||||
configure :build do
|
||||
|
||||
@@ -89,10 +89,6 @@ Optional:
|
||||
associate with the AMI. By default no product codes are associated with
|
||||
the AMI.
|
||||
|
||||
* `ami_regions` (array of string) - A list of regions to copy the AMI to.
|
||||
Tags and attributes are copied along with the AMI. AMI copying takes time
|
||||
depending on the size of the AMI, but will generally take many minutes.
|
||||
|
||||
* `ami_users` (array of string) - A list of account IDs that have access
|
||||
to launch the resulting AMI(s). By default no additional users other than the user
|
||||
creating the AMI has permissions to launch it.
|
||||
@@ -121,8 +117,6 @@ Optional:
|
||||
template where the `.Device` variable is replaced with the name of the
|
||||
device where the volume is attached.
|
||||
|
||||
* `tags` (object of key/value strings) - Tags applied to the AMI.
|
||||
|
||||
* `unmount_command` (string) - Just like `mount_command`, except this is
|
||||
the command to unmount devices.
|
||||
|
||||
|
||||
@@ -75,10 +75,6 @@ Optional:
|
||||
associate with the AMI. By default no product codes are associated with
|
||||
the AMI.
|
||||
|
||||
* `ami_regions` (array of string) - A list of regions to copy the AMI to.
|
||||
Tags and attributes are copied along with the AMI. AMI copying takes time
|
||||
depending on the size of the AMI, but will generally take many minutes.
|
||||
|
||||
* `ami_users` (array of string) - A list of account IDs that have access
|
||||
to launch the resulting AMI(s). By default no additional users other than the user
|
||||
creating the AMI has permissions to launch it.
|
||||
|
||||
@@ -91,10 +91,6 @@ Optional:
|
||||
associate with the AMI. By default no product codes are associated with
|
||||
the AMI.
|
||||
|
||||
* `ami_regions` (array of string) - A list of regions to copy the AMI to.
|
||||
Tags and attributes are copied along with the AMI. AMI copying takes time
|
||||
depending on the size of the AMI, but will generally take many minutes.
|
||||
|
||||
* `ami_users` (array of string) - A list of account IDs that have access
|
||||
to launch the resulting AMI(s). By default no additional users other than the user
|
||||
creating the AMI has permissions to launch it.
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
---
|
||||
layout: "docs"
|
||||
---
|
||||
|
||||
# OpenStack Builder
|
||||
|
||||
Type: `openstack`
|
||||
|
||||
The `openstack` builder is able to create new images for use with
|
||||
[OpenStack](http://www.openstack.org). The builder takes a source
|
||||
image, runs any provisioning necessary on the image after launching it,
|
||||
then creates a new reusable image. This reusable image can then be
|
||||
used as the foundation of new servers that are launched within OpenStack.
|
||||
The builder will create temporary keypairs that provide temporary access to
|
||||
the server while the image is being created. This simplifies configuration
|
||||
quite a bit.
|
||||
|
||||
The builder does _not_ manage images. Once it creates an image, it is up to
|
||||
you to use it or delete it.
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
There are many configuration options available for the builder. They are
|
||||
segmented below into two categories: required and optional parameters. Within
|
||||
each category, the available configuration keys are alphabetized.
|
||||
|
||||
Required:
|
||||
|
||||
* `flavor` (string) - The ID or full URL for the desired flavor for the
|
||||
server to be created.
|
||||
|
||||
* `image_name` (string) - The name of the resulting image.
|
||||
|
||||
* `password` (string) - The password used to connect to the OpenStack service.
|
||||
If not specified, Packer will attempt to read this from the
|
||||
`SDK_PASSWORD` environment variable.
|
||||
|
||||
* `provider` (string) - The provider used to connect to the OpenStack service.
|
||||
If not specified, Packer will attempt to read this from the
|
||||
`SDK_PROVIDER` environment variable.
|
||||
|
||||
* `source_image` (string) - The ID or full URL to the base image to use.
|
||||
This is the image that will be used to launch a new server and provision it.
|
||||
|
||||
* `username` (string) - The username used to connect to the OpenStack service.
|
||||
If not specified, Packer will attempt to read this from the
|
||||
`SDK_USERNAME` environment variable.
|
||||
|
||||
Optional:
|
||||
|
||||
* `ssh_port` (int) - The port that SSH will be available on. Defaults to port
|
||||
22.
|
||||
|
||||
* `ssh_timeout` (string) - The time to wait for SSH to become available
|
||||
before timing out. The format of this value is a duration such as "5s"
|
||||
or "5m". The default SSH timeout is "1m".
|
||||
|
||||
* `ssh_username` (string) - The username to use in order to communicate
|
||||
over SSH to the running droplet. The default is "root".
|
||||
|
||||
## Basic Example
|
||||
|
||||
Here is a basic example. This is a working example to build a
|
||||
Ubuntu 12.04 LTS (Precise Pangolin) on Rackspace OpenStack cloud offering.
|
||||
|
||||
<pre class="prettyprint">
|
||||
{
|
||||
"type": "openstack",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"provider": "",
|
||||
"ssh_username": "root",
|
||||
"image_name": "Test image",
|
||||
"source_image": "23b564c9-c3e6-49f9-bc68-86c7a9ab5018",
|
||||
"flavor": "2"
|
||||
}
|
||||
</pre>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user