Commit a832e126 authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

Merge pull request #2235 from mitchellh/b-refactor-ssh

Communicator refactor, shared code for communicator connect
parents 7eff6b11 c3cc9e84
...@@ -4,9 +4,9 @@ import ( ...@@ -4,9 +4,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"time"
"github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
) )
...@@ -21,11 +21,6 @@ type RunConfig struct { ...@@ -21,11 +21,6 @@ type RunConfig struct {
SourceAmi string `mapstructure:"source_ami"` SourceAmi string `mapstructure:"source_ami"`
SpotPrice string `mapstructure:"spot_price"` SpotPrice string `mapstructure:"spot_price"`
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"` SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPrivateKeyFile string `mapstructure:"ssh_private_key_file"`
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
SSHPort int `mapstructure:"ssh_port"`
SecurityGroupId string `mapstructure:"security_group_id"` SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"` SecurityGroupIds []string `mapstructure:"security_group_ids"`
SubnetId string `mapstructure:"subnet_id"` SubnetId string `mapstructure:"subnet_id"`
...@@ -34,27 +29,19 @@ type RunConfig struct { ...@@ -34,27 +29,19 @@ type RunConfig struct {
UserDataFile string `mapstructure:"user_data_file"` UserDataFile string `mapstructure:"user_data_file"`
VpcId string `mapstructure:"vpc_id"` VpcId string `mapstructure:"vpc_id"`
// Unexported fields that are calculated from others // Communicator settings
sshTimeout time.Duration Comm communicator.Config `mapstructure:",squash"`
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
} }
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
// Defaults
if c.SSHPort == 0 {
c.SSHPort = 22
}
if c.RawSSHTimeout == "" {
c.RawSSHTimeout = "5m"
}
if c.TemporaryKeyPairName == "" { if c.TemporaryKeyPairName == "" {
c.TemporaryKeyPairName = fmt.Sprintf( c.TemporaryKeyPairName = fmt.Sprintf(
"packer %s", uuid.TimeOrderedUUID()) "packer %s", uuid.TimeOrderedUUID())
} }
// Validation // Validation
var errs []error errs := c.Comm.Prepare(ctx)
if c.SourceAmi == "" { if c.SourceAmi == "" {
errs = append(errs, errors.New("A source_ami must be specified")) errs = append(errs, errors.New("A source_ami must be specified"))
} }
...@@ -70,10 +57,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { ...@@ -70,10 +57,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
} }
} }
if c.SSHUsername == "" {
errs = append(errs, errors.New("An ssh_username must be specified"))
}
if c.UserData != "" && c.UserDataFile != "" { if c.UserData != "" && c.UserDataFile != "" {
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified.")) errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
} else if c.UserDataFile != "" { } else if c.UserDataFile != "" {
...@@ -91,15 +74,5 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { ...@@ -91,15 +74,5 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
} }
} }
var err error
c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
}
return errs return errs
} }
func (c *RunConfig) SSHTimeout() time.Duration {
return c.sshTimeout
}
...@@ -4,6 +4,8 @@ import ( ...@@ -4,6 +4,8 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"github.com/mitchellh/packer/helper/communicator"
) )
func init() { func init() {
...@@ -19,7 +21,10 @@ func testConfig() *RunConfig { ...@@ -19,7 +21,10 @@ func testConfig() *RunConfig {
return &RunConfig{ return &RunConfig{
SourceAmi: "abcd", SourceAmi: "abcd",
InstanceType: "m1.small", InstanceType: "m1.small",
SSHUsername: "root",
Comm: communicator.Config{
SSHUsername: "foo",
},
} }
} }
...@@ -62,41 +67,28 @@ func TestRunConfigPrepare_SpotAuto(t *testing.T) { ...@@ -62,41 +67,28 @@ func TestRunConfigPrepare_SpotAuto(t *testing.T) {
func TestRunConfigPrepare_SSHPort(t *testing.T) { func TestRunConfigPrepare_SSHPort(t *testing.T) {
c := testConfig() c := testConfig()
c.SSHPort = 0 c.Comm.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 { if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if c.SSHPort != 44 { if c.Comm.SSHPort != 22 {
t.Fatalf("invalid value: %d", c.SSHPort) t.Fatalf("invalid value: %d", c.Comm.SSHPort)
} }
}
func TestRunConfigPrepare_SSHTimeout(t *testing.T) { c.Comm.SSHPort = 44
c := testConfig()
c.RawSSHTimeout = ""
if err := c.Prepare(nil); len(err) != 0 { if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
c.RawSSHTimeout = "bad" if c.Comm.SSHPort != 44 {
if err := c.Prepare(nil); len(err) != 1 { t.Fatalf("invalid value: %d", c.Comm.SSHPort)
t.Fatalf("err: %s", err)
} }
} }
func TestRunConfigPrepare_SSHUsername(t *testing.T) { func TestRunConfigPrepare_SSHUsername(t *testing.T) {
c := testConfig() c := testConfig()
c.SSHUsername = "" c.Comm.SSHUsername = ""
if err := c.Prepare(nil); len(err) != 1 { if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
......
...@@ -10,9 +10,9 @@ import ( ...@@ -10,9 +10,9 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
// SSHAddress returns a function that can be given to the SSH communicator // SSHHost returns a function that can be given to the SSH communicator
// for determining the SSH address based on the instance DNS name. // for determining the SSH address based on the instance DNS name.
func SSHAddress(e *ec2.EC2, port int, private bool) func(multistep.StateBag) (string, error) { func SSHHost(e *ec2.EC2, private bool) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) { return func(state multistep.StateBag) (string, error) {
for j := 0; j < 2; j++ { for j := 0; j < 2; j++ {
var host string var host string
...@@ -28,7 +28,7 @@ func SSHAddress(e *ec2.EC2, port int, private bool) func(multistep.StateBag) (st ...@@ -28,7 +28,7 @@ func SSHAddress(e *ec2.EC2, port int, private bool) func(multistep.StateBag) (st
} }
if host != "" { if host != "" {
return fmt.Sprintf("%s:%d", host, port), nil return host, nil
} }
r, err := e.DescribeInstances(&ec2.DescribeInstancesInput{ r, err := e.DescribeInstances(&ec2.DescribeInstancesInput{
......
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common" awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
...@@ -89,11 +90,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -89,11 +90,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Debug: b.config.PackerDebug, Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
KeyPairName: b.config.TemporaryKeyPairName, KeyPairName: b.config.TemporaryKeyPairName,
PrivateKeyFile: b.config.SSHPrivateKeyFile, PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
}, },
&awscommon.StepSecurityGroup{ &awscommon.StepSecurityGroup{
SecurityGroupIds: b.config.SecurityGroupIds, SecurityGroupIds: b.config.SecurityGroupIds,
SSHPort: b.config.SSHPort, SSHPort: b.config.RunConfig.Comm.SSHPort,
VpcId: b.config.VpcId, VpcId: b.config.VpcId,
}, },
&awscommon.StepRunSourceInstance{ &awscommon.StepRunSourceInstance{
...@@ -112,11 +113,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -112,11 +113,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
BlockDevices: b.config.BlockDevices, BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags, Tags: b.config.RunTags,
}, },
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: awscommon.SSHAddress( Config: &b.config.RunConfig.Comm,
ec2conn, b.config.SSHPort, b.config.SSHPrivateIp), Host: awscommon.SSHHost(
SSHConfig: awscommon.SSHConfig(b.config.SSHUsername), ec2conn,
SSHWaitTimeout: b.config.SSHTimeout(), b.config.SSHPrivateIp),
SSHConfig: awscommon.SSHConfig(
b.config.RunConfig.Comm.SSHUsername),
}, },
&common.StepProvision{}, &common.StepProvision{},
&stepStopInstance{SpotPrice: b.config.SpotPrice}, &stepStopInstance{SpotPrice: b.config.SpotPrice},
......
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common" awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
...@@ -175,11 +176,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -175,11 +176,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Debug: b.config.PackerDebug, Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName), DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
KeyPairName: b.config.TemporaryKeyPairName, KeyPairName: b.config.TemporaryKeyPairName,
PrivateKeyFile: b.config.SSHPrivateKeyFile, PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
}, },
&awscommon.StepSecurityGroup{ &awscommon.StepSecurityGroup{
SecurityGroupIds: b.config.SecurityGroupIds, SecurityGroupIds: b.config.SecurityGroupIds,
SSHPort: b.config.SSHPort, SSHPort: b.config.RunConfig.Comm.SSHPort,
VpcId: b.config.VpcId, VpcId: b.config.VpcId,
}, },
&awscommon.StepRunSourceInstance{ &awscommon.StepRunSourceInstance{
...@@ -197,11 +198,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -197,11 +198,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
BlockDevices: b.config.BlockDevices, BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags, Tags: b.config.RunTags,
}, },
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: awscommon.SSHAddress( Config: &b.config.RunConfig.Comm,
ec2conn, b.config.SSHPort, b.config.SSHPrivateIp), Host: awscommon.SSHHost(
SSHConfig: awscommon.SSHConfig(b.config.SSHUsername), ec2conn,
SSHWaitTimeout: b.config.SSHTimeout(), b.config.SSHPrivateIp),
SSHConfig: awscommon.SSHConfig(
b.config.RunConfig.Comm.SSHUsername),
}, },
&common.StepProvision{}, &common.StepProvision{},
&StepUploadX509Cert{}, &StepUploadX509Cert{},
......
...@@ -6,11 +6,11 @@ package digitalocean ...@@ -6,11 +6,11 @@ package digitalocean
import ( import (
"fmt" "fmt"
"log" "log"
"time"
"github.com/digitalocean/godo" "github.com/digitalocean/godo"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
...@@ -53,10 +53,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -53,10 +53,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}, },
new(stepCreateDroplet), new(stepCreateDroplet),
new(stepDropletInfo), new(stepDropletInfo),
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: sshAddress, Config: &b.config.Comm,
SSHConfig: sshConfig, Host: commHost,
SSHWaitTimeout: 5 * time.Minute, SSHConfig: sshConfig,
}, },
new(common.StepProvision), new(common.StepProvision),
new(stepShutdown), new(stepShutdown),
......
...@@ -3,6 +3,7 @@ package digitalocean ...@@ -3,6 +3,7 @@ package digitalocean
import ( import (
"strconv" "strconv"
"testing" "testing"
"time"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
) )
...@@ -163,8 +164,8 @@ func TestBuilderPrepare_SSHUsername(t *testing.T) { ...@@ -163,8 +164,8 @@ func TestBuilderPrepare_SSHUsername(t *testing.T) {
t.Fatalf("should not have error: %s", err) t.Fatalf("should not have error: %s", err)
} }
if b.config.SSHUsername != "root" { if b.config.Comm.SSHUsername != "root" {
t.Errorf("invalid: %s", b.config.SSHUsername) t.Errorf("invalid: %s", b.config.Comm.SSHUsername)
} }
// Test set // Test set
...@@ -178,52 +179,11 @@ func TestBuilderPrepare_SSHUsername(t *testing.T) { ...@@ -178,52 +179,11 @@ func TestBuilderPrepare_SSHUsername(t *testing.T) {
t.Fatalf("should not have error: %s", err) t.Fatalf("should not have error: %s", err)
} }
if b.config.SSHUsername != "foo" { if b.config.Comm.SSHUsername != "foo" {
t.Errorf("invalid: %s", b.config.SSHUsername) t.Errorf("invalid: %s", b.config.Comm.SSHUsername)
} }
} }
func TestBuilderPrepare_SSHTimeout(t *testing.T) {
var b Builder
config := testConfig()
// Test default
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.RawSSHTimeout != "1m" {
t.Errorf("invalid: %s", b.config.RawSSHTimeout)
}
// Test set
config["ssh_timeout"] = "30s"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["ssh_timeout"] = "tubes"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_StateTimeout(t *testing.T) { func TestBuilderPrepare_StateTimeout(t *testing.T) {
var b Builder var b Builder
config := testConfig() config := testConfig()
...@@ -237,8 +197,8 @@ func TestBuilderPrepare_StateTimeout(t *testing.T) { ...@@ -237,8 +197,8 @@ func TestBuilderPrepare_StateTimeout(t *testing.T) {
t.Fatalf("should not have error: %s", err) t.Fatalf("should not have error: %s", err)
} }
if b.config.RawStateTimeout != "6m" { if b.config.StateTimeout != 6*time.Minute {
t.Errorf("invalid: %s", b.config.RawStateTimeout) t.Errorf("invalid: %s", b.config.StateTimeout)
} }
// Test set // Test set
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
...@@ -16,6 +17,7 @@ import ( ...@@ -16,6 +17,7 @@ import (
type Config struct { type Config struct {
common.PackerConfig `mapstructure:",squash"` common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
APIToken string `mapstructure:"api_token"` APIToken string `mapstructure:"api_token"`
...@@ -23,20 +25,11 @@ type Config struct { ...@@ -23,20 +25,11 @@ type Config struct {
Size string `mapstructure:"size"` Size string `mapstructure:"size"`
Image string `mapstructure:"image"` Image string `mapstructure:"image"`
PrivateNetworking bool `mapstructure:"private_networking"` PrivateNetworking bool `mapstructure:"private_networking"`
SnapshotName string `mapstructure:"snapshot_name"` SnapshotName string `mapstructure:"snapshot_name"`
DropletName string `mapstructure:"droplet_name"` StateTimeout time.Duration `mapstructure:"state_timeout"`
UserData string `mapstructure:"user_data"` DropletName string `mapstructure:"droplet_name"`
SSHUsername string `mapstructure:"ssh_username"` UserData string `mapstructure:"user_data"`
SSHPort uint `mapstructure:"ssh_port"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
RawStateTimeout string `mapstructure:"state_timeout"`
// These are unexported since they're set by other fields
// being set.
sshTimeout time.Duration
stateTimeout time.Duration
ctx *interpolate.Context ctx *interpolate.Context
} }
...@@ -79,29 +72,22 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { ...@@ -79,29 +72,22 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
c.DropletName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) c.DropletName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
} }
if c.SSHUsername == "" { if c.Comm.SSHUsername == "" {
// Default to "root". You can override this if your // Default to "root". You can override this if your
// SourceImage has a different user account then the DO default // SourceImage has a different user account then the DO default
c.SSHUsername = "root" c.Comm.SSHUsername = "root"
}
if c.SSHPort == 0 {
// Default to port 22 per DO default
c.SSHPort = 22
} }
if c.RawSSHTimeout == "" { if c.StateTimeout == 0 {
// Default to 1 minute timeouts
c.RawSSHTimeout = "1m"
}
if c.RawStateTimeout == "" {
// Default to 6 minute timeouts waiting for // Default to 6 minute timeouts waiting for
// desired state. i.e waiting for droplet to become active // desired state. i.e waiting for droplet to become active
c.RawStateTimeout = "6m" c.StateTimeout = 6 * time.Minute
} }
var errs *packer.MultiError var errs *packer.MultiError
if es := c.Comm.Prepare(c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
if c.APIToken == "" { if c.APIToken == "" {
// Required configurations that will display errors if not set // Required configurations that will display errors if not set
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
...@@ -123,20 +109,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { ...@@ -123,20 +109,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
errs, errors.New("image is required")) errs, errors.New("image is required"))
} }
sshTimeout, err := time.ParseDuration(c.RawSSHTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
}
c.sshTimeout = sshTimeout
stateTimeout, err := time.ParseDuration(c.RawStateTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing state_timeout: %s", err))
}
c.stateTimeout = stateTimeout
if errs != nil && len(errs.Errors) > 0 { if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs return nil, nil, errs
} }
......
...@@ -2,14 +2,14 @@ package digitalocean ...@@ -2,14 +2,14 @@ package digitalocean
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"github.com/mitchellh/multistep"
) )
func sshAddress(state multistep.StateBag) (string, error) { func commHost(state multistep.StateBag) (string, error) {
config := state.Get("config").(Config)
ipAddress := state.Get("droplet_ip").(string) ipAddress := state.Get("droplet_ip").(string)
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil return ipAddress, nil
} }
func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) { func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
...@@ -22,7 +22,7 @@ func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) { ...@@ -22,7 +22,7 @@ func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
} }
return &ssh.ClientConfig{ return &ssh.ClientConfig{
User: config.SSHUsername, User: config.Comm.SSHUsername,
Auth: []ssh.AuthMethod{ Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer), ssh.PublicKeys(signer),
}, },
......
...@@ -18,7 +18,7 @@ func (s *stepDropletInfo) Run(state multistep.StateBag) multistep.StepAction { ...@@ -18,7 +18,7 @@ func (s *stepDropletInfo) Run(state multistep.StateBag) multistep.StepAction {
ui.Say("Waiting for droplet to become active...") ui.Say("Waiting for droplet to become active...")
err := waitForDropletState("active", dropletId, client, c.stateTimeout) err := waitForDropletState("active", dropletId, client, c.StateTimeout)
if err != nil { if err != nil {
err := fmt.Errorf("Error waiting for droplet to become active: %s", err) err := fmt.Errorf("Error waiting for droplet to become active: %s", err)
state.Put("error", err) state.Put("error", err)
......
...@@ -42,7 +42,7 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { ...@@ -42,7 +42,7 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction {
} }
log.Println("Waiting for poweroff event to complete...") log.Println("Waiting for poweroff event to complete...")
err = waitForDropletState("off", dropletId, client, c.stateTimeout) err = waitForDropletState("off", dropletId, client, c.StateTimeout)
if err != nil { if err != nil {
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
......
...@@ -41,7 +41,7 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction { ...@@ -41,7 +41,7 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
// With the pending state over, verify that we're in the active state // With the pending state over, verify that we're in the active state
ui.Say("Waiting for snapshot to complete...") ui.Say("Waiting for snapshot to complete...")
err = waitForDropletState("active", dropletId, client, c.stateTimeout) err = waitForDropletState("active", dropletId, client, c.StateTimeout)
if err != nil { if err != nil {
err := fmt.Errorf("Error waiting for snapshot to complete: %s", err) err := fmt.Errorf("Error waiting for snapshot to complete: %s", err)
state.Put("error", err) state.Put("error", err)
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
) )
...@@ -60,10 +61,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -60,10 +61,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepInstanceInfo{ &StepInstanceInfo{
Debug: b.config.PackerDebug, Debug: b.config.PackerDebug,
}, },
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: sshAddress, Config: &b.config.Comm,
SSHConfig: sshConfig, Host: commHost,
SSHWaitTimeout: b.config.sshTimeout, SSHConfig: sshConfig,
}, },
new(common.StepProvision), new(common.StepProvision),
new(StepTeardownInstance), new(StepTeardownInstance),
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
...@@ -17,6 +18,7 @@ import ( ...@@ -17,6 +18,7 @@ import (
// state of the config object. // state of the config object.
type Config struct { type Config struct {
common.PackerConfig `mapstructure:",squash"` common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
AccountFile string `mapstructure:"account_file"` AccountFile string `mapstructure:"account_file"`
ProjectId string `mapstructure:"project_id"` ProjectId string `mapstructure:"project_id"`
...@@ -31,16 +33,12 @@ type Config struct { ...@@ -31,16 +33,12 @@ type Config struct {
Network string `mapstructure:"network"` Network string `mapstructure:"network"`
SourceImage string `mapstructure:"source_image"` SourceImage string `mapstructure:"source_image"`
SourceImageProjectId string `mapstructure:"source_image_project_id"` SourceImageProjectId string `mapstructure:"source_image_project_id"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort uint `mapstructure:"ssh_port"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
RawStateTimeout string `mapstructure:"state_timeout"` RawStateTimeout string `mapstructure:"state_timeout"`
Tags []string `mapstructure:"tags"` Tags []string `mapstructure:"tags"`
Zone string `mapstructure:"zone"` Zone string `mapstructure:"zone"`
account accountFile account accountFile
privateKeyBytes []byte privateKeyBytes []byte
sshTimeout time.Duration
stateTimeout time.Duration stateTimeout time.Duration
ctx *interpolate.Context ctx *interpolate.Context
} }
...@@ -88,20 +86,12 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { ...@@ -88,20 +86,12 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
c.MachineType = "n1-standard-1" c.MachineType = "n1-standard-1"
} }
if c.RawSSHTimeout == "" {
c.RawSSHTimeout = "5m"
}
if c.RawStateTimeout == "" { if c.RawStateTimeout == "" {
c.RawStateTimeout = "5m" c.RawStateTimeout = "5m"
} }
if c.SSHUsername == "" { if c.Comm.SSHUsername == "" {
c.SSHUsername = "root" c.Comm.SSHUsername = "root"
}
if c.SSHPort == 0 {
c.SSHPort = 22
} }
var errs *packer.MultiError var errs *packer.MultiError
...@@ -122,14 +112,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { ...@@ -122,14 +112,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
errs, errors.New("a zone must be specified")) errs, errors.New("a zone must be specified"))
} }
// Process timeout settings.
sshTimeout, err := time.ParseDuration(c.RawSSHTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
}
c.sshTimeout = sshTimeout
stateTimeout, err := time.ParseDuration(c.RawStateTimeout) stateTimeout, err := time.ParseDuration(c.RawStateTimeout)
if err != nil { if err != nil {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
......
...@@ -2,15 +2,14 @@ package googlecompute ...@@ -2,15 +2,14 @@ package googlecompute
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
// sshAddress returns the ssh address. func commHost(state multistep.StateBag) (string, error) {
func sshAddress(state multistep.StateBag) (string, error) {
config := state.Get("config").(*Config)
ipAddress := state.Get("instance_ip").(string) ipAddress := state.Get("instance_ip").(string)
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil return ipAddress, nil
} }
// sshConfig returns the ssh configuration. // sshConfig returns the ssh configuration.
...@@ -24,7 +23,7 @@ func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) { ...@@ -24,7 +23,7 @@ func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
} }
return &ssh.ClientConfig{ return &ssh.ClientConfig{
User: config.SSHUsername, User: config.Comm.SSHUsername,
Auth: []ssh.AuthMethod{ Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer), ssh.PublicKeys(signer),
}, },
......
...@@ -32,7 +32,7 @@ func (config *Config) getInstanceMetadata(sshPublicKey string) map[string]string ...@@ -32,7 +32,7 @@ func (config *Config) getInstanceMetadata(sshPublicKey string) map[string]string
// Merge any existing ssh keys with our public key // Merge any existing ssh keys with our public key
sshMetaKey := "sshKeys" sshMetaKey := "sshKeys"
sshKeys := fmt.Sprintf("%s:%s", config.SSHUsername, sshPublicKey) sshKeys := fmt.Sprintf("%s:%s", config.Comm.SSHUsername, sshPublicKey)
if confSshKeys, exists := instanceMetadata[sshMetaKey]; exists { if confSshKeys, exists := instanceMetadata[sshMetaKey]; exists {
sshKeys = fmt.Sprintf("%s\n%s", sshKeys, confSshKeys) sshKeys = fmt.Sprintf("%s\n%s", sshKeys, confSshKeys)
} }
......
package null package null
import ( import (
"log"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log"
"time"
) )
const BuilderId = "fnoeding.null" const BuilderId = "fnoeding.null"
...@@ -27,10 +28,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -27,10 +28,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
steps := []multistep.Step{ steps := []multistep.Step{
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: SSHAddress(b.config.Host, b.config.Port), Config: &b.config.CommConfig,
SSHConfig: SSHConfig(b.config.SSHUsername, b.config.SSHPassword, b.config.SSHPrivateKeyFile), Host: CommHost(b.config.CommConfig.SSHHost),
SSHWaitTimeout: 1 * time.Minute, SSHConfig: SSHConfig(
b.config.CommConfig.SSHUsername,
b.config.CommConfig.SSHPassword,
b.config.CommConfig.SSHPrivateKey),
}, },
&common.StepProvision{}, &common.StepProvision{},
} }
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
...@@ -12,49 +13,40 @@ import ( ...@@ -12,49 +13,40 @@ import (
type Config struct { type Config struct {
common.PackerConfig `mapstructure:",squash"` common.PackerConfig `mapstructure:",squash"`
Host string `mapstructure:"host"` CommConfig communicator.Config `mapstructure:",squash"`
Port int `mapstructure:"port"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPrivateKeyFile string `mapstructure:"ssh_private_key_file"`
} }
func NewConfig(raws ...interface{}) (*Config, []string, error) { func NewConfig(raws ...interface{}) (*Config, []string, error) {
var c Config var c Config
err := config.Decode(&c, &config.DecodeOpts{ err := config.Decode(&c, &config.DecodeOpts{
Interpolate: true, Interpolate: true,
InterpolateFilter: &interpolate.RenderFilter{ InterpolateFilter: &interpolate.RenderFilter{},
Exclude: []string{
"run_command",
},
},
}, raws...) }, raws...)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if c.Port == 0 {
c.Port = 22
}
var errs *packer.MultiError var errs *packer.MultiError
if c.Host == "" { if es := c.CommConfig.Prepare(nil); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
if c.CommConfig.SSHHost == "" {
errs = packer.MultiErrorAppend(errs, errs = packer.MultiErrorAppend(errs,
fmt.Errorf("host must be specified")) fmt.Errorf("host must be specified"))
} }
if c.SSHUsername == "" { if c.CommConfig.SSHUsername == "" {
errs = packer.MultiErrorAppend(errs, errs = packer.MultiErrorAppend(errs,
fmt.Errorf("ssh_username must be specified")) fmt.Errorf("ssh_username must be specified"))
} }
if c.SSHPassword == "" && c.SSHPrivateKeyFile == "" { if c.CommConfig.SSHPassword == "" && c.CommConfig.SSHPrivateKey == "" {
errs = packer.MultiErrorAppend(errs, errs = packer.MultiErrorAppend(errs,
fmt.Errorf("one of ssh_password and ssh_private_key_file must be specified")) fmt.Errorf("one of ssh_password and ssh_private_key_file must be specified"))
} }
if c.SSHPassword != "" && c.SSHPrivateKeyFile != "" { if c.CommConfig.SSHPassword != "" && c.CommConfig.SSHPrivateKey != "" {
errs = packer.MultiErrorAppend(errs, errs = packer.MultiErrorAppend(errs,
fmt.Errorf("only one of ssh_password and ssh_private_key_file must be specified")) fmt.Errorf("only one of ssh_password and ssh_private_key_file must be specified"))
} }
......
package null package null
import ( import (
"os"
"testing" "testing"
"github.com/mitchellh/packer/helper/communicator"
) )
func testConfig() map[string]interface{} { func testConfig() map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"host": "foo", "ssh_host": "foo",
"ssh_username": "bar", "ssh_username": "bar",
"ssh_password": "baz", "ssh_password": "baz",
} }
...@@ -48,8 +51,8 @@ func TestConfigPrepare_port(t *testing.T) { ...@@ -48,8 +51,8 @@ func TestConfigPrepare_port(t *testing.T) {
// default port should be 22 // default port should be 22
delete(raw, "port") delete(raw, "port")
c, warns, errs := NewConfig(raw) c, warns, errs := NewConfig(raw)
if c.Port != 22 { if c.CommConfig.SSHPort != 22 {
t.Fatalf("bad: port should default to 22, not %d", c.Port) t.Fatalf("bad: port should default to 22, not %d", c.CommConfig.SSHPort)
} }
testConfigOk(t, warns, errs) testConfigOk(t, warns, errs)
} }
...@@ -58,12 +61,12 @@ func TestConfigPrepare_host(t *testing.T) { ...@@ -58,12 +61,12 @@ func TestConfigPrepare_host(t *testing.T) {
raw := testConfig() raw := testConfig()
// No host // No host
delete(raw, "host") delete(raw, "ssh_host")
_, warns, errs := NewConfig(raw) _, warns, errs := NewConfig(raw)
testConfigErr(t, warns, errs) testConfigErr(t, warns, errs)
// Good host // Good host
raw["host"] = "good" raw["ssh_host"] = "good"
_, warns, errs = NewConfig(raw) _, warns, errs = NewConfig(raw)
testConfigOk(t, warns, errs) testConfigOk(t, warns, errs)
} }
...@@ -97,7 +100,9 @@ func TestConfigPrepare_sshCredential(t *testing.T) { ...@@ -97,7 +100,9 @@ func TestConfigPrepare_sshCredential(t *testing.T) {
testConfigOk(t, warns, errs) testConfigOk(t, warns, errs)
// only ssh_private_key_file // only ssh_private_key_file
raw["ssh_private_key_file"] = "good" testFile := communicator.TestPEM(t)
defer os.Remove(testFile)
raw["ssh_private_key_file"] = testFile
delete(raw, "ssh_password") delete(raw, "ssh_password")
_, warns, errs = NewConfig(raw) _, warns, errs = NewConfig(raw)
testConfigOk(t, warns, errs) testConfigOk(t, warns, errs)
......
...@@ -8,11 +8,9 @@ import ( ...@@ -8,11 +8,9 @@ import (
"io/ioutil" "io/ioutil"
) )
// SSHAddress returns a function that can be given to the SSH communicator func CommHost(host string) func(multistep.StateBag) (string, error) {
// for determining the SSH address
func SSHAddress(host string, port int) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) { return func(state multistep.StateBag) (string, error) {
return fmt.Sprintf("%s:%d", host, port), nil return host, nil
} }
} }
......
...@@ -5,10 +5,11 @@ package openstack ...@@ -5,10 +5,11 @@ package openstack
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"log" "log"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
...@@ -19,9 +20,10 @@ const BuilderId = "mitchellh.openstack" ...@@ -19,9 +20,10 @@ const BuilderId = "mitchellh.openstack"
type Config struct { type Config struct {
common.PackerConfig `mapstructure:",squash"` common.PackerConfig `mapstructure:",squash"`
AccessConfig `mapstructure:",squash"`
ImageConfig `mapstructure:",squash"` AccessConfig `mapstructure:",squash"`
RunConfig `mapstructure:",squash"` ImageConfig `mapstructure:",squash"`
RunConfig `mapstructure:",squash"`
ctx interpolate.Context ctx interpolate.Context
} }
...@@ -88,10 +90,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -88,10 +90,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
FloatingIpPool: b.config.FloatingIpPool, FloatingIpPool: b.config.FloatingIpPool,
FloatingIp: b.config.FloatingIp, FloatingIp: b.config.FloatingIp,
}, },
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: SSHAddress(computeClient, b.config.SSHInterface, b.config.SSHPort), Config: &b.config.RunConfig.Comm,
SSHConfig: SSHConfig(b.config.SSHUsername), Host: CommHost(
SSHWaitTimeout: b.config.SSHTimeout(), computeClient,
b.config.SSHInterface),
SSHConfig: SSHConfig(b.config.RunConfig.Comm.SSHUsername),
}, },
&common.StepProvision{}, &common.StepProvision{},
&stepCreateImage{}, &stepCreateImage{},
......
...@@ -2,21 +2,19 @@ package openstack ...@@ -2,21 +2,19 @@ package openstack
import ( import (
"errors" "errors"
"fmt"
"time"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
) )
// RunConfig contains configuration for running an instance from a source // RunConfig contains configuration for running an instance from a source
// image and details on how to access that launched image. // image and details on how to access that launched image.
type RunConfig struct { type RunConfig struct {
Comm communicator.Config `mapstructure:",squash"`
SSHInterface string `mapstructure:"ssh_interface"`
SourceImage string `mapstructure:"source_image"` SourceImage string `mapstructure:"source_image"`
Flavor string `mapstructure:"flavor"` Flavor string `mapstructure:"flavor"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
SSHInterface string `mapstructure:"ssh_interface"`
AvailabilityZone string `mapstructure:"availability_zone"` AvailabilityZone string `mapstructure:"availability_zone"`
RackconnectWait bool `mapstructure:"rackconnect_wait"` RackconnectWait bool `mapstructure:"rackconnect_wait"`
FloatingIpPool string `mapstructure:"floating_ip_pool"` FloatingIpPool string `mapstructure:"floating_ip_pool"`
...@@ -27,23 +25,12 @@ type RunConfig struct { ...@@ -27,23 +25,12 @@ type RunConfig struct {
// Not really used, but here for BC // Not really used, but here for BC
OpenstackProvider string `mapstructure:"openstack_provider"` OpenstackProvider string `mapstructure:"openstack_provider"`
UseFloatingIp bool `mapstructure:"use_floating_ip"` UseFloatingIp bool `mapstructure:"use_floating_ip"`
// Unexported fields that are calculated from others
sshTimeout time.Duration
} }
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
// Defaults // Defaults
if c.SSHUsername == "" { if c.Comm.SSHUsername == "" {
c.SSHUsername = "root" c.Comm.SSHUsername = "root"
}
if c.SSHPort == 0 {
c.SSHPort = 22
}
if c.RawSSHTimeout == "" {
c.RawSSHTimeout = "5m"
} }
if c.UseFloatingIp && c.FloatingIpPool == "" { if c.UseFloatingIp && c.FloatingIpPool == "" {
...@@ -51,8 +38,7 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { ...@@ -51,8 +38,7 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
} }
// Validation // Validation
var err error errs := c.Comm.Prepare(ctx)
errs := make([]error, 0)
if c.SourceImage == "" { if c.SourceImage == "" {
errs = append(errs, errors.New("A source_image must be specified")) errs = append(errs, errors.New("A source_image must be specified"))
} }
...@@ -61,18 +47,5 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { ...@@ -61,18 +47,5 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
errs = append(errs, errors.New("A flavor must be specified")) errs = append(errs, errors.New("A flavor must be specified"))
} }
if c.SSHUsername == "" {
errs = append(errs, errors.New("An ssh_username must be specified"))
}
c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
}
return errs return errs
} }
func (c *RunConfig) SSHTimeout() time.Duration {
return c.sshTimeout
}
...@@ -3,6 +3,8 @@ package openstack ...@@ -3,6 +3,8 @@ package openstack
import ( import (
"os" "os"
"testing" "testing"
"github.com/mitchellh/packer/helper/communicator"
) )
func init() { func init() {
...@@ -17,7 +19,10 @@ func testRunConfig() *RunConfig { ...@@ -17,7 +19,10 @@ func testRunConfig() *RunConfig {
return &RunConfig{ return &RunConfig{
SourceImage: "abcd", SourceImage: "abcd",
Flavor: "m1.small", Flavor: "m1.small",
SSHUsername: "root",
Comm: communicator.Config{
SSHUsername: "foo",
},
} }
} }
...@@ -47,41 +52,28 @@ func TestRunConfigPrepare_SourceImage(t *testing.T) { ...@@ -47,41 +52,28 @@ func TestRunConfigPrepare_SourceImage(t *testing.T) {
func TestRunConfigPrepare_SSHPort(t *testing.T) { func TestRunConfigPrepare_SSHPort(t *testing.T) {
c := testRunConfig() c := testRunConfig()
c.SSHPort = 0 c.Comm.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 { if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if c.SSHPort != 44 { if c.Comm.SSHPort != 22 {
t.Fatalf("invalid value: %d", c.SSHPort) t.Fatalf("invalid value: %d", c.Comm.SSHPort)
} }
}
func TestRunConfigPrepare_SSHTimeout(t *testing.T) { c.Comm.SSHPort = 44
c := testRunConfig()
c.RawSSHTimeout = ""
if err := c.Prepare(nil); len(err) != 0 { if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
c.RawSSHTimeout = "bad" if c.Comm.SSHPort != 44 {
if err := c.Prepare(nil); len(err) != 1 { t.Fatalf("invalid value: %d", c.Comm.SSHPort)
t.Fatalf("err: %s", err)
} }
} }
func TestRunConfigPrepare_SSHUsername(t *testing.T) { func TestRunConfigPrepare_SSHUsername(t *testing.T) {
c := testRunConfig() c := testRunConfig()
c.SSHUsername = "" c.Comm.SSHUsername = ""
if err := c.Prepare(nil); len(err) != 0 { if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
......
...@@ -13,22 +13,21 @@ import ( ...@@ -13,22 +13,21 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
// SSHAddress returns a function that can be given to the SSH communicator // CommHost looks up the host for the communicator.
// for determining the SSH address based on the server AccessIPv4 setting.. func CommHost(
func SSHAddress(
client *gophercloud.ServiceClient, client *gophercloud.ServiceClient,
sshinterface string, port int) func(multistep.StateBag) (string, error) { sshinterface string) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) { return func(state multistep.StateBag) (string, error) {
s := state.Get("server").(*servers.Server) s := state.Get("server").(*servers.Server)
// If we have a floating IP, use that // If we have a floating IP, use that
ip := state.Get("access_ip").(*floatingip.FloatingIP) ip := state.Get("access_ip").(*floatingip.FloatingIP)
if ip != nil && ip.IP != "" { if ip != nil && ip.IP != "" {
return fmt.Sprintf("%s:%d", ip.IP, port), nil return ip.IP, nil
} }
if s.AccessIPv4 != "" { if s.AccessIPv4 != "" {
return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil return s.AccessIPv4, nil
} }
// Get all the addresses associated with this server. This // Get all the addresses associated with this server. This
...@@ -53,7 +52,7 @@ func SSHAddress( ...@@ -53,7 +52,7 @@ func SSHAddress(
} }
} }
if addr != "" { if addr != "" {
return fmt.Sprintf("%s:%d", addr, port), nil return addr, nil
} }
} }
} }
......
package common package common
import ( import (
"fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
commonssh "github.com/mitchellh/packer/common/ssh" commonssh "github.com/mitchellh/packer/common/ssh"
packerssh "github.com/mitchellh/packer/communicator/ssh" packerssh "github.com/mitchellh/packer/communicator/ssh"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
func SSHAddress(state multistep.StateBag) (string, error) { func CommHost(state multistep.StateBag) (string, error) {
vmName := state.Get("vmName").(string) vmName := state.Get("vmName").(string)
driver := state.Get("driver").(Driver) driver := state.Get("driver").(Driver)
...@@ -23,19 +21,19 @@ func SSHAddress(state multistep.StateBag) (string, error) { ...@@ -23,19 +21,19 @@ func SSHAddress(state multistep.StateBag) (string, error) {
return "", err return "", err
} }
return fmt.Sprintf("%s:22", ip), nil return ip, nil
} }
func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig, error) { func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig, error) {
return func(state multistep.StateBag) (*ssh.ClientConfig, error) { return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
auth := []ssh.AuthMethod{ auth := []ssh.AuthMethod{
ssh.Password(config.SSHPassword), ssh.Password(config.Comm.SSHPassword),
ssh.KeyboardInteractive( ssh.KeyboardInteractive(
packerssh.PasswordKeyboardInteractive(config.SSHPassword)), packerssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
} }
if config.SSHKeyPath != "" { if config.SSHKeyPath != "" {
signer, err := commonssh.FileSigner(config.SSHKeyPath) signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -44,7 +42,7 @@ func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig ...@@ -44,7 +42,7 @@ func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig
} }
return &ssh.ClientConfig{ return &ssh.ClientConfig{
User: config.SSHUser, User: config.Comm.SSHUsername,
Auth: auth, Auth: auth,
}, nil }, nil
} }
......
package common package common
import ( import (
"errors"
"fmt"
"os"
"time" "time"
commonssh "github.com/mitchellh/packer/common/ssh" "github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
) )
type SSHConfig struct { type SSHConfig struct {
SSHKeyPath string `mapstructure:"ssh_key_path"` Comm communicator.Config `mapstructure:",squash"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPort uint `mapstructure:"ssh_port"`
SSHUser string `mapstructure:"ssh_username"`
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
SSHWaitTimeout time.Duration // These are deprecated, but we keep them around for BC
// TODO(@mitchellh): remove
SSHKeyPath string `mapstructure:"ssh_key_path"`
SSHWaitTimeout time.Duration `mapstructure:"ssh_wait_timeout"`
} }
func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error { func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error {
if c.SSHPort == 0 { // TODO: backwards compatibility, write fixer instead
c.SSHPort = 22
}
if c.RawSSHWaitTimeout == "" {
c.RawSSHWaitTimeout = "20m"
}
var errs []error
if c.SSHKeyPath != "" { if c.SSHKeyPath != "" {
if _, err := os.Stat(c.SSHKeyPath); err != nil { c.Comm.SSHPrivateKey = c.SSHKeyPath
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
} else if _, err := commonssh.FileSigner(c.SSHKeyPath); err != nil {
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
}
} }
if c.SSHWaitTimeout != 0 {
if c.SSHUser == "" { c.Comm.SSHTimeout = c.SSHWaitTimeout
errs = append(errs, errors.New("An ssh_username must be specified."))
}
var err error
c.SSHWaitTimeout, err = time.ParseDuration(c.RawSSHWaitTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
} }
return errs return c.Comm.Prepare(ctx)
} }
...@@ -4,11 +4,15 @@ import ( ...@@ -4,11 +4,15 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"github.com/mitchellh/packer/helper/communicator"
) )
func testSSHConfig() *SSHConfig { func testSSHConfig() *SSHConfig {
return &SSHConfig{ return &SSHConfig{
SSHUser: "foo", Comm: communicator.Config{
SSHUsername: "foo",
},
} }
} }
...@@ -19,8 +23,8 @@ func TestSSHConfigPrepare(t *testing.T) { ...@@ -19,8 +23,8 @@ func TestSSHConfigPrepare(t *testing.T) {
t.Fatalf("err: %#v", errs) t.Fatalf("err: %#v", errs)
} }
if c.SSHPort != 22 { if c.Comm.SSHPort != 22 {
t.Errorf("bad ssh port: %d", c.SSHPort) t.Errorf("bad ssh port: %d", c.Comm.SSHPort)
} }
} }
...@@ -78,46 +82,14 @@ func TestSSHConfigPrepare_SSHUser(t *testing.T) { ...@@ -78,46 +82,14 @@ func TestSSHConfigPrepare_SSHUser(t *testing.T) {
var errs []error var errs []error
c = testSSHConfig() c = testSSHConfig()
c.SSHUser = "" c.Comm.SSHUsername = ""
errs = c.Prepare(testConfigTemplate(t)) errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 { if len(errs) == 0 {
t.Fatalf("should have error") t.Fatalf("should have error")
} }
c = testSSHConfig() c = testSSHConfig()
c.SSHUser = "exists" c.Comm.SSHUsername = "exists"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
}
func TestSSHConfigPrepare_SSHWaitTimeout(t *testing.T) {
var c *SSHConfig
var errs []error
// Defaults
c = testSSHConfig()
c.RawSSHWaitTimeout = ""
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
if c.RawSSHWaitTimeout != "20m" {
t.Fatalf("bad value: %s", c.RawSSHWaitTimeout)
}
// Test with a bad value
c = testSSHConfig()
c.RawSSHWaitTimeout = "this is not good"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 {
t.Fatal("should have error")
}
// Test with a good one
c = testSSHConfig()
c.RawSSHWaitTimeout = "5s"
errs = c.Prepare(testConfigTemplate(t)) errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 { if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs) t.Fatalf("should not have error: %#v", errs)
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
parallelscommon "github.com/mitchellh/packer/builder/parallels/common" parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
...@@ -245,10 +246,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -245,10 +246,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VMName: b.config.VMName, VMName: b.config.VMName,
Ctx: b.config.ctx, Ctx: b.config.ctx,
}, },
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: parallelscommon.SSHAddress, Config: &b.config.SSHConfig.Comm,
SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig), Host: parallelscommon.CommHost,
SSHWaitTimeout: b.config.SSHWaitTimeout, SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig),
}, },
&parallelscommon.StepUploadVersion{ &parallelscommon.StepUploadVersion{
Path: b.config.PrlctlVersionFile, Path: b.config.PrlctlVersionFile,
......
...@@ -3,11 +3,13 @@ package pvm ...@@ -3,11 +3,13 @@ package pvm
import ( import (
"errors" "errors"
"fmt" "fmt"
"log"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
parallelscommon "github.com/mitchellh/packer/builder/parallels/common" parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log"
) )
// Builder implements packer.Builder and builds the actual Parallels // Builder implements packer.Builder and builds the actual Parallels
...@@ -80,10 +82,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -80,10 +82,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VMName: b.config.VMName, VMName: b.config.VMName,
Ctx: b.config.ctx, Ctx: b.config.ctx,
}, },
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: parallelscommon.SSHAddress, Config: &b.config.SSHConfig.Comm,
SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig), Host: parallelscommon.CommHost,
SSHWaitTimeout: b.config.SSHWaitTimeout, SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig),
}, },
&parallelscommon.StepUploadVersion{ &parallelscommon.StepUploadVersion{
Path: b.config.PrlctlVersionFile, Path: b.config.PrlctlVersionFile,
......
...@@ -12,7 +12,7 @@ import ( ...@@ -12,7 +12,7 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
commonssh "github.com/mitchellh/packer/common/ssh" "github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
...@@ -78,6 +78,7 @@ type Builder struct { ...@@ -78,6 +78,7 @@ type Builder struct {
type Config struct { type Config struct {
common.PackerConfig `mapstructure:",squash"` common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
Accelerator string `mapstructure:"accelerator"` Accelerator string `mapstructure:"accelerator"`
BootCommand []string `mapstructure:"boot_command"` BootCommand []string `mapstructure:"boot_command"`
...@@ -103,25 +104,24 @@ type Config struct { ...@@ -103,25 +104,24 @@ type Config struct {
ShutdownCommand string `mapstructure:"shutdown_command"` ShutdownCommand string `mapstructure:"shutdown_command"`
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"` SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"` SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPort uint `mapstructure:"ssh_port"`
SSHUser string `mapstructure:"ssh_username"`
SSHKeyPath string `mapstructure:"ssh_key_path"`
VNCPortMin uint `mapstructure:"vnc_port_min"` VNCPortMin uint `mapstructure:"vnc_port_min"`
VNCPortMax uint `mapstructure:"vnc_port_max"` VNCPortMax uint `mapstructure:"vnc_port_max"`
VMName string `mapstructure:"vm_name"` VMName string `mapstructure:"vm_name"`
// These are deprecated, but we keep them around for BC
// TODO(@mitchellh): remove
SSHKeyPath string `mapstructure:"ssh_key_path"`
SSHWaitTimeout time.Duration `mapstructure:"ssh_wait_timeout"`
// TODO(mitchellh): deprecate // TODO(mitchellh): deprecate
RunOnce bool `mapstructure:"run_once"` RunOnce bool `mapstructure:"run_once"`
RawBootWait string `mapstructure:"boot_wait"` RawBootWait string `mapstructure:"boot_wait"`
RawSingleISOUrl string `mapstructure:"iso_url"` RawSingleISOUrl string `mapstructure:"iso_url"`
RawShutdownTimeout string `mapstructure:"shutdown_timeout"` RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
bootWait time.Duration `` bootWait time.Duration ``
shutdownTimeout time.Duration `` shutdownTimeout time.Duration ``
sshWaitTimeout time.Duration ``
ctx interpolate.Context ctx interpolate.Context
} }
...@@ -139,9 +139,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -139,9 +139,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
return nil, err return nil, err
} }
var errs *packer.MultiError
warnings := make([]string, 0)
if b.config.DiskSize == 0 { if b.config.DiskSize == 0 {
b.config.DiskSize = 40000 b.config.DiskSize = 40000
} }
...@@ -190,10 +187,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -190,10 +187,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.SSHHostPortMax = 4444 b.config.SSHHostPortMax = 4444
} }
if b.config.SSHPort == 0 {
b.config.SSHPort = 22
}
if b.config.VNCPortMin == 0 { if b.config.VNCPortMin == 0 {
b.config.VNCPortMin = 5900 b.config.VNCPortMin = 5900
} }
...@@ -222,6 +215,21 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -222,6 +215,21 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.DiskInterface = "virtio" b.config.DiskInterface = "virtio"
} }
// TODO: backwards compatibility, write fixer instead
if b.config.SSHKeyPath != "" {
b.config.Comm.SSHPrivateKey = b.config.SSHKeyPath
}
if b.config.SSHWaitTimeout != 0 {
b.config.Comm.SSHTimeout = b.config.SSHWaitTimeout
}
var errs *packer.MultiError
warnings := make([]string, 0)
if es := b.config.Comm.Prepare(&b.config.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
if !(b.config.Format == "qcow2" || b.config.Format == "raw") { if !(b.config.Format == "qcow2" || b.config.Format == "raw") {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed")) errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed"))
...@@ -314,42 +322,17 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -314,42 +322,17 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.RawShutdownTimeout = "5m" b.config.RawShutdownTimeout = "5m"
} }
if b.config.RawSSHWaitTimeout == "" {
b.config.RawSSHWaitTimeout = "20m"
}
b.config.shutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout) b.config.shutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout)
if err != nil { if err != nil {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) 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 := commonssh.FileSigner(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 { if b.config.SSHHostPortMin > b.config.SSHHostPortMax {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max")) errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
} }
if b.config.SSHUser == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("An ssh_username must be specified."))
}
b.config.sshWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
}
if b.config.VNCPortMin > b.config.VNCPortMax { if b.config.VNCPortMin > b.config.VNCPortMax {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max")) errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
...@@ -409,10 +392,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -409,10 +392,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
steprun, steprun,
&stepBootWait{}, &stepBootWait{},
&stepTypeBootCommand{}, &stepTypeBootCommand{},
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: sshAddress, Config: &b.config.Comm,
SSHConfig: sshConfig, Host: commHost,
SSHWaitTimeout: b.config.sshWaitTimeout, SSHConfig: sshConfig,
SSHPort: commPort,
}, },
new(common.StepProvision), new(common.StepProvision),
new(stepShutdown), new(stepShutdown),
......
...@@ -79,8 +79,8 @@ func TestBuilderPrepare_Defaults(t *testing.T) { ...@@ -79,8 +79,8 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
t.Errorf("bad max ssh host port: %d", b.config.SSHHostPortMax) t.Errorf("bad max ssh host port: %d", b.config.SSHHostPortMax)
} }
if b.config.SSHPort != 22 { if b.config.Comm.SSHPort != 22 {
t.Errorf("bad ssh port: %d", b.config.SSHPort) t.Errorf("bad ssh port: %d", b.config.Comm.SSHPort)
} }
if b.config.VMName != "packer-foo" { if b.config.VMName != "packer-foo" {
...@@ -595,10 +595,6 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) { ...@@ -595,10 +595,6 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if b.config.RawSSHWaitTimeout != "20m" {
t.Fatalf("bad value: %s", b.config.RawSSHWaitTimeout)
}
// Test with a bad value // Test with a bad value
config["ssh_wait_timeout"] = "this is not good" config["ssh_wait_timeout"] = "this is not good"
b = Builder{} b = Builder{}
......
package qemu package qemu
import ( import (
"fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
commonssh "github.com/mitchellh/packer/common/ssh" commonssh "github.com/mitchellh/packer/common/ssh"
"github.com/mitchellh/packer/communicator/ssh" "github.com/mitchellh/packer/communicator/ssh"
gossh "golang.org/x/crypto/ssh" gossh "golang.org/x/crypto/ssh"
) )
func sshAddress(state multistep.StateBag) (string, error) { func commHost(state multistep.StateBag) (string, error) {
return "127.0.0.1", nil
}
func commPort(state multistep.StateBag) (int, error) {
sshHostPort := state.Get("sshHostPort").(uint) sshHostPort := state.Get("sshHostPort").(uint)
return fmt.Sprintf("127.0.0.1:%d", sshHostPort), nil return int(sshHostPort), nil
} }
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) { func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(*Config) config := state.Get("config").(*Config)
auth := []gossh.AuthMethod{ auth := []gossh.AuthMethod{
gossh.Password(config.SSHPassword), gossh.Password(config.Comm.SSHPassword),
gossh.KeyboardInteractive( gossh.KeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)), ssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
} }
if config.SSHKeyPath != "" { if config.Comm.SSHPrivateKey != "" {
signer, err := commonssh.FileSigner(config.SSHKeyPath) signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -33,7 +35,7 @@ func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) { ...@@ -33,7 +35,7 @@ func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
} }
return &gossh.ClientConfig{ return &gossh.ClientConfig{
User: config.SSHUser, User: config.Comm.SSHUsername,
Auth: auth, Auth: auth,
}, nil }, nil
} }
package common package common
import ( import (
"fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
commonssh "github.com/mitchellh/packer/common/ssh" commonssh "github.com/mitchellh/packer/common/ssh"
"github.com/mitchellh/packer/communicator/ssh" "github.com/mitchellh/packer/communicator/ssh"
gossh "golang.org/x/crypto/ssh" gossh "golang.org/x/crypto/ssh"
) )
func SSHAddress(state multistep.StateBag) (string, error) { func CommHost(state multistep.StateBag) (string, error) {
return "127.0.0.1", nil
}
func SSHPort(state multistep.StateBag) (int, error) {
sshHostPort := state.Get("sshHostPort").(uint) sshHostPort := state.Get("sshHostPort").(uint)
return fmt.Sprintf("127.0.0.1:%d", sshHostPort), nil return int(sshHostPort), nil
} }
func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) { func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) {
return func(state multistep.StateBag) (*gossh.ClientConfig, error) { return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
auth := []gossh.AuthMethod{ auth := []gossh.AuthMethod{
gossh.Password(config.SSHPassword), gossh.Password(config.Comm.SSHPassword),
gossh.KeyboardInteractive( gossh.KeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)), ssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
} }
if config.SSHKeyPath != "" { if config.SSHKeyPath != "" {
signer, err := commonssh.FileSigner(config.SSHKeyPath) signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -32,7 +34,7 @@ func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConf ...@@ -32,7 +34,7 @@ func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConf
} }
return &gossh.ClientConfig{ return &gossh.ClientConfig{
User: config.SSHUser, User: config.Comm.SSHUsername,
Auth: auth, Auth: auth,
}, nil }, nil
} }
......
...@@ -2,25 +2,23 @@ package common ...@@ -2,25 +2,23 @@ package common
import ( import (
"errors" "errors"
"fmt"
"os"
"time" "time"
commonssh "github.com/mitchellh/packer/common/ssh" "github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
) )
type SSHConfig struct { type SSHConfig struct {
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"` Comm communicator.Config `mapstructure:",squash"`
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"`
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
SSHSkipNatMapping bool `mapstructure:"ssh_skip_nat_mapping"`
SSHWaitTimeout time.Duration SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
SSHSkipNatMapping bool `mapstructure:"ssh_skip_nat_mapping"`
// These are deprecated, but we keep them around for BC
// TODO(@mitchellh): remove
SSHKeyPath string `mapstructure:"ssh_key_path"`
SSHWaitTimeout time.Duration `mapstructure:"ssh_wait_timeout"`
} }
func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error { func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error {
...@@ -32,37 +30,19 @@ func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error { ...@@ -32,37 +30,19 @@ func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error {
c.SSHHostPortMax = 4444 c.SSHHostPortMax = 4444
} }
if c.SSHPort == 0 { // TODO: backwards compatibility, write fixer instead
c.SSHPort = 22
}
if c.RawSSHWaitTimeout == "" {
c.RawSSHWaitTimeout = "20m"
}
var errs []error
if c.SSHKeyPath != "" { if c.SSHKeyPath != "" {
if _, err := os.Stat(c.SSHKeyPath); err != nil { c.Comm.SSHPrivateKey = c.SSHKeyPath
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) }
} else if _, err := commonssh.FileSigner(c.SSHKeyPath); err != nil { if c.SSHWaitTimeout != 0 {
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err)) c.Comm.SSHTimeout = c.SSHWaitTimeout
}
} }
errs := c.Comm.Prepare(ctx)
if c.SSHHostPortMin > c.SSHHostPortMax { if c.SSHHostPortMin > c.SSHHostPortMax {
errs = append(errs, errs = append(errs,
errors.New("ssh_host_port_min must be less than ssh_host_port_max")) errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
} }
if c.SSHUser == "" {
errs = append(errs, errors.New("An ssh_username must be specified."))
}
var err error
c.SSHWaitTimeout, err = time.ParseDuration(c.RawSSHWaitTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
}
return errs return errs
} }
...@@ -4,11 +4,15 @@ import ( ...@@ -4,11 +4,15 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"github.com/mitchellh/packer/helper/communicator"
) )
func testSSHConfig() *SSHConfig { func testSSHConfig() *SSHConfig {
return &SSHConfig{ return &SSHConfig{
SSHUser: "foo", Comm: communicator.Config{
SSHUsername: "foo",
},
} }
} }
...@@ -27,8 +31,8 @@ func TestSSHConfigPrepare(t *testing.T) { ...@@ -27,8 +31,8 @@ func TestSSHConfigPrepare(t *testing.T) {
t.Errorf("bad max ssh host port: %d", c.SSHHostPortMax) t.Errorf("bad max ssh host port: %d", c.SSHHostPortMax)
} }
if c.SSHPort != 22 { if c.Comm.SSHPort != 22 {
t.Errorf("bad ssh port: %d", c.SSHPort) t.Errorf("bad ssh port: %d", c.Comm.SSHPort)
} }
} }
...@@ -109,46 +113,14 @@ func TestSSHConfigPrepare_SSHUser(t *testing.T) { ...@@ -109,46 +113,14 @@ func TestSSHConfigPrepare_SSHUser(t *testing.T) {
var errs []error var errs []error
c = testSSHConfig() c = testSSHConfig()
c.SSHUser = "" c.Comm.SSHUsername = ""
errs = c.Prepare(testConfigTemplate(t)) errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 { if len(errs) == 0 {
t.Fatalf("should have error") t.Fatalf("should have error")
} }
c = testSSHConfig() c = testSSHConfig()
c.SSHUser = "exists" c.Comm.SSHUsername = "exists"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
}
func TestSSHConfigPrepare_SSHWaitTimeout(t *testing.T) {
var c *SSHConfig
var errs []error
// Defaults
c = testSSHConfig()
c.RawSSHWaitTimeout = ""
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
if c.RawSSHWaitTimeout != "20m" {
t.Fatalf("bad value: %s", c.RawSSHWaitTimeout)
}
// Test with a bad value
c = testSSHConfig()
c.RawSSHWaitTimeout = "this is not good"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 {
t.Fatal("should have error")
}
// Test with a good one
c = testSSHConfig()
c.RawSSHWaitTimeout = "5s"
errs = c.Prepare(testConfigTemplate(t)) errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 { if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs) t.Fatalf("should not have error: %#v", errs)
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common" vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
...@@ -253,7 +254,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -253,7 +254,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}, },
new(vboxcommon.StepAttachFloppy), new(vboxcommon.StepAttachFloppy),
&vboxcommon.StepForwardSSH{ &vboxcommon.StepForwardSSH{
GuestPort: b.config.SSHPort, GuestPort: uint(b.config.SSHConfig.Comm.SSHPort),
HostPortMin: b.config.SSHHostPortMin, HostPortMin: b.config.SSHHostPortMin,
HostPortMax: b.config.SSHHostPortMax, HostPortMax: b.config.SSHHostPortMax,
SkipNatMapping: b.config.SSHSkipNatMapping, SkipNatMapping: b.config.SSHSkipNatMapping,
...@@ -271,10 +272,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -271,10 +272,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VMName: b.config.VMName, VMName: b.config.VMName,
Ctx: b.config.ctx, Ctx: b.config.ctx,
}, },
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: vboxcommon.SSHAddress, Config: &b.config.SSHConfig.Comm,
SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig), Host: vboxcommon.CommHost,
SSHWaitTimeout: b.config.SSHWaitTimeout, SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig),
SSHPort: vboxcommon.SSHPort,
}, },
&vboxcommon.StepUploadVersion{ &vboxcommon.StepUploadVersion{
Path: b.config.VBoxVersionFile, Path: b.config.VBoxVersionFile,
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common" vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
) )
...@@ -82,7 +83,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -82,7 +83,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}, },
new(vboxcommon.StepAttachFloppy), new(vboxcommon.StepAttachFloppy),
&vboxcommon.StepForwardSSH{ &vboxcommon.StepForwardSSH{
GuestPort: b.config.SSHPort, GuestPort: uint(b.config.SSHConfig.Comm.SSHPort),
HostPortMin: b.config.SSHHostPortMin, HostPortMin: b.config.SSHHostPortMin,
HostPortMax: b.config.SSHHostPortMax, HostPortMax: b.config.SSHHostPortMax,
SkipNatMapping: b.config.SSHSkipNatMapping, SkipNatMapping: b.config.SSHSkipNatMapping,
...@@ -100,10 +101,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -100,10 +101,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VMName: b.config.VMName, VMName: b.config.VMName,
Ctx: b.config.ctx, Ctx: b.config.ctx,
}, },
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: vboxcommon.SSHAddress, Config: &b.config.SSHConfig.Comm,
SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig), Host: vboxcommon.CommHost,
SSHWaitTimeout: b.config.SSHWaitTimeout, SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig),
SSHPort: vboxcommon.SSHPort,
}, },
&vboxcommon.StepUploadVersion{ &vboxcommon.StepUploadVersion{
Path: b.config.VBoxVersionFile, Path: b.config.VBoxVersionFile,
......
...@@ -29,9 +29,9 @@ type Driver interface { ...@@ -29,9 +29,9 @@ type Driver interface {
// Checks if the VMX file at the given path is running. // Checks if the VMX file at the given path is running.
IsRunning(string) (bool, error) IsRunning(string) (bool, error)
// SSHAddress returns the SSH address for the VM that is being // CommHost returns the host address for the VM that is being
// managed by this driver. // managed by this driver.
SSHAddress(multistep.StateBag) (string, error) CommHost(multistep.StateBag) (string, error)
// Start starts a VM specified by the path to the VMX given. // Start starts a VM specified by the path to the VMX given.
Start(string, bool) error Start(string, bool) error
......
...@@ -69,8 +69,8 @@ func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) { ...@@ -69,8 +69,8 @@ func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) {
return false, nil return false, nil
} }
func (d *Fusion5Driver) SSHAddress(state multistep.StateBag) (string, error) { func (d *Fusion5Driver) CommHost(state multistep.StateBag) (string, error) {
return SSHAddressFunc(d.SSHConfig)(state) return CommHost(d.SSHConfig)(state)
} }
func (d *Fusion5Driver) Start(vmxPath string, headless bool) error { func (d *Fusion5Driver) Start(vmxPath string, headless bool) error {
......
...@@ -29,10 +29,10 @@ type DriverMock struct { ...@@ -29,10 +29,10 @@ type DriverMock struct {
IsRunningResult bool IsRunningResult bool
IsRunningErr error IsRunningErr error
SSHAddressCalled bool CommHostCalled bool
SSHAddressState multistep.StateBag CommHostState multistep.StateBag
SSHAddressResult string CommHostResult string
SSHAddressErr error CommHostErr error
StartCalled bool StartCalled bool
StartPath string StartPath string
...@@ -92,10 +92,10 @@ func (d *DriverMock) IsRunning(path string) (bool, error) { ...@@ -92,10 +92,10 @@ func (d *DriverMock) IsRunning(path string) (bool, error) {
return d.IsRunningResult, d.IsRunningErr return d.IsRunningResult, d.IsRunningErr
} }
func (d *DriverMock) SSHAddress(state multistep.StateBag) (string, error) { func (d *DriverMock) CommHost(state multistep.StateBag) (string, error) {
d.SSHAddressCalled = true d.CommHostCalled = true
d.SSHAddressState = state d.CommHostState = state
return d.SSHAddressResult, d.SSHAddressErr return d.CommHostResult, d.CommHostErr
} }
func (d *DriverMock) Start(path string, headless bool) error { func (d *DriverMock) Start(path string, headless bool) error {
......
...@@ -97,8 +97,8 @@ func (d *Player5Driver) IsRunning(vmxPath string) (bool, error) { ...@@ -97,8 +97,8 @@ func (d *Player5Driver) IsRunning(vmxPath string) (bool, error) {
return false, nil return false, nil
} }
func (d *Player5Driver) SSHAddress(state multistep.StateBag) (string, error) { func (d *Player5Driver) CommHost(state multistep.StateBag) (string, error) {
return SSHAddressFunc(d.SSHConfig)(state) return CommHost(d.SSHConfig)(state)
} }
func (d *Player5Driver) Start(vmxPath string, headless bool) error { func (d *Player5Driver) Start(vmxPath string, headless bool) error {
......
...@@ -70,8 +70,8 @@ func (d *Workstation9Driver) IsRunning(vmxPath string) (bool, error) { ...@@ -70,8 +70,8 @@ func (d *Workstation9Driver) IsRunning(vmxPath string) (bool, error) {
return false, nil return false, nil
} }
func (d *Workstation9Driver) SSHAddress(state multistep.StateBag) (string, error) { func (d *Workstation9Driver) CommHost(state multistep.StateBag) (string, error) {
return SSHAddressFunc(d.SSHConfig)(state) return CommHost(d.SSHConfig)(state)
} }
func (d *Workstation9Driver) Start(vmxPath string, headless bool) error { func (d *Workstation9Driver) Start(vmxPath string, headless bool) error {
......
...@@ -13,13 +13,13 @@ import ( ...@@ -13,13 +13,13 @@ import (
gossh "golang.org/x/crypto/ssh" gossh "golang.org/x/crypto/ssh"
) )
func SSHAddressFunc(config *SSHConfig) func(multistep.StateBag) (string, error) { func CommHost(config *SSHConfig) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) { return func(state multistep.StateBag) (string, error) {
driver := state.Get("driver").(Driver) driver := state.Get("driver").(Driver)
vmxPath := state.Get("vmx_path").(string) vmxPath := state.Get("vmx_path").(string)
if config.SSHHost != "" { if config.Comm.SSHHost != "" {
return fmt.Sprintf("%s:%d", config.SSHHost, config.SSHPort), nil return config.Comm.SSHHost, nil
} }
log.Println("Lookup up IP information...") log.Println("Lookup up IP information...")
...@@ -62,20 +62,20 @@ func SSHAddressFunc(config *SSHConfig) func(multistep.StateBag) (string, error) ...@@ -62,20 +62,20 @@ func SSHAddressFunc(config *SSHConfig) func(multistep.StateBag) (string, error)
} }
log.Printf("Detected IP: %s", ipAddress) log.Printf("Detected IP: %s", ipAddress)
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil return ipAddress, nil
} }
} }
func SSHConfigFunc(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) { func SSHConfigFunc(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) {
return func(state multistep.StateBag) (*gossh.ClientConfig, error) { return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
auth := []gossh.AuthMethod{ auth := []gossh.AuthMethod{
gossh.Password(config.SSHPassword), gossh.Password(config.Comm.SSHPassword),
gossh.KeyboardInteractive( gossh.KeyboardInteractive(
ssh.PasswordKeyboardInteractive(config.SSHPassword)), ssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
} }
if config.SSHKeyPath != "" { if config.Comm.SSHPrivateKey != "" {
signer, err := commonssh.FileSigner(config.SSHKeyPath) signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -84,7 +84,7 @@ func SSHConfigFunc(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientCon ...@@ -84,7 +84,7 @@ func SSHConfigFunc(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientCon
} }
return &gossh.ClientConfig{ return &gossh.ClientConfig{
User: config.SSHUser, User: config.Comm.SSHUsername,
Auth: auth, Auth: auth,
}, nil }, nil
} }
......
package common package common
import ( import (
"errors"
"fmt"
"net"
"os"
"time" "time"
commonssh "github.com/mitchellh/packer/common/ssh" "github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
) )
type SSHConfig struct { type SSHConfig struct {
SSHUser string `mapstructure:"ssh_username"` Comm communicator.Config `mapstructure:",squash"`
SSHKeyPath string `mapstructure:"ssh_key_path"`
SSHPassword string `mapstructure:"ssh_password"`
SSHHost string `mapstructure:"ssh_host"`
SSHPort uint `mapstructure:"ssh_port"`
SSHSkipRequestPty bool `mapstructure:"ssh_skip_request_pty"`
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
SSHWaitTimeout time.Duration // These are deprecated, but we keep them around for BC
// TODO(@mitchellh): remove
SSHKeyPath string `mapstructure:"ssh_key_path"`
SSHSkipRequestPty bool `mapstructure:"ssh_skip_request_pty"`
SSHWaitTimeout time.Duration `mapstructure:"ssh_wait_timeout"`
} }
func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error { func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error {
if c.SSHPort == 0 { // TODO: backwards compatibility, write fixer instead
c.SSHPort = 22
}
if c.RawSSHWaitTimeout == "" {
c.RawSSHWaitTimeout = "20m"
}
var errs []error
if c.SSHKeyPath != "" { if c.SSHKeyPath != "" {
if _, err := os.Stat(c.SSHKeyPath); err != nil { c.Comm.SSHPrivateKey = c.SSHKeyPath
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
} else if _, err := commonssh.FileSigner(c.SSHKeyPath); err != nil {
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
}
} }
if c.SSHWaitTimeout != 0 {
if c.SSHHost != "" { c.Comm.SSHTimeout = c.SSHWaitTimeout
if ip := net.ParseIP(c.SSHHost); ip == nil {
if _, err := net.LookupHost(c.SSHHost); err != nil {
errs = append(errs, errors.New("ssh_host is an invalid IP or hostname"))
}
}
}
if c.SSHUser == "" {
errs = append(errs, errors.New("An ssh_username must be specified."))
} }
if c.SSHSkipRequestPty {
var err error c.Comm.SSHPty = false
c.SSHWaitTimeout, err = time.ParseDuration(c.RawSSHWaitTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_wait_timeout: %s", err))
} }
return errs return c.Comm.Prepare(ctx)
} }
...@@ -4,11 +4,15 @@ import ( ...@@ -4,11 +4,15 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"github.com/mitchellh/packer/helper/communicator"
) )
func testSSHConfig() *SSHConfig { func testSSHConfig() *SSHConfig {
return &SSHConfig{ return &SSHConfig{
SSHUser: "foo", Comm: communicator.Config{
SSHUsername: "foo",
},
} }
} }
...@@ -19,8 +23,8 @@ func TestSSHConfigPrepare(t *testing.T) { ...@@ -19,8 +23,8 @@ func TestSSHConfigPrepare(t *testing.T) {
t.Fatalf("err: %#v", errs) t.Fatalf("err: %#v", errs)
} }
if c.SSHPort != 22 { if c.Comm.SSHPort != 22 {
t.Errorf("bad ssh port: %d", c.SSHPort) t.Errorf("bad ssh port: %d", c.Comm.SSHPort)
} }
} }
...@@ -73,57 +77,6 @@ func TestSSHConfigPrepare_SSHKeyPath(t *testing.T) { ...@@ -73,57 +77,6 @@ func TestSSHConfigPrepare_SSHKeyPath(t *testing.T) {
} }
} }
func TestSSHConfigPrepare_SSHUser(t *testing.T) {
var c *SSHConfig
var errs []error
c = testSSHConfig()
c.SSHUser = ""
errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 {
t.Fatalf("should have error")
}
c = testSSHConfig()
c.SSHUser = "exists"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
}
func TestSSHConfigPrepare_SSHWaitTimeout(t *testing.T) {
var c *SSHConfig
var errs []error
// Defaults
c = testSSHConfig()
c.RawSSHWaitTimeout = ""
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
if c.RawSSHWaitTimeout != "20m" {
t.Fatalf("bad value: %s", c.RawSSHWaitTimeout)
}
// Test with a bad value
c = testSSHConfig()
c.RawSSHWaitTimeout = "this is not good"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) == 0 {
t.Fatal("should have error")
}
// Test with a good one
c = testSSHConfig()
c.RawSSHWaitTimeout = "5s"
errs = c.Prepare(testConfigTemplate(t))
if len(errs) > 0 {
t.Fatalf("should not have error: %#v", errs)
}
}
const testPem = ` const testPem = `
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
......
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
vmwcommon "github.com/mitchellh/packer/builder/vmware/common" vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
...@@ -298,11 +299,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -298,11 +299,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VMName: b.config.VMName, VMName: b.config.VMName,
Ctx: b.config.ctx, Ctx: b.config.ctx,
}, },
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: driver.SSHAddress, Config: &b.config.SSHConfig.Comm,
SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig), Host: driver.CommHost,
SSHWaitTimeout: b.config.SSHWaitTimeout, SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig),
Pty: !b.config.SSHSkipRequestPty,
}, },
&vmwcommon.StepUploadTools{ &vmwcommon.StepUploadTools{
RemoteType: b.config.RemoteType, RemoteType: b.config.RemoteType,
......
package iso package iso
import ( import (
"github.com/mitchellh/packer/packer"
"io/ioutil" "io/ioutil"
"os" "os"
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/mitchellh/packer/packer"
) )
func testConfig() map[string]interface{} { func testConfig() map[string]interface{} {
...@@ -138,10 +138,6 @@ func TestBuilderPrepare_Defaults(t *testing.T) { ...@@ -138,10 +138,6 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
t.Errorf("bad Version: %s", b.config.Version) t.Errorf("bad Version: %s", b.config.Version)
} }
if b.config.SSHWaitTimeout != (20 * time.Minute) {
t.Errorf("bad wait timeout: %s", b.config.SSHWaitTimeout)
}
if b.config.VMName != "packer-foo" { if b.config.VMName != "packer-foo" {
t.Errorf("bad vm name: %s", b.config.VMName) t.Errorf("bad vm name: %s", b.config.VMName)
} }
......
...@@ -218,7 +218,7 @@ func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint, error) { ...@@ -218,7 +218,7 @@ func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint, error) {
return d.Host, vncPort, nil return d.Host, vncPort, nil
} }
func (d *ESX5Driver) SSHAddress(state multistep.StateBag) (string, error) { func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) {
config := state.Get("config").(*Config) config := state.Get("config").(*Config)
if address, ok := state.GetOk("vm_address"); ok { if address, ok := state.GetOk("vm_address"); ok {
...@@ -253,7 +253,7 @@ func (d *ESX5Driver) SSHAddress(state multistep.StateBag) (string, error) { ...@@ -253,7 +253,7 @@ func (d *ESX5Driver) SSHAddress(state multistep.StateBag) (string, error) {
return "", errors.New("VM network port found, but no IP address") return "", errors.New("VM network port found, but no IP address")
} }
address := fmt.Sprintf("%s:%d", record["IPAddress"], config.SSHPort) address := record["IPAddress"]
state.Put("vm_address", address) state.Put("vm_address", address)
return address, nil return address, nil
} }
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
vmwcommon "github.com/mitchellh/packer/builder/vmware/common" vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
) )
...@@ -90,11 +91,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -90,11 +91,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
VMName: b.config.VMName, VMName: b.config.VMName,
Ctx: b.config.ctx, Ctx: b.config.ctx,
}, },
&common.StepConnectSSH{ &communicator.StepConnect{
SSHAddress: driver.SSHAddress, Config: &b.config.SSHConfig.Comm,
SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig), Host: driver.CommHost,
SSHWaitTimeout: b.config.SSHWaitTimeout, SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig),
Pty: !b.config.SSHSkipRequestPty,
}, },
&vmwcommon.StepUploadTools{ &vmwcommon.StepUploadTools{
RemoteType: b.config.RemoteType, RemoteType: b.config.RemoteType,
......
package common
import (
"github.com/mitchellh/multistep"
"testing"
)
func TestStepConnectSSH_Impl(t *testing.T) {
var raw interface{}
raw = new(StepConnectSSH)
if _, ok := raw.(multistep.Step); !ok {
t.Fatalf("connect ssh should be a step")
}
}
package communicator
import (
"errors"
"fmt"
"os"
"time"
"github.com/mitchellh/packer/template/interpolate"
)
// Config is the common configuration that communicators allow within
// a builder.
type Config struct {
Type string `mapstructure:"communicator"`
SSHHost string `mapstructure:"ssh_host"`
SSHPort int `mapstructure:"ssh_port"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPassword string `mapstructure:"ssh_password"`
SSHPrivateKey string `mapstructure:"ssh_private_key_file"`
SSHPty bool `mapstructure:"ssh_pty"`
SSHTimeout time.Duration `mapstructure:"ssh_timeout"`
}
func (c *Config) Prepare(ctx *interpolate.Context) []error {
if c.Type == "" {
c.Type = "ssh"
}
if c.SSHPort == 0 {
c.SSHPort = 22
}
if c.SSHTimeout == 0 {
c.SSHTimeout = 5 * time.Minute
}
// Validation
var errs []error
if c.Type == "ssh" {
if c.SSHUsername == "" {
errs = append(errs, errors.New("An ssh_username must be specified"))
}
if c.SSHPrivateKey != "" {
if _, err := os.Stat(c.SSHPrivateKey); err != nil {
errs = append(errs, fmt.Errorf(
"ssh_private_key_file is invalid: %s", err))
} else if _, err := SSHFileSigner(c.SSHPrivateKey); err != nil {
errs = append(errs, fmt.Errorf(
"ssh_private_key_file is invalid: %s", err))
}
}
}
return errs
}
package communicator
import (
"testing"
"github.com/mitchellh/packer/template/interpolate"
)
func testConfig() *Config {
return &Config{
SSHUsername: "root",
}
}
func TestConfigType(t *testing.T) {
c := testConfig()
if err := c.Prepare(testContext(t)); len(err) > 0 {
t.Fatalf("bad: %#v", err)
}
if c.Type != "ssh" {
t.Fatalf("bad: %#v", c)
}
}
func TestConfig_none(t *testing.T) {
c := &Config{Type: "none"}
if err := c.Prepare(testContext(t)); len(err) > 0 {
t.Fatalf("bad: %#v", err)
}
}
func testContext(t *testing.T) *interpolate.Context {
return nil
}
package communicator
import (
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"golang.org/x/crypto/ssh"
)
// SSHFileSigner returns an ssh.Signer for a key file.
func SSHFileSigner(path string) (ssh.Signer, 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
}
// We parse the private key on our own first so that we can
// show a nicer error if the private key has a password.
block, _ := pem.Decode(keyBytes)
if block == nil {
return nil, fmt.Errorf(
"Failed to read key '%s': no key found", path)
}
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
return nil, fmt.Errorf(
"Failed to read key '%s': password protected keys are\n"+
"not supported. Please decrypt the key prior to use.", path)
}
signer, err := ssh.ParsePrivateKey(keyBytes)
if err != nil {
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
}
return signer, nil
}
package communicator
import (
"fmt"
"log"
"github.com/mitchellh/multistep"
gossh "golang.org/x/crypto/ssh"
)
// StepConnect is a multistep Step implementation that connects to
// the proper communicator and stores it in the "communicator" key in the
// state bag.
type StepConnect struct {
// Config is the communicator config struct
Config *Config
// Host should return a host that can be connected to for communicator
// connections.
Host func(multistep.StateBag) (string, error)
// The fields below are callbacks to assist with connecting to SSH.
//
// SSHConfig should return the default configuration for
// connecting via SSH.
SSHConfig func(multistep.StateBag) (*gossh.ClientConfig, error)
SSHPort func(multistep.StateBag) (int, error)
substep multistep.Step
}
func (s *StepConnect) Run(state multistep.StateBag) multistep.StepAction {
typeMap := map[string]multistep.Step{
"none": nil,
"ssh": &StepConnectSSH{
Config: s.Config,
Host: s.Host,
SSHConfig: s.SSHConfig,
SSHPort: s.SSHPort,
},
}
step, ok := typeMap[s.Config.Type]
if !ok {
state.Put("error", fmt.Errorf("unknown communicator type: %s", s.Config.Type))
return multistep.ActionHalt
}
if step == nil {
log.Printf("[INFO] communicator disabled, will not connect")
return multistep.ActionContinue
}
s.substep = step
return s.substep.Run(state)
}
func (s *StepConnect) Cleanup(state multistep.StateBag) {
if s.substep != nil {
s.substep.Cleanup(state)
}
}
package common package communicator
import ( import (
"errors" "errors"
...@@ -13,32 +13,15 @@ import ( ...@@ -13,32 +13,15 @@ import (
gossh "golang.org/x/crypto/ssh" gossh "golang.org/x/crypto/ssh"
) )
// StepConnectSSH is a multistep Step implementation that waits for SSH // StepConnectSSH is a step that only connects to SSH.
// to become available. It gets the connection information from a single
// configuration when creating the step.
// //
// Uses: // In general, you should use StepConnect.
// ui packer.Ui
//
// Produces:
// communicator packer.Communicator
type StepConnectSSH struct { type StepConnectSSH struct {
// SSHAddress is a function that returns the TCP address to connect to // All the fields below are documented on StepConnect
// for SSH. This is a function so that you can query information Config *Config
// if necessary for this address. Host func(multistep.StateBag) (string, error)
SSHAddress func(multistep.StateBag) (string, error)
// SSHConfig is a function that returns the proper client configuration
// for SSH access.
SSHConfig func(multistep.StateBag) (*gossh.ClientConfig, error) SSHConfig func(multistep.StateBag) (*gossh.ClientConfig, error)
SSHPort func(multistep.StateBag) (int, error)
// SSHWaitTimeout is the total timeout to wait for SSH to become available.
SSHWaitTimeout time.Duration
// Pty, if true, will request a Pty from the remote end.
Pty bool
comm packer.Communicator
} }
func (s *StepConnectSSH) Run(state multistep.StateBag) multistep.StepAction { func (s *StepConnectSSH) Run(state multistep.StateBag) multistep.StepAction {
...@@ -55,8 +38,8 @@ func (s *StepConnectSSH) Run(state multistep.StateBag) multistep.StepAction { ...@@ -55,8 +38,8 @@ func (s *StepConnectSSH) Run(state multistep.StateBag) multistep.StepAction {
waitDone <- true waitDone <- true
}() }()
log.Printf("Waiting for SSH, up to timeout: %s", s.SSHWaitTimeout) log.Printf("[INFO] Waiting for SSH, up to timeout: %s", s.Config.SSHTimeout)
timeout := time.After(s.SSHWaitTimeout) timeout := time.After(s.Config.SSHTimeout)
WaitLoop: WaitLoop:
for { for {
// Wait for either SSH to become available, a timeout to occur, // Wait for either SSH to become available, a timeout to occur,
...@@ -70,7 +53,6 @@ WaitLoop: ...@@ -70,7 +53,6 @@ WaitLoop:
} }
ui.Say("Connected to SSH!") ui.Say("Connected to SSH!")
s.comm = comm
state.Put("communicator", comm) state.Put("communicator", comm)
break WaitLoop break WaitLoop
case <-timeout: case <-timeout:
...@@ -84,7 +66,7 @@ WaitLoop: ...@@ -84,7 +66,7 @@ WaitLoop:
// The step sequence was cancelled, so cancel waiting for SSH // The step sequence was cancelled, so cancel waiting for SSH
// and just start the halting process. // and just start the halting process.
close(cancel) close(cancel)
log.Println("Interrupt detected, quitting waiting for SSH.") log.Println("[WARN] Interrupt detected, quitting waiting for SSH.")
return multistep.ActionHalt return multistep.ActionHalt
} }
} }
...@@ -106,7 +88,7 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru ...@@ -106,7 +88,7 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
if !first { if !first {
select { select {
case <-cancel: case <-cancel:
log.Println("SSH wait cancelled. Exiting loop.") log.Println("[DEBUG] SSH wait cancelled. Exiting loop.")
return nil, errors.New("SSH wait cancelled") return nil, errors.New("SSH wait cancelled")
case <-time.After(5 * time.Second): case <-time.After(5 * time.Second):
} }
...@@ -114,24 +96,34 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru ...@@ -114,24 +96,34 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
first = false first = false
// First we request the TCP connection information // First we request the TCP connection information
address, err := s.SSHAddress(state) host, err := s.Host(state)
if err != nil { if err != nil {
log.Printf("Error getting SSH address: %s", err) log.Printf("[DEBUG] Error getting SSH address: %s", err)
continue continue
} }
port := s.Config.SSHPort
if s.SSHPort != nil {
port, err = s.SSHPort(state)
if err != nil {
log.Printf("[DEBUG] Error getting SSH port: %s", err)
continue
}
}
// Retrieve the SSH configuration // Retrieve the SSH configuration
sshConfig, err := s.SSHConfig(state) sshConfig, err := s.SSHConfig(state)
if err != nil { if err != nil {
log.Printf("Error getting SSH config: %s", err) log.Printf("[DEBUG] Error getting SSH config: %s", err)
continue continue
} }
address := fmt.Sprintf("%s:%d", host, port)
// Attempt to connect to SSH port // Attempt to connect to SSH port
connFunc := ssh.ConnectFunc("tcp", address) connFunc := ssh.ConnectFunc("tcp", address)
nc, err := connFunc() nc, err := connFunc()
if err != nil { if err != nil {
log.Printf("TCP connection to SSH ip/port failed: %s", err) log.Printf("[DEBUG] TCP connection to SSH ip/port failed: %s", err)
continue continue
} }
nc.Close() nc.Close()
...@@ -140,19 +132,20 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru ...@@ -140,19 +132,20 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
config := &ssh.Config{ config := &ssh.Config{
Connection: connFunc, Connection: connFunc,
SSHConfig: sshConfig, SSHConfig: sshConfig,
Pty: s.Pty, Pty: s.Config.SSHPty,
} }
log.Println("Attempting SSH connection...") log.Println("[INFO] Attempting SSH connection...")
comm, err = ssh.New(address, config) comm, err = ssh.New(address, config)
if err != nil { if err != nil {
log.Printf("SSH handshake err: %s", err) log.Printf("[DEBUG] SSH handshake err: %s", err)
// Only count this as an attempt if we were able to attempt // Only count this as an attempt if we were able to attempt
// to authenticate. Note this is very brittle since it depends // to authenticate. Note this is very brittle since it depends
// on the string of the error... but I don't see any other way. // on the string of the error... but I don't see any other way.
if strings.Contains(err.Error(), "authenticate") { if strings.Contains(err.Error(), "authenticate") {
log.Printf("Detected authentication error. Increasing handshake attempts.") log.Printf(
"[DEBUG] Detected authentication error. Increasing handshake attempts.")
handshakeAttempts += 1 handshakeAttempts += 1
} }
......
package communicator
import (
"bytes"
"testing"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
func TestStepConnect_impl(t *testing.T) {
var _ multistep.Step = new(StepConnect)
}
func TestStepConnect_none(t *testing.T) {
state := testState(t)
step := &StepConnect{
Config: &Config{
Type: "none",
},
}
defer step.Cleanup(state)
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
}
func testState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("hook", &packer.MockHook{})
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
package communicator
import (
"io/ioutil"
"testing"
)
func TestPEM(t *testing.T) string {
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Write([]byte(TestPEMContents))
tf.Close()
return tf.Name()
}
const TestPEMContents = `
-----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-----
`
...@@ -66,6 +66,7 @@ func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error { ...@@ -66,6 +66,7 @@ func Decode(target interface{}, config *DecodeOpts, raws ...interface{}) error {
DecodeHook: mapstructure.ComposeDecodeHookFunc( DecodeHook: mapstructure.ComposeDecodeHookFunc(
uint8ToStringHook, uint8ToStringHook,
mapstructure.StringToSliceHookFunc(","), mapstructure.StringToSliceHookFunc(","),
mapstructure.StringToTimeDurationHookFunc(),
), ),
}) })
if err != nil { if err != nil {
......
...@@ -3,6 +3,7 @@ package config ...@@ -3,6 +3,7 @@ package config
import ( import (
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
) )
...@@ -11,6 +12,7 @@ func TestDecode(t *testing.T) { ...@@ -11,6 +12,7 @@ func TestDecode(t *testing.T) {
type Target struct { type Target struct {
Name string Name string
Address string Address string
Time time.Duration
} }
cases := map[string]struct { cases := map[string]struct {
...@@ -22,10 +24,12 @@ func TestDecode(t *testing.T) { ...@@ -22,10 +24,12 @@ func TestDecode(t *testing.T) {
[]interface{}{ []interface{}{
map[string]interface{}{ map[string]interface{}{
"name": "bar", "name": "bar",
"time": "5s",
}, },
}, },
&Target{ &Target{
Name: "bar", Name: "bar",
Time: 5 * time.Second,
}, },
nil, nil,
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment