Merge pull request #2235 from mitchellh/b-refactor-ssh
Communicator refactor, shared code for communicator connect
This commit is contained in:
commit
a832e1264b
|
@ -4,9 +4,9 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/common/uuid"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
|
@ -21,11 +21,6 @@ type RunConfig struct {
|
|||
SourceAmi string `mapstructure:"source_ami"`
|
||||
SpotPrice string `mapstructure:"spot_price"`
|
||||
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"`
|
||||
SecurityGroupIds []string `mapstructure:"security_group_ids"`
|
||||
SubnetId string `mapstructure:"subnet_id"`
|
||||
|
@ -34,27 +29,19 @@ type RunConfig struct {
|
|||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
|
||||
// Unexported fields that are calculated from others
|
||||
sshTimeout time.Duration
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
c.TemporaryKeyPairName = fmt.Sprintf(
|
||||
"packer %s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
// Validation
|
||||
var errs []error
|
||||
errs := c.Comm.Prepare(ctx)
|
||||
if c.SourceAmi == "" {
|
||||
errs = append(errs, errors.New("A source_ami must be specified"))
|
||||
}
|
||||
|
@ -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 != "" {
|
||||
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
|
||||
} else if c.UserDataFile != "" {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
func (c *RunConfig) SSHTimeout() time.Duration {
|
||||
return c.sshTimeout
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -19,7 +21,10 @@ func testConfig() *RunConfig {
|
|||
return &RunConfig{
|
||||
SourceAmi: "abcd",
|
||||
InstanceType: "m1.small",
|
||||
SSHUsername: "root",
|
||||
|
||||
Comm: communicator.Config{
|
||||
SSHUsername: "foo",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,41 +67,28 @@ func TestRunConfigPrepare_SpotAuto(t *testing.T) {
|
|||
|
||||
func TestRunConfigPrepare_SSHPort(t *testing.T) {
|
||||
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)
|
||||
if c.Comm.SSHPort != 22 {
|
||||
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
|
||||
}
|
||||
|
||||
c.SSHPort = 44
|
||||
c.Comm.SSHPort = 44
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.SSHPort != 44 {
|
||||
t.Fatalf("invalid value: %d", c.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHTimeout(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.RawSSHTimeout = ""
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c.RawSSHTimeout = "bad"
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
if c.Comm.SSHPort != 44 {
|
||||
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHUsername(t *testing.T) {
|
||||
c := testConfig()
|
||||
c.SSHUsername = ""
|
||||
c.Comm.SSHUsername = ""
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
"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.
|
||||
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) {
|
||||
for j := 0; j < 2; j++ {
|
||||
var host string
|
||||
|
@ -28,7 +28,7 @@ func SSHAddress(e *ec2.EC2, port int, private bool) func(multistep.StateBag) (st
|
|||
}
|
||||
|
||||
if host != "" {
|
||||
return fmt.Sprintf("%s:%d", host, port), nil
|
||||
return host, nil
|
||||
}
|
||||
|
||||
r, err := e.DescribeInstances(&ec2.DescribeInstancesInput{
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
|
@ -89,11 +90,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
Debug: b.config.PackerDebug,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
KeyPairName: b.config.TemporaryKeyPairName,
|
||||
PrivateKeyFile: b.config.SSHPrivateKeyFile,
|
||||
PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
|
||||
},
|
||||
&awscommon.StepSecurityGroup{
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
SSHPort: b.config.SSHPort,
|
||||
SSHPort: b.config.RunConfig.Comm.SSHPort,
|
||||
VpcId: b.config.VpcId,
|
||||
},
|
||||
&awscommon.StepRunSourceInstance{
|
||||
|
@ -112,11 +113,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
BlockDevices: b.config.BlockDevices,
|
||||
Tags: b.config.RunTags,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: awscommon.SSHAddress(
|
||||
ec2conn, b.config.SSHPort, b.config.SSHPrivateIp),
|
||||
SSHConfig: awscommon.SSHConfig(b.config.SSHUsername),
|
||||
SSHWaitTimeout: b.config.SSHTimeout(),
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: awscommon.SSHHost(
|
||||
ec2conn,
|
||||
b.config.SSHPrivateIp),
|
||||
SSHConfig: awscommon.SSHConfig(
|
||||
b.config.RunConfig.Comm.SSHUsername),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&stepStopInstance{SpotPrice: b.config.SpotPrice},
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/mitchellh/multistep"
|
||||
awscommon "github.com/mitchellh/packer/builder/amazon/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
|
@ -175,11 +176,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
Debug: b.config.PackerDebug,
|
||||
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
|
||||
KeyPairName: b.config.TemporaryKeyPairName,
|
||||
PrivateKeyFile: b.config.SSHPrivateKeyFile,
|
||||
PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
|
||||
},
|
||||
&awscommon.StepSecurityGroup{
|
||||
SecurityGroupIds: b.config.SecurityGroupIds,
|
||||
SSHPort: b.config.SSHPort,
|
||||
SSHPort: b.config.RunConfig.Comm.SSHPort,
|
||||
VpcId: b.config.VpcId,
|
||||
},
|
||||
&awscommon.StepRunSourceInstance{
|
||||
|
@ -197,11 +198,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
BlockDevices: b.config.BlockDevices,
|
||||
Tags: b.config.RunTags,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: awscommon.SSHAddress(
|
||||
ec2conn, b.config.SSHPort, b.config.SSHPrivateIp),
|
||||
SSHConfig: awscommon.SSHConfig(b.config.SSHUsername),
|
||||
SSHWaitTimeout: b.config.SSHTimeout(),
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: awscommon.SSHHost(
|
||||
ec2conn,
|
||||
b.config.SSHPrivateIp),
|
||||
SSHConfig: awscommon.SSHConfig(
|
||||
b.config.RunConfig.Comm.SSHUsername),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&StepUploadX509Cert{},
|
||||
|
|
|
@ -6,11 +6,11 @@ package digitalocean
|
|||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
@ -53,10 +53,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
},
|
||||
new(stepCreateDroplet),
|
||||
new(stepDropletInfo),
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: 5 * time.Minute,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
Host: commHost,
|
||||
SSHConfig: sshConfig,
|
||||
},
|
||||
new(common.StepProvision),
|
||||
new(stepShutdown),
|
||||
|
|
|
@ -3,6 +3,7 @@ package digitalocean
|
|||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
@ -163,8 +164,8 @@ func TestBuilderPrepare_SSHUsername(t *testing.T) {
|
|||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.SSHUsername != "root" {
|
||||
t.Errorf("invalid: %s", b.config.SSHUsername)
|
||||
if b.config.Comm.SSHUsername != "root" {
|
||||
t.Errorf("invalid: %s", b.config.Comm.SSHUsername)
|
||||
}
|
||||
|
||||
// Test set
|
||||
|
@ -178,52 +179,11 @@ func TestBuilderPrepare_SSHUsername(t *testing.T) {
|
|||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.SSHUsername != "foo" {
|
||||
t.Errorf("invalid: %s", b.config.SSHUsername)
|
||||
if b.config.Comm.SSHUsername != "foo" {
|
||||
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) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
@ -237,8 +197,8 @@ func TestBuilderPrepare_StateTimeout(t *testing.T) {
|
|||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.RawStateTimeout != "6m" {
|
||||
t.Errorf("invalid: %s", b.config.RawStateTimeout)
|
||||
if b.config.StateTimeout != 6*time.Minute {
|
||||
t.Errorf("invalid: %s", b.config.StateTimeout)
|
||||
}
|
||||
|
||||
// Test set
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/common/uuid"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
|
@ -16,6 +17,7 @@ import (
|
|||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
APIToken string `mapstructure:"api_token"`
|
||||
|
||||
|
@ -23,20 +25,11 @@ type Config struct {
|
|||
Size string `mapstructure:"size"`
|
||||
Image string `mapstructure:"image"`
|
||||
|
||||
PrivateNetworking bool `mapstructure:"private_networking"`
|
||||
SnapshotName string `mapstructure:"snapshot_name"`
|
||||
DropletName string `mapstructure:"droplet_name"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
SSHUsername string `mapstructure:"ssh_username"`
|
||||
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
|
||||
PrivateNetworking bool `mapstructure:"private_networking"`
|
||||
SnapshotName string `mapstructure:"snapshot_name"`
|
||||
StateTimeout time.Duration `mapstructure:"state_timeout"`
|
||||
DropletName string `mapstructure:"droplet_name"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
|
||||
ctx *interpolate.Context
|
||||
}
|
||||
|
@ -79,29 +72,22 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
|||
c.DropletName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
if c.SSHUsername == "" {
|
||||
if c.Comm.SSHUsername == "" {
|
||||
// Default to "root". You can override this if your
|
||||
// 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 == "" {
|
||||
// Default to 1 minute timeouts
|
||||
c.RawSSHTimeout = "1m"
|
||||
}
|
||||
|
||||
if c.RawStateTimeout == "" {
|
||||
if c.StateTimeout == 0 {
|
||||
// Default to 6 minute timeouts waiting for
|
||||
// desired state. i.e waiting for droplet to become active
|
||||
c.RawStateTimeout = "6m"
|
||||
c.StateTimeout = 6 * time.Minute
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
if es := c.Comm.Prepare(c.ctx); len(es) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
if c.APIToken == "" {
|
||||
// Required configurations that will display errors if not set
|
||||
errs = packer.MultiErrorAppend(
|
||||
|
@ -123,20 +109,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
|||
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 {
|
||||
return nil, nil, errs
|
||||
}
|
||||
|
|
|
@ -2,14 +2,14 @@ package digitalocean
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func sshAddress(state multistep.StateBag) (string, error) {
|
||||
config := state.Get("config").(Config)
|
||||
func commHost(state multistep.StateBag) (string, error) {
|
||||
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) {
|
||||
|
@ -22,7 +22,7 @@ func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
|||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: config.SSHUsername,
|
||||
User: config.Comm.SSHUsername,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
|
|
|
@ -18,7 +18,7 @@ func (s *stepDropletInfo) Run(state multistep.StateBag) multistep.StepAction {
|
|||
|
||||
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 {
|
||||
err := fmt.Errorf("Error waiting for droplet to become active: %s", err)
|
||||
state.Put("error", err)
|
||||
|
|
|
@ -42,7 +42,7 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction {
|
|||
}
|
||||
|
||||
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 {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
|
|
|
@ -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
|
||||
ui.Say("Waiting for snapshot to complete...")
|
||||
err = waitForDropletState("active", dropletId, client, c.stateTimeout)
|
||||
err = waitForDropletState("active", dropletId, client, c.StateTimeout)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error waiting for snapshot to complete: %s", err)
|
||||
state.Put("error", err)
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
|
@ -60,10 +61,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
&StepInstanceInfo{
|
||||
Debug: b.config.PackerDebug,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: b.config.sshTimeout,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
Host: commHost,
|
||||
SSHConfig: sshConfig,
|
||||
},
|
||||
new(common.StepProvision),
|
||||
new(StepTeardownInstance),
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/common/uuid"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
|
@ -17,6 +18,7 @@ import (
|
|||
// state of the config object.
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
AccountFile string `mapstructure:"account_file"`
|
||||
ProjectId string `mapstructure:"project_id"`
|
||||
|
@ -31,16 +33,12 @@ type Config struct {
|
|||
Network string `mapstructure:"network"`
|
||||
SourceImage string `mapstructure:"source_image"`
|
||||
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"`
|
||||
Tags []string `mapstructure:"tags"`
|
||||
Zone string `mapstructure:"zone"`
|
||||
|
||||
account accountFile
|
||||
privateKeyBytes []byte
|
||||
sshTimeout time.Duration
|
||||
stateTimeout time.Duration
|
||||
ctx *interpolate.Context
|
||||
}
|
||||
|
@ -88,20 +86,12 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
|||
c.MachineType = "n1-standard-1"
|
||||
}
|
||||
|
||||
if c.RawSSHTimeout == "" {
|
||||
c.RawSSHTimeout = "5m"
|
||||
}
|
||||
|
||||
if c.RawStateTimeout == "" {
|
||||
c.RawStateTimeout = "5m"
|
||||
}
|
||||
|
||||
if c.SSHUsername == "" {
|
||||
c.SSHUsername = "root"
|
||||
}
|
||||
|
||||
if c.SSHPort == 0 {
|
||||
c.SSHPort = 22
|
||||
if c.Comm.SSHUsername == "" {
|
||||
c.Comm.SSHUsername = "root"
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
|
@ -122,14 +112,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
|||
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)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
|
|
|
@ -2,15 +2,14 @@ package googlecompute
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// sshAddress returns the ssh address.
|
||||
func sshAddress(state multistep.StateBag) (string, error) {
|
||||
config := state.Get("config").(*Config)
|
||||
func commHost(state multistep.StateBag) (string, error) {
|
||||
ipAddress := state.Get("instance_ip").(string)
|
||||
return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil
|
||||
return ipAddress, nil
|
||||
}
|
||||
|
||||
// sshConfig returns the ssh configuration.
|
||||
|
@ -24,7 +23,7 @@ func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
|||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: config.SSHUsername,
|
||||
User: config.Comm.SSHUsername,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
|
|
|
@ -32,7 +32,7 @@ func (config *Config) getInstanceMetadata(sshPublicKey string) map[string]string
|
|||
|
||||
// Merge any existing ssh keys with our public key
|
||||
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 {
|
||||
sshKeys = fmt.Sprintf("%s\n%s", sshKeys, confSshKeys)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package null
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
const BuilderId = "fnoeding.null"
|
||||
|
@ -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) {
|
||||
steps := []multistep.Step{
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: SSHAddress(b.config.Host, b.config.Port),
|
||||
SSHConfig: SSHConfig(b.config.SSHUsername, b.config.SSHPassword, b.config.SSHPrivateKeyFile),
|
||||
SSHWaitTimeout: 1 * time.Minute,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.CommConfig,
|
||||
Host: CommHost(b.config.CommConfig.SSHHost),
|
||||
SSHConfig: SSHConfig(
|
||||
b.config.CommConfig.SSHUsername,
|
||||
b.config.CommConfig.SSHPassword,
|
||||
b.config.CommConfig.SSHPrivateKey),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
|
@ -12,49 +13,40 @@ import (
|
|||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
SSHUsername string `mapstructure:"ssh_username"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHPrivateKeyFile string `mapstructure:"ssh_private_key_file"`
|
||||
CommConfig communicator.Config `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
var c Config
|
||||
|
||||
err := config.Decode(&c, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"run_command",
|
||||
},
|
||||
},
|
||||
Interpolate: true,
|
||||
InterpolateFilter: &interpolate.RenderFilter{},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if c.Port == 0 {
|
||||
c.Port = 22
|
||||
}
|
||||
|
||||
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,
|
||||
fmt.Errorf("host must be specified"))
|
||||
}
|
||||
|
||||
if c.SSHUsername == "" {
|
||||
if c.CommConfig.SSHUsername == "" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("ssh_username must be specified"))
|
||||
}
|
||||
|
||||
if c.SSHPassword == "" && c.SSHPrivateKeyFile == "" {
|
||||
if c.CommConfig.SSHPassword == "" && c.CommConfig.SSHPrivateKey == "" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
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,
|
||||
fmt.Errorf("only one of ssh_password and ssh_private_key_file must be specified"))
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
package null
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"host": "foo",
|
||||
"ssh_host": "foo",
|
||||
"ssh_username": "bar",
|
||||
"ssh_password": "baz",
|
||||
}
|
||||
|
@ -48,8 +51,8 @@ func TestConfigPrepare_port(t *testing.T) {
|
|||
// default port should be 22
|
||||
delete(raw, "port")
|
||||
c, warns, errs := NewConfig(raw)
|
||||
if c.Port != 22 {
|
||||
t.Fatalf("bad: port should default to 22, not %d", c.Port)
|
||||
if c.CommConfig.SSHPort != 22 {
|
||||
t.Fatalf("bad: port should default to 22, not %d", c.CommConfig.SSHPort)
|
||||
}
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
|
@ -58,12 +61,12 @@ func TestConfigPrepare_host(t *testing.T) {
|
|||
raw := testConfig()
|
||||
|
||||
// No host
|
||||
delete(raw, "host")
|
||||
delete(raw, "ssh_host")
|
||||
_, warns, errs := NewConfig(raw)
|
||||
testConfigErr(t, warns, errs)
|
||||
|
||||
// Good host
|
||||
raw["host"] = "good"
|
||||
raw["ssh_host"] = "good"
|
||||
_, warns, errs = NewConfig(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
}
|
||||
|
@ -97,7 +100,9 @@ func TestConfigPrepare_sshCredential(t *testing.T) {
|
|||
testConfigOk(t, warns, errs)
|
||||
|
||||
// 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")
|
||||
_, warns, errs = NewConfig(raw)
|
||||
testConfigOk(t, warns, errs)
|
||||
|
|
|
@ -8,11 +8,9 @@ import (
|
|||
"io/ioutil"
|
||||
)
|
||||
|
||||
// SSHAddress returns a function that can be given to the SSH communicator
|
||||
// for determining the SSH address
|
||||
func SSHAddress(host string, port int) func(multistep.StateBag) (string, error) {
|
||||
func CommHost(host string) func(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
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"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/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
|
@ -19,9 +20,10 @@ const BuilderId = "mitchellh.openstack"
|
|||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
AccessConfig `mapstructure:",squash"`
|
||||
ImageConfig `mapstructure:",squash"`
|
||||
RunConfig `mapstructure:",squash"`
|
||||
|
||||
AccessConfig `mapstructure:",squash"`
|
||||
ImageConfig `mapstructure:",squash"`
|
||||
RunConfig `mapstructure:",squash"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
@ -88,10 +90,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
FloatingIpPool: b.config.FloatingIpPool,
|
||||
FloatingIp: b.config.FloatingIp,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: SSHAddress(computeClient, b.config.SSHInterface, b.config.SSHPort),
|
||||
SSHConfig: SSHConfig(b.config.SSHUsername),
|
||||
SSHWaitTimeout: b.config.SSHTimeout(),
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
Host: CommHost(
|
||||
computeClient,
|
||||
b.config.SSHInterface),
|
||||
SSHConfig: SSHConfig(b.config.RunConfig.Comm.SSHUsername),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&stepCreateImage{},
|
||||
|
|
|
@ -2,21 +2,19 @@ package openstack
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// RunConfig contains configuration for running an instance from a source
|
||||
// image and details on how to access that launched image.
|
||||
type RunConfig struct {
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHInterface string `mapstructure:"ssh_interface"`
|
||||
|
||||
SourceImage string `mapstructure:"source_image"`
|
||||
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"`
|
||||
RackconnectWait bool `mapstructure:"rackconnect_wait"`
|
||||
FloatingIpPool string `mapstructure:"floating_ip_pool"`
|
||||
|
@ -27,23 +25,12 @@ type RunConfig struct {
|
|||
// Not really used, but here for BC
|
||||
OpenstackProvider string `mapstructure:"openstack_provider"`
|
||||
UseFloatingIp bool `mapstructure:"use_floating_ip"`
|
||||
|
||||
// Unexported fields that are calculated from others
|
||||
sshTimeout time.Duration
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
// Defaults
|
||||
if c.SSHUsername == "" {
|
||||
c.SSHUsername = "root"
|
||||
}
|
||||
|
||||
if c.SSHPort == 0 {
|
||||
c.SSHPort = 22
|
||||
}
|
||||
|
||||
if c.RawSSHTimeout == "" {
|
||||
c.RawSSHTimeout = "5m"
|
||||
if c.Comm.SSHUsername == "" {
|
||||
c.Comm.SSHUsername = "root"
|
||||
}
|
||||
|
||||
if c.UseFloatingIp && c.FloatingIpPool == "" {
|
||||
|
@ -51,8 +38,7 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
|||
}
|
||||
|
||||
// Validation
|
||||
var err error
|
||||
errs := make([]error, 0)
|
||||
errs := c.Comm.Prepare(ctx)
|
||||
if c.SourceImage == "" {
|
||||
errs = append(errs, errors.New("A source_image must be specified"))
|
||||
}
|
||||
|
@ -61,18 +47,5 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
|||
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
|
||||
}
|
||||
|
||||
func (c *RunConfig) SSHTimeout() time.Duration {
|
||||
return c.sshTimeout
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package openstack
|
|||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -17,7 +19,10 @@ func testRunConfig() *RunConfig {
|
|||
return &RunConfig{
|
||||
SourceImage: "abcd",
|
||||
Flavor: "m1.small",
|
||||
SSHUsername: "root",
|
||||
|
||||
Comm: communicator.Config{
|
||||
SSHUsername: "foo",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,41 +52,28 @@ func TestRunConfigPrepare_SourceImage(t *testing.T) {
|
|||
|
||||
func TestRunConfigPrepare_SSHPort(t *testing.T) {
|
||||
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)
|
||||
if c.Comm.SSHPort != 22 {
|
||||
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
|
||||
}
|
||||
|
||||
c.SSHPort = 44
|
||||
c.Comm.SSHPort = 44
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if c.SSHPort != 44 {
|
||||
t.Fatalf("invalid value: %d", c.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHTimeout(t *testing.T) {
|
||||
c := testRunConfig()
|
||||
c.RawSSHTimeout = ""
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c.RawSSHTimeout = "bad"
|
||||
if err := c.Prepare(nil); len(err) != 1 {
|
||||
t.Fatalf("err: %s", err)
|
||||
if c.Comm.SSHPort != 44 {
|
||||
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConfigPrepare_SSHUsername(t *testing.T) {
|
||||
c := testRunConfig()
|
||||
c.SSHUsername = ""
|
||||
c.Comm.SSHUsername = ""
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
@ -13,22 +13,21 @@ import (
|
|||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// SSHAddress returns a function that can be given to the SSH communicator
|
||||
// for determining the SSH address based on the server AccessIPv4 setting..
|
||||
func SSHAddress(
|
||||
// CommHost looks up the host for the communicator.
|
||||
func CommHost(
|
||||
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) {
|
||||
s := state.Get("server").(*servers.Server)
|
||||
|
||||
// If we have a floating IP, use that
|
||||
ip := state.Get("access_ip").(*floatingip.FloatingIP)
|
||||
if ip != nil && ip.IP != "" {
|
||||
return fmt.Sprintf("%s:%d", ip.IP, port), nil
|
||||
return ip.IP, nil
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -53,7 +52,7 @@ func SSHAddress(
|
|||
}
|
||||
}
|
||||
if addr != "" {
|
||||
return fmt.Sprintf("%s:%d", addr, port), nil
|
||||
return addr, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
commonssh "github.com/mitchellh/packer/common/ssh"
|
||||
packerssh "github.com/mitchellh/packer/communicator/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)
|
||||
driver := state.Get("driver").(Driver)
|
||||
|
||||
|
@ -23,19 +21,19 @@ func SSHAddress(state multistep.StateBag) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:22", ip), nil
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
auth := []ssh.AuthMethod{
|
||||
ssh.Password(config.SSHPassword),
|
||||
ssh.Password(config.Comm.SSHPassword),
|
||||
ssh.KeyboardInteractive(
|
||||
packerssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
packerssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
|
||||
}
|
||||
|
||||
if config.SSHKeyPath != "" {
|
||||
signer, err := commonssh.FileSigner(config.SSHKeyPath)
|
||||
signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -44,7 +42,7 @@ func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig
|
|||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
User: config.Comm.SSHUsername,
|
||||
Auth: auth,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -1,52 +1,29 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
commonssh "github.com/mitchellh/packer/common/ssh"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type SSHConfig struct {
|
||||
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"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
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 {
|
||||
if c.SSHPort == 0 {
|
||||
c.SSHPort = 22
|
||||
}
|
||||
|
||||
if c.RawSSHWaitTimeout == "" {
|
||||
c.RawSSHWaitTimeout = "20m"
|
||||
}
|
||||
|
||||
var errs []error
|
||||
// TODO: backwards compatibility, write fixer instead
|
||||
if c.SSHKeyPath != "" {
|
||||
if _, err := os.Stat(c.SSHKeyPath); err != nil {
|
||||
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))
|
||||
}
|
||||
c.Comm.SSHPrivateKey = c.SSHKeyPath
|
||||
}
|
||||
if c.SSHWaitTimeout != 0 {
|
||||
c.Comm.SSHTimeout = c.SSHWaitTimeout
|
||||
}
|
||||
|
||||
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 c.Comm.Prepare(ctx)
|
||||
}
|
||||
|
|
|
@ -4,11 +4,15 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
)
|
||||
|
||||
func testSSHConfig() *SSHConfig {
|
||||
return &SSHConfig{
|
||||
SSHUser: "foo",
|
||||
Comm: communicator.Config{
|
||||
SSHUsername: "foo",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,8 +23,8 @@ func TestSSHConfigPrepare(t *testing.T) {
|
|||
t.Fatalf("err: %#v", errs)
|
||||
}
|
||||
|
||||
if c.SSHPort != 22 {
|
||||
t.Errorf("bad ssh port: %d", c.SSHPort)
|
||||
if c.Comm.SSHPort != 22 {
|
||||
t.Errorf("bad ssh port: %d", c.Comm.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,46 +82,14 @@ func TestSSHConfigPrepare_SSHUser(t *testing.T) {
|
|||
var errs []error
|
||||
|
||||
c = testSSHConfig()
|
||||
c.SSHUser = ""
|
||||
c.Comm.SSHUsername = ""
|
||||
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"
|
||||
c.Comm.SSHUsername = "exists"
|
||||
errs = c.Prepare(testConfigTemplate(t))
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("should not have error: %#v", errs)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/mitchellh/multistep"
|
||||
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
|
@ -245,10 +246,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: parallelscommon.SSHAddress,
|
||||
SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig),
|
||||
SSHWaitTimeout: b.config.SSHWaitTimeout,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.SSHConfig.Comm,
|
||||
Host: parallelscommon.CommHost,
|
||||
SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig),
|
||||
},
|
||||
¶llelscommon.StepUploadVersion{
|
||||
Path: b.config.PrlctlVersionFile,
|
||||
|
|
|
@ -3,11 +3,13 @@ package pvm
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
parallelscommon "github.com/mitchellh/packer/builder/parallels/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
)
|
||||
|
||||
// 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
|
|||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: parallelscommon.SSHAddress,
|
||||
SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig),
|
||||
SSHWaitTimeout: b.config.SSHWaitTimeout,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.SSHConfig.Comm,
|
||||
Host: parallelscommon.CommHost,
|
||||
SSHConfig: parallelscommon.SSHConfigFunc(b.config.SSHConfig),
|
||||
},
|
||||
¶llelscommon.StepUploadVersion{
|
||||
Path: b.config.PrlctlVersionFile,
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
"github.com/mitchellh/multistep"
|
||||
"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/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
|
@ -78,6 +78,7 @@ type Builder struct {
|
|||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
Accelerator string `mapstructure:"accelerator"`
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
|
@ -103,25 +104,24 @@ type Config struct {
|
|||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
|
||||
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"`
|
||||
VNCPortMax uint `mapstructure:"vnc_port_max"`
|
||||
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
|
||||
RunOnce bool `mapstructure:"run_once"`
|
||||
|
||||
RawBootWait string `mapstructure:"boot_wait"`
|
||||
RawSingleISOUrl string `mapstructure:"iso_url"`
|
||||
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
|
||||
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
|
||||
|
||||
bootWait time.Duration ``
|
||||
shutdownTimeout time.Duration ``
|
||||
sshWaitTimeout time.Duration ``
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
|
@ -139,9 +139,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
warnings := make([]string, 0)
|
||||
|
||||
if b.config.DiskSize == 0 {
|
||||
b.config.DiskSize = 40000
|
||||
}
|
||||
|
@ -190,10 +187,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
b.config.SSHHostPortMax = 4444
|
||||
}
|
||||
|
||||
if b.config.SSHPort == 0 {
|
||||
b.config.SSHPort = 22
|
||||
}
|
||||
|
||||
if b.config.VNCPortMin == 0 {
|
||||
b.config.VNCPortMin = 5900
|
||||
}
|
||||
|
@ -222,6 +215,21 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
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") {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("invalid format, only 'qcow2' or 'raw' are allowed"))
|
||||
|
@ -314,42 +322,17 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
b.config.RawShutdownTimeout = "5m"
|
||||
}
|
||||
|
||||
if b.config.RawSSHWaitTimeout == "" {
|
||||
b.config.RawSSHWaitTimeout = "20m"
|
||||
}
|
||||
|
||||
b.config.shutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
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 {
|
||||
errs = packer.MultiErrorAppend(
|
||||
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 {
|
||||
errs = packer.MultiErrorAppend(
|
||||
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
|
|||
steprun,
|
||||
&stepBootWait{},
|
||||
&stepTypeBootCommand{},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: sshAddress,
|
||||
SSHConfig: sshConfig,
|
||||
SSHWaitTimeout: b.config.sshWaitTimeout,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
Host: commHost,
|
||||
SSHConfig: sshConfig,
|
||||
SSHPort: commPort,
|
||||
},
|
||||
new(common.StepProvision),
|
||||
new(stepShutdown),
|
||||
|
|
|
@ -79,8 +79,8 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
|||
t.Errorf("bad max ssh host port: %d", b.config.SSHHostPortMax)
|
||||
}
|
||||
|
||||
if b.config.SSHPort != 22 {
|
||||
t.Errorf("bad ssh port: %d", b.config.SSHPort)
|
||||
if b.config.Comm.SSHPort != 22 {
|
||||
t.Errorf("bad ssh port: %d", b.config.Comm.SSHPort)
|
||||
}
|
||||
|
||||
if b.config.VMName != "packer-foo" {
|
||||
|
@ -595,10 +595,6 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if b.config.RawSSHWaitTimeout != "20m" {
|
||||
t.Fatalf("bad value: %s", b.config.RawSSHWaitTimeout)
|
||||
}
|
||||
|
||||
// Test with a bad value
|
||||
config["ssh_wait_timeout"] = "this is not good"
|
||||
b = Builder{}
|
||||
|
|
|
@ -1,30 +1,32 @@
|
|||
package qemu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
commonssh "github.com/mitchellh/packer/common/ssh"
|
||||
"github.com/mitchellh/packer/communicator/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)
|
||||
return fmt.Sprintf("127.0.0.1:%d", sshHostPort), nil
|
||||
return int(sshHostPort), nil
|
||||
}
|
||||
|
||||
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
auth := []gossh.AuthMethod{
|
||||
gossh.Password(config.SSHPassword),
|
||||
gossh.Password(config.Comm.SSHPassword),
|
||||
gossh.KeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
ssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
|
||||
}
|
||||
|
||||
if config.SSHKeyPath != "" {
|
||||
signer, err := commonssh.FileSigner(config.SSHKeyPath)
|
||||
if config.Comm.SSHPrivateKey != "" {
|
||||
signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -33,7 +35,7 @@ func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
|
|||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
User: config.Comm.SSHUsername,
|
||||
Auth: auth,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
commonssh "github.com/mitchellh/packer/common/ssh"
|
||||
"github.com/mitchellh/packer/communicator/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)
|
||||
return fmt.Sprintf("127.0.0.1:%d", sshHostPort), nil
|
||||
return int(sshHostPort), nil
|
||||
}
|
||||
|
||||
func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) {
|
||||
return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
|
||||
auth := []gossh.AuthMethod{
|
||||
gossh.Password(config.SSHPassword),
|
||||
gossh.Password(config.Comm.SSHPassword),
|
||||
gossh.KeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
ssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
|
||||
}
|
||||
|
||||
if config.SSHKeyPath != "" {
|
||||
signer, err := commonssh.FileSigner(config.SSHKeyPath)
|
||||
signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -32,7 +34,7 @@ func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConf
|
|||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
User: config.Comm.SSHUsername,
|
||||
Auth: auth,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -2,25 +2,23 @@ package common
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
commonssh "github.com/mitchellh/packer/common/ssh"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type SSHConfig struct {
|
||||
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
|
||||
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
|
||||
SSHKeyPath string `mapstructure:"ssh_key_path"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
SSHPort uint `mapstructure:"ssh_port"`
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
|
||||
SSHSkipNatMapping bool `mapstructure:"ssh_skip_nat_mapping"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
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 {
|
||||
|
@ -32,37 +30,19 @@ func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error {
|
|||
c.SSHHostPortMax = 4444
|
||||
}
|
||||
|
||||
if c.SSHPort == 0 {
|
||||
c.SSHPort = 22
|
||||
}
|
||||
|
||||
if c.RawSSHWaitTimeout == "" {
|
||||
c.RawSSHWaitTimeout = "20m"
|
||||
}
|
||||
|
||||
var errs []error
|
||||
// TODO: backwards compatibility, write fixer instead
|
||||
if c.SSHKeyPath != "" {
|
||||
if _, err := os.Stat(c.SSHKeyPath); err != nil {
|
||||
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))
|
||||
}
|
||||
c.Comm.SSHPrivateKey = c.SSHKeyPath
|
||||
}
|
||||
if c.SSHWaitTimeout != 0 {
|
||||
c.Comm.SSHTimeout = c.SSHWaitTimeout
|
||||
}
|
||||
|
||||
errs := c.Comm.Prepare(ctx)
|
||||
if c.SSHHostPortMin > c.SSHHostPortMax {
|
||||
errs = append(errs,
|
||||
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
|
||||
}
|
||||
|
|
|
@ -4,11 +4,15 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
)
|
||||
|
||||
func testSSHConfig() *SSHConfig {
|
||||
return &SSHConfig{
|
||||
SSHUser: "foo",
|
||||
Comm: communicator.Config{
|
||||
SSHUsername: "foo",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,8 +31,8 @@ func TestSSHConfigPrepare(t *testing.T) {
|
|||
t.Errorf("bad max ssh host port: %d", c.SSHHostPortMax)
|
||||
}
|
||||
|
||||
if c.SSHPort != 22 {
|
||||
t.Errorf("bad ssh port: %d", c.SSHPort)
|
||||
if c.Comm.SSHPort != 22 {
|
||||
t.Errorf("bad ssh port: %d", c.Comm.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,46 +113,14 @@ func TestSSHConfigPrepare_SSHUser(t *testing.T) {
|
|||
var errs []error
|
||||
|
||||
c = testSSHConfig()
|
||||
c.SSHUser = ""
|
||||
c.Comm.SSHUsername = ""
|
||||
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"
|
||||
c.Comm.SSHUsername = "exists"
|
||||
errs = c.Prepare(testConfigTemplate(t))
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("should not have error: %#v", errs)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/mitchellh/multistep"
|
||||
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
|
@ -253,7 +254,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
},
|
||||
new(vboxcommon.StepAttachFloppy),
|
||||
&vboxcommon.StepForwardSSH{
|
||||
GuestPort: b.config.SSHPort,
|
||||
GuestPort: uint(b.config.SSHConfig.Comm.SSHPort),
|
||||
HostPortMin: b.config.SSHHostPortMin,
|
||||
HostPortMax: b.config.SSHHostPortMax,
|
||||
SkipNatMapping: b.config.SSHSkipNatMapping,
|
||||
|
@ -271,10 +272,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: vboxcommon.SSHAddress,
|
||||
SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig),
|
||||
SSHWaitTimeout: b.config.SSHWaitTimeout,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.SSHConfig.Comm,
|
||||
Host: vboxcommon.CommHost,
|
||||
SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig),
|
||||
SSHPort: vboxcommon.SSHPort,
|
||||
},
|
||||
&vboxcommon.StepUploadVersion{
|
||||
Path: b.config.VBoxVersionFile,
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/mitchellh/multistep"
|
||||
vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
|
@ -82,7 +83,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
},
|
||||
new(vboxcommon.StepAttachFloppy),
|
||||
&vboxcommon.StepForwardSSH{
|
||||
GuestPort: b.config.SSHPort,
|
||||
GuestPort: uint(b.config.SSHConfig.Comm.SSHPort),
|
||||
HostPortMin: b.config.SSHHostPortMin,
|
||||
HostPortMax: b.config.SSHHostPortMax,
|
||||
SkipNatMapping: b.config.SSHSkipNatMapping,
|
||||
|
@ -100,10 +101,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: vboxcommon.SSHAddress,
|
||||
SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig),
|
||||
SSHWaitTimeout: b.config.SSHWaitTimeout,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.SSHConfig.Comm,
|
||||
Host: vboxcommon.CommHost,
|
||||
SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig),
|
||||
SSHPort: vboxcommon.SSHPort,
|
||||
},
|
||||
&vboxcommon.StepUploadVersion{
|
||||
Path: b.config.VBoxVersionFile,
|
||||
|
|
|
@ -29,9 +29,9 @@ type Driver interface {
|
|||
// Checks if the VMX file at the given path is running.
|
||||
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.
|
||||
SSHAddress(multistep.StateBag) (string, error)
|
||||
CommHost(multistep.StateBag) (string, error)
|
||||
|
||||
// Start starts a VM specified by the path to the VMX given.
|
||||
Start(string, bool) error
|
||||
|
|
|
@ -69,8 +69,8 @@ func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) SSHAddress(state multistep.StateBag) (string, error) {
|
||||
return SSHAddressFunc(d.SSHConfig)(state)
|
||||
func (d *Fusion5Driver) CommHost(state multistep.StateBag) (string, error) {
|
||||
return CommHost(d.SSHConfig)(state)
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) Start(vmxPath string, headless bool) error {
|
||||
|
|
|
@ -29,10 +29,10 @@ type DriverMock struct {
|
|||
IsRunningResult bool
|
||||
IsRunningErr error
|
||||
|
||||
SSHAddressCalled bool
|
||||
SSHAddressState multistep.StateBag
|
||||
SSHAddressResult string
|
||||
SSHAddressErr error
|
||||
CommHostCalled bool
|
||||
CommHostState multistep.StateBag
|
||||
CommHostResult string
|
||||
CommHostErr error
|
||||
|
||||
StartCalled bool
|
||||
StartPath string
|
||||
|
@ -92,10 +92,10 @@ func (d *DriverMock) IsRunning(path string) (bool, error) {
|
|||
return d.IsRunningResult, d.IsRunningErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) SSHAddress(state multistep.StateBag) (string, error) {
|
||||
d.SSHAddressCalled = true
|
||||
d.SSHAddressState = state
|
||||
return d.SSHAddressResult, d.SSHAddressErr
|
||||
func (d *DriverMock) CommHost(state multistep.StateBag) (string, error) {
|
||||
d.CommHostCalled = true
|
||||
d.CommHostState = state
|
||||
return d.CommHostResult, d.CommHostErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) Start(path string, headless bool) error {
|
||||
|
|
|
@ -97,8 +97,8 @@ func (d *Player5Driver) IsRunning(vmxPath string) (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Player5Driver) SSHAddress(state multistep.StateBag) (string, error) {
|
||||
return SSHAddressFunc(d.SSHConfig)(state)
|
||||
func (d *Player5Driver) CommHost(state multistep.StateBag) (string, error) {
|
||||
return CommHost(d.SSHConfig)(state)
|
||||
}
|
||||
|
||||
func (d *Player5Driver) Start(vmxPath string, headless bool) error {
|
||||
|
|
|
@ -70,8 +70,8 @@ func (d *Workstation9Driver) IsRunning(vmxPath string) (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Workstation9Driver) SSHAddress(state multistep.StateBag) (string, error) {
|
||||
return SSHAddressFunc(d.SSHConfig)(state)
|
||||
func (d *Workstation9Driver) CommHost(state multistep.StateBag) (string, error) {
|
||||
return CommHost(d.SSHConfig)(state)
|
||||
}
|
||||
|
||||
func (d *Workstation9Driver) Start(vmxPath string, headless bool) error {
|
||||
|
|
|
@ -13,13 +13,13 @@ import (
|
|||
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) {
|
||||
driver := state.Get("driver").(Driver)
|
||||
vmxPath := state.Get("vmx_path").(string)
|
||||
|
||||
if config.SSHHost != "" {
|
||||
return fmt.Sprintf("%s:%d", config.SSHHost, config.SSHPort), nil
|
||||
if config.Comm.SSHHost != "" {
|
||||
return config.Comm.SSHHost, nil
|
||||
}
|
||||
|
||||
log.Println("Lookup up IP information...")
|
||||
|
@ -62,20 +62,20 @@ func SSHAddressFunc(config *SSHConfig) func(multistep.StateBag) (string, error)
|
|||
}
|
||||
|
||||
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) {
|
||||
return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
|
||||
auth := []gossh.AuthMethod{
|
||||
gossh.Password(config.SSHPassword),
|
||||
gossh.Password(config.Comm.SSHPassword),
|
||||
gossh.KeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHPassword)),
|
||||
ssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
|
||||
}
|
||||
|
||||
if config.SSHKeyPath != "" {
|
||||
signer, err := commonssh.FileSigner(config.SSHKeyPath)
|
||||
if config.Comm.SSHPrivateKey != "" {
|
||||
signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ func SSHConfigFunc(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientCon
|
|||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHUser,
|
||||
User: config.Comm.SSHUsername,
|
||||
Auth: auth,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -1,63 +1,33 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
commonssh "github.com/mitchellh/packer/common/ssh"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type SSHConfig struct {
|
||||
SSHUser string `mapstructure:"ssh_username"`
|
||||
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"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
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 {
|
||||
if c.SSHPort == 0 {
|
||||
c.SSHPort = 22
|
||||
}
|
||||
|
||||
if c.RawSSHWaitTimeout == "" {
|
||||
c.RawSSHWaitTimeout = "20m"
|
||||
}
|
||||
|
||||
var errs []error
|
||||
// TODO: backwards compatibility, write fixer instead
|
||||
if c.SSHKeyPath != "" {
|
||||
if _, err := os.Stat(c.SSHKeyPath); err != nil {
|
||||
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))
|
||||
}
|
||||
c.Comm.SSHPrivateKey = c.SSHKeyPath
|
||||
}
|
||||
if c.SSHWaitTimeout != 0 {
|
||||
c.Comm.SSHTimeout = c.SSHWaitTimeout
|
||||
}
|
||||
if c.SSHSkipRequestPty {
|
||||
c.Comm.SSHPty = false
|
||||
}
|
||||
|
||||
if c.SSHHost != "" {
|
||||
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."))
|
||||
}
|
||||
|
||||
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 (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
)
|
||||
|
||||
func testSSHConfig() *SSHConfig {
|
||||
return &SSHConfig{
|
||||
SSHUser: "foo",
|
||||
Comm: communicator.Config{
|
||||
SSHUsername: "foo",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,8 +23,8 @@ func TestSSHConfigPrepare(t *testing.T) {
|
|||
t.Fatalf("err: %#v", errs)
|
||||
}
|
||||
|
||||
if c.SSHPort != 22 {
|
||||
t.Errorf("bad ssh port: %d", c.SSHPort)
|
||||
if c.Comm.SSHPort != 22 {
|
||||
t.Errorf("bad ssh port: %d", c.Comm.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAxd4iamvrwRJvtNDGQSIbNvvIQN8imXTRWlRY62EvKov60vqu
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/mitchellh/multistep"
|
||||
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
|
@ -298,11 +299,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: driver.SSHAddress,
|
||||
SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig),
|
||||
SSHWaitTimeout: b.config.SSHWaitTimeout,
|
||||
Pty: !b.config.SSHSkipRequestPty,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.SSHConfig.Comm,
|
||||
Host: driver.CommHost,
|
||||
SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig),
|
||||
},
|
||||
&vmwcommon.StepUploadTools{
|
||||
RemoteType: b.config.RemoteType,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package iso
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
|
@ -138,10 +138,6 @@ func TestBuilderPrepare_Defaults(t *testing.T) {
|
|||
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" {
|
||||
t.Errorf("bad vm name: %s", b.config.VMName)
|
||||
}
|
||||
|
|
|
@ -218,7 +218,7 @@ func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint, error) {
|
|||
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)
|
||||
|
||||
if address, ok := state.GetOk("vm_address"); ok {
|
||||
|
@ -253,7 +253,7 @@ func (d *ESX5Driver) SSHAddress(state multistep.StateBag) (string, error) {
|
|||
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)
|
||||
return address, nil
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/mitchellh/multistep"
|
||||
vmwcommon "github.com/mitchellh/packer/builder/vmware/common"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
|
@ -90,11 +91,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
&common.StepConnectSSH{
|
||||
SSHAddress: driver.SSHAddress,
|
||||
SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig),
|
||||
SSHWaitTimeout: b.config.SSHWaitTimeout,
|
||||
Pty: !b.config.SSHSkipRequestPty,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.SSHConfig.Comm,
|
||||
Host: driver.CommHost,
|
||||
SSHConfig: vmwcommon.SSHConfigFunc(&b.config.SSHConfig),
|
||||
},
|
||||
&vmwcommon.StepUploadTools{
|
||||
RemoteType: b.config.RemoteType,
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
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")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
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
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
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
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
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
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package common
|
||||
package communicator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -13,32 +13,15 @@ import (
|
|||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// StepConnectSSH is a multistep Step implementation that waits for SSH
|
||||
// to become available. It gets the connection information from a single
|
||||
// configuration when creating the step.
|
||||
// StepConnectSSH is a step that only connects to SSH.
|
||||
//
|
||||
// Uses:
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// communicator packer.Communicator
|
||||
// In general, you should use StepConnect.
|
||||
type StepConnectSSH struct {
|
||||
// SSHAddress is a function that returns the TCP address to connect to
|
||||
// for SSH. This is a function so that you can query information
|
||||
// if necessary for this address.
|
||||
SSHAddress func(multistep.StateBag) (string, error)
|
||||
|
||||
// SSHConfig is a function that returns the proper client configuration
|
||||
// for SSH access.
|
||||
// All the fields below are documented on StepConnect
|
||||
Config *Config
|
||||
Host func(multistep.StateBag) (string, error)
|
||||
SSHConfig func(multistep.StateBag) (*gossh.ClientConfig, 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
|
||||
SSHPort func(multistep.StateBag) (int, error)
|
||||
}
|
||||
|
||||
func (s *StepConnectSSH) Run(state multistep.StateBag) multistep.StepAction {
|
||||
|
@ -55,8 +38,8 @@ func (s *StepConnectSSH) Run(state multistep.StateBag) multistep.StepAction {
|
|||
waitDone <- true
|
||||
}()
|
||||
|
||||
log.Printf("Waiting for SSH, up to timeout: %s", s.SSHWaitTimeout)
|
||||
timeout := time.After(s.SSHWaitTimeout)
|
||||
log.Printf("[INFO] Waiting for SSH, up to timeout: %s", s.Config.SSHTimeout)
|
||||
timeout := time.After(s.Config.SSHTimeout)
|
||||
WaitLoop:
|
||||
for {
|
||||
// Wait for either SSH to become available, a timeout to occur,
|
||||
|
@ -70,7 +53,6 @@ WaitLoop:
|
|||
}
|
||||
|
||||
ui.Say("Connected to SSH!")
|
||||
s.comm = comm
|
||||
state.Put("communicator", comm)
|
||||
break WaitLoop
|
||||
case <-timeout:
|
||||
|
@ -84,7 +66,7 @@ WaitLoop:
|
|||
// The step sequence was cancelled, so cancel waiting for SSH
|
||||
// and just start the halting process.
|
||||
close(cancel)
|
||||
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||
log.Println("[WARN] Interrupt detected, quitting waiting for SSH.")
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +88,7 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
|
|||
if !first {
|
||||
select {
|
||||
case <-cancel:
|
||||
log.Println("SSH wait cancelled. Exiting loop.")
|
||||
log.Println("[DEBUG] SSH wait cancelled. Exiting loop.")
|
||||
return nil, errors.New("SSH wait cancelled")
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
|
@ -114,24 +96,34 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
|
|||
first = false
|
||||
|
||||
// First we request the TCP connection information
|
||||
address, err := s.SSHAddress(state)
|
||||
host, err := s.Host(state)
|
||||
if err != nil {
|
||||
log.Printf("Error getting SSH address: %s", err)
|
||||
log.Printf("[DEBUG] Error getting SSH address: %s", err)
|
||||
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
|
||||
sshConfig, err := s.SSHConfig(state)
|
||||
if err != nil {
|
||||
log.Printf("Error getting SSH config: %s", err)
|
||||
log.Printf("[DEBUG] Error getting SSH config: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
address := fmt.Sprintf("%s:%d", host, port)
|
||||
|
||||
// Attempt to connect to SSH port
|
||||
connFunc := ssh.ConnectFunc("tcp", address)
|
||||
nc, err := connFunc()
|
||||
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
|
||||
}
|
||||
nc.Close()
|
||||
|
@ -140,19 +132,20 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
|
|||
config := &ssh.Config{
|
||||
Connection: connFunc,
|
||||
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)
|
||||
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
|
||||
// to authenticate. Note this is very brittle since it depends
|
||||
// on the string of the error... but I don't see any other way.
|
||||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
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
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
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 {
|
|||
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||
uint8ToStringHook,
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
@ -3,6 +3,7 @@ package config
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
@ -11,6 +12,7 @@ func TestDecode(t *testing.T) {
|
|||
type Target struct {
|
||||
Name string
|
||||
Address string
|
||||
Time time.Duration
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
|
@ -22,10 +24,12 @@ func TestDecode(t *testing.T) {
|
|||
[]interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "bar",
|
||||
"time": "5s",
|
||||
},
|
||||
},
|
||||
&Target{
|
||||
Name: "bar",
|
||||
Time: 5 * time.Second,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue