Adding support for WinRM
This commit is contained in:
parent
b1c6a3f63f
commit
4a29c714e5
|
@ -76,7 +76,7 @@ func (p *ResourceProvisioner) copyFiles(comm communicator.Communicator, src, dst
|
|||
|
||||
// If we're uploading a directory, short circuit and do that
|
||||
if info.IsDir() {
|
||||
if err := comm.UploadDir(dst, src, nil); err != nil {
|
||||
if err := comm.UploadDir(dst, src); err != nil {
|
||||
return fmt.Errorf("Upload failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/communicator/remote"
|
||||
"github.com/hashicorp/terraform/communicator/ssh"
|
||||
"github.com/hashicorp/terraform/communicator/winrm"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -35,7 +36,7 @@ type Communicator interface {
|
|||
UploadScript(string, io.Reader) error
|
||||
|
||||
// UploadDir is used to upload a directory
|
||||
UploadDir(string, string, []string) error
|
||||
UploadDir(string, string) error
|
||||
}
|
||||
|
||||
// New returns a configured Communicator or an error if the connection type is not supported
|
||||
|
@ -44,9 +45,9 @@ func New(s *terraform.InstanceState) (Communicator, error) {
|
|||
switch connType {
|
||||
case "ssh", "": // The default connection type is ssh, so if connType is empty use ssh
|
||||
return ssh.New(s)
|
||||
//case "winrm":
|
||||
// return winrm.New()
|
||||
case "winrm":
|
||||
return winrm.New(s)
|
||||
default:
|
||||
return nil, fmt.Errorf("Connection type '%s' not supported", connType)
|
||||
return nil, fmt.Errorf("connection type '%s' not supported", connType)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,14 @@ func TestCommunicator_new(t *testing.T) {
|
|||
if _, err := New(r); err == nil {
|
||||
t.Fatalf("expected error with telnet")
|
||||
}
|
||||
|
||||
r.Ephemeral.ConnInfo["type"] = "ssh"
|
||||
if _, err := New(r); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
r.Ephemeral.ConnInfo["type"] = "winrm"
|
||||
if _, err := New(r); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,47 +23,54 @@ const (
|
|||
DefaultShebang = "#!/bin/sh\n"
|
||||
)
|
||||
|
||||
type communicator struct {
|
||||
connInfo *ConnectionInfo
|
||||
config *SSHConfig
|
||||
// Communicator represents the SSH communicator
|
||||
type Communicator struct {
|
||||
connInfo *connectionInfo
|
||||
client *ssh.Client
|
||||
config *sshConfig
|
||||
conn net.Conn
|
||||
address string
|
||||
}
|
||||
|
||||
// SSHConfig is the structure used to configure the SSH communicator.
|
||||
type SSHConfig struct {
|
||||
type sshConfig struct {
|
||||
// The configuration of the Go SSH connection
|
||||
Config *ssh.ClientConfig
|
||||
config *ssh.ClientConfig
|
||||
|
||||
// Connection returns a new connection. The current connection
|
||||
// connection returns a new connection. The current connection
|
||||
// in use will be closed as part of the Close method, or in the
|
||||
// case an error occurs.
|
||||
Connection func() (net.Conn, error)
|
||||
connection func() (net.Conn, error)
|
||||
|
||||
// NoPty, if true, will not request a pty from the remote end.
|
||||
NoPty bool
|
||||
// noPty, if true, will not request a pty from the remote end.
|
||||
noPty bool
|
||||
|
||||
// SSHAgentConn is a pointer to the UNIX connection for talking with the
|
||||
// sshAgentConn is a pointer to the UNIX connection for talking with the
|
||||
// ssh-agent.
|
||||
SSHAgentConn net.Conn
|
||||
sshAgentConn net.Conn
|
||||
}
|
||||
|
||||
// New creates a new communicator implementation over SSH. This takes
|
||||
// an already existing TCP connection and SSH configuration.
|
||||
func New(s *terraform.InstanceState) (*communicator, error) {
|
||||
connInfo, err := ParseConnectionInfo(s)
|
||||
// New creates a new communicator implementation over SSH.
|
||||
func New(s *terraform.InstanceState) (*Communicator, error) {
|
||||
connInfo, err := parseConnectionInfo(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comm := &communicator{connInfo: connInfo}
|
||||
config, err := prepareSSHConfig(connInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comm := &Communicator{
|
||||
connInfo: connInfo,
|
||||
config: config,
|
||||
}
|
||||
|
||||
return comm, nil
|
||||
}
|
||||
|
||||
// Connect implementation of communicator.Communicator interface
|
||||
func (c *communicator) Connect(o terraform.UIOutput) (err error) {
|
||||
func (c *Communicator) Connect(o terraform.UIOutput) (err error) {
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
}
|
||||
|
@ -72,19 +79,14 @@ func (c *communicator) Connect(o terraform.UIOutput) (err error) {
|
|||
c.conn = nil
|
||||
c.client = nil
|
||||
|
||||
c.config, err = PrepareSSHConfig(c.connInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o != nil {
|
||||
o.Output(fmt.Sprintf(
|
||||
"Connecting to remote host via SSH...\n"+
|
||||
" Host: %s\n"+
|
||||
" User: %s\n"+
|
||||
" Password: %v\n"+
|
||||
" Private key: %v"+
|
||||
" SSH Agent: %v",
|
||||
" Password: %t\n"+
|
||||
" Private key: %t\n"+
|
||||
" SSH Agent: %t",
|
||||
c.connInfo.Host, c.connInfo.User,
|
||||
c.connInfo.Password != "",
|
||||
c.connInfo.KeyFile != "",
|
||||
|
@ -93,7 +95,7 @@ func (c *communicator) Connect(o terraform.UIOutput) (err error) {
|
|||
}
|
||||
|
||||
log.Printf("connecting to TCP connection for SSH")
|
||||
c.conn, err = c.config.Connection()
|
||||
c.conn, err = c.config.connection()
|
||||
if err != nil {
|
||||
// Explicitly set this to the REAL nil. Connection() can return
|
||||
// a nil implementation of net.Conn which will make the
|
||||
|
@ -109,7 +111,7 @@ func (c *communicator) Connect(o terraform.UIOutput) (err error) {
|
|||
|
||||
log.Printf("handshaking with SSH")
|
||||
host := fmt.Sprintf("%s:%d", c.connInfo.Host, c.connInfo.Port)
|
||||
sshConn, sshChan, req, err := ssh.NewClientConn(c.conn, host, c.config.Config)
|
||||
sshConn, sshChan, req, err := ssh.NewClientConn(c.conn, host, c.config.config)
|
||||
if err != nil {
|
||||
log.Printf("handshake error: %s", err)
|
||||
return err
|
||||
|
@ -125,26 +127,26 @@ func (c *communicator) Connect(o terraform.UIOutput) (err error) {
|
|||
}
|
||||
|
||||
// Disconnect implementation of communicator.Communicator interface
|
||||
func (c *communicator) Disconnect() error {
|
||||
if c.config.SSHAgentConn != nil {
|
||||
return c.config.SSHAgentConn.Close()
|
||||
func (c *Communicator) Disconnect() error {
|
||||
if c.config.sshAgentConn != nil {
|
||||
return c.config.sshAgentConn.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout implementation of communicator.Communicator interface
|
||||
func (c *communicator) Timeout() time.Duration {
|
||||
func (c *Communicator) Timeout() time.Duration {
|
||||
return c.connInfo.TimeoutVal
|
||||
}
|
||||
|
||||
// Timeout implementation of communicator.Communicator interface
|
||||
func (c *communicator) ScriptPath() string {
|
||||
// ScriptPath implementation of communicator.Communicator interface
|
||||
func (c *Communicator) ScriptPath() string {
|
||||
return c.connInfo.ScriptPath
|
||||
}
|
||||
|
||||
// Start implementation of communicator.Communicator interface
|
||||
func (c *communicator) Start(cmd *remote.Cmd) error {
|
||||
func (c *Communicator) Start(cmd *remote.Cmd) error {
|
||||
session, err := c.newSession()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -155,7 +157,7 @@ func (c *communicator) Start(cmd *remote.Cmd) error {
|
|||
session.Stdout = cmd.Stdout
|
||||
session.Stderr = cmd.Stderr
|
||||
|
||||
if !c.config.NoPty {
|
||||
if !c.config.noPty {
|
||||
// Request a PTY
|
||||
termModes := ssh.TerminalModes{
|
||||
ssh.ECHO: 0, // do not echo
|
||||
|
@ -196,7 +198,7 @@ func (c *communicator) Start(cmd *remote.Cmd) error {
|
|||
}
|
||||
|
||||
// Upload implementation of communicator.Communicator interface
|
||||
func (c *communicator) Upload(path string, input io.Reader) error {
|
||||
func (c *Communicator) Upload(path string, input io.Reader) error {
|
||||
// The target directory and file for talking the SCP protocol
|
||||
targetDir := filepath.Dir(path)
|
||||
targetFile := filepath.Base(path)
|
||||
|
@ -214,7 +216,7 @@ func (c *communicator) Upload(path string, input io.Reader) error {
|
|||
}
|
||||
|
||||
// UploadScript implementation of communicator.Communicator interface
|
||||
func (c *communicator) UploadScript(path string, input io.Reader) error {
|
||||
func (c *Communicator) UploadScript(path string, input io.Reader) error {
|
||||
script := bytes.NewBufferString(DefaultShebang)
|
||||
script.ReadFrom(input)
|
||||
|
||||
|
@ -236,7 +238,7 @@ func (c *communicator) UploadScript(path string, input io.Reader) error {
|
|||
}
|
||||
|
||||
// UploadDir implementation of communicator.Communicator interface
|
||||
func (c *communicator) UploadDir(dst string, src string, excl []string) error {
|
||||
func (c *Communicator) UploadDir(dst string, src string) error {
|
||||
log.Printf("Upload dir '%s' to '%s'", src, dst)
|
||||
scpFunc := func(w io.Writer, r *bufio.Reader) error {
|
||||
uploadEntries := func() error {
|
||||
|
@ -265,12 +267,7 @@ func (c *communicator) UploadDir(dst string, src string, excl []string) error {
|
|||
return c.scpSession("scp -rvt "+dst, scpFunc)
|
||||
}
|
||||
|
||||
// Download implementation of communicator.Communicator interface
|
||||
func (c *communicator) Download(string, io.Writer) error {
|
||||
panic("not implemented yet")
|
||||
}
|
||||
|
||||
func (c *communicator) newSession() (session *ssh.Session, err error) {
|
||||
func (c *Communicator) newSession() (session *ssh.Session, err error) {
|
||||
log.Println("opening new ssh session")
|
||||
if c.client == nil {
|
||||
err = errors.New("client not available")
|
||||
|
@ -290,7 +287,7 @@ func (c *communicator) newSession() (session *ssh.Session, err error) {
|
|||
return session, nil
|
||||
}
|
||||
|
||||
func (c *communicator) scpSession(scpCommand string, f func(io.Writer, *bufio.Reader) error) error {
|
||||
func (c *Communicator) scpSession(scpCommand string, f func(io.Writer, *bufio.Reader) error) error {
|
||||
session, err := c.newSession()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// DefaultUser is used if there is no default user given
|
||||
// DefaultUser is used if there is no user given
|
||||
DefaultUser = "root"
|
||||
|
||||
// DefaultPort is used if there is no port given
|
||||
|
@ -31,10 +31,10 @@ const (
|
|||
DefaultTimeout = 5 * time.Minute
|
||||
)
|
||||
|
||||
// ConnectionInfo is decoded from the ConnInfo of the resource. These are the
|
||||
// connectionInfo is decoded from the ConnInfo of the resource. These are the
|
||||
// only keys we look at. If a KeyFile is given, that is used instead
|
||||
// of a password.
|
||||
type ConnectionInfo struct {
|
||||
type connectionInfo struct {
|
||||
User string
|
||||
Password string
|
||||
KeyFile string `mapstructure:"key_file"`
|
||||
|
@ -46,10 +46,10 @@ type ConnectionInfo struct {
|
|||
TimeoutVal time.Duration `mapstructure:"-"`
|
||||
}
|
||||
|
||||
// ParseConnectionInfo is used to convert the ConnInfo of the InstanceState into
|
||||
// parseConnectionInfo is used to convert the ConnInfo of the InstanceState into
|
||||
// a ConnectionInfo struct
|
||||
func ParseConnectionInfo(s *terraform.InstanceState) (*ConnectionInfo, error) {
|
||||
connInfo := &ConnectionInfo{}
|
||||
func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) {
|
||||
connInfo := &connectionInfo{}
|
||||
decConf := &mapstructure.DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: connInfo,
|
||||
|
@ -88,9 +88,9 @@ func safeDuration(dur string, defaultDur time.Duration) time.Duration {
|
|||
return d
|
||||
}
|
||||
|
||||
// PrepareSSHConfig is used to turn the *ConnectionInfo provided into a
|
||||
// prepareSSHConfig is used to turn the *ConnectionInfo provided into a
|
||||
// usable *SSHConfig for client initialization.
|
||||
func PrepareSSHConfig(connInfo *ConnectionInfo) (*SSHConfig, error) {
|
||||
func prepareSSHConfig(connInfo *connectionInfo) (*sshConfig, error) {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
|
@ -154,10 +154,10 @@ func PrepareSSHConfig(connInfo *ConnectionInfo) (*SSHConfig, error) {
|
|||
ssh.KeyboardInteractive(PasswordKeyboardInteractive(connInfo.Password)))
|
||||
}
|
||||
host := fmt.Sprintf("%s:%d", connInfo.Host, connInfo.Port)
|
||||
config := &SSHConfig{
|
||||
Config: sshConf,
|
||||
Connection: ConnectFunc("tcp", host),
|
||||
SSHAgentConn: conn,
|
||||
config := &sshConfig{
|
||||
config: sshConf,
|
||||
connection: ConnectFunc("tcp", host),
|
||||
sshAgentConn: conn,
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ func TestProvisioner_connInfo(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
conf, err := ParseConnectionInfo(r)
|
||||
conf, err := parseConnectionInfo(r)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
package winrm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/communicator/remote"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/masterzen/winrm/winrm"
|
||||
"github.com/packer-community/winrmcp/winrmcp"
|
||||
)
|
||||
|
||||
// Communicator represents the WinRM communicator
|
||||
type Communicator struct {
|
||||
connInfo *connectionInfo
|
||||
client *winrm.Client
|
||||
endpoint *winrm.Endpoint
|
||||
}
|
||||
|
||||
// New creates a new communicator implementation over WinRM.
|
||||
//func New(endpoint *winrm.Endpoint, user string, password string, timeout time.Duration) (*communicator, error) {
|
||||
func New(s *terraform.InstanceState) (*Communicator, error) {
|
||||
connInfo, err := parseConnectionInfo(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoint := &winrm.Endpoint{
|
||||
Host: connInfo.Host,
|
||||
Port: connInfo.Port,
|
||||
HTTPS: connInfo.HTTPS,
|
||||
Insecure: connInfo.Insecure,
|
||||
CACert: connInfo.CACert,
|
||||
}
|
||||
|
||||
comm := &Communicator{
|
||||
connInfo: connInfo,
|
||||
endpoint: endpoint,
|
||||
}
|
||||
|
||||
return comm, nil
|
||||
}
|
||||
|
||||
// Connect implementation of communicator.Communicator interface
|
||||
func (c *Communicator) Connect(o terraform.UIOutput) error {
|
||||
if c.client != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
params := winrm.DefaultParameters()
|
||||
params.Timeout = formatDuration(c.Timeout())
|
||||
|
||||
client, err := winrm.NewClientWithParameters(
|
||||
c.endpoint, c.connInfo.User, c.connInfo.Password, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o != nil {
|
||||
o.Output(fmt.Sprintf(
|
||||
"Connecting to remote host via WinRM...\n"+
|
||||
" Host: %s\n"+
|
||||
" Port: %d\n"+
|
||||
" User: %s\n"+
|
||||
" Password: %t\n"+
|
||||
" HTTPS: %t\n"+
|
||||
" Insecure: %t\n"+
|
||||
" CACert: %t",
|
||||
c.connInfo.Host,
|
||||
c.connInfo.Port,
|
||||
c.connInfo.User,
|
||||
c.connInfo.Password != "",
|
||||
c.connInfo.HTTPS,
|
||||
c.connInfo.Insecure,
|
||||
c.connInfo.CACert != nil,
|
||||
))
|
||||
}
|
||||
|
||||
log.Printf("connecting to remote shell using WinRM")
|
||||
shell, err := client.CreateShell()
|
||||
if err != nil {
|
||||
log.Printf("connection error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = shell.Close()
|
||||
if err != nil {
|
||||
log.Printf("error closing connection: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if o != nil {
|
||||
o.Output("Connected!")
|
||||
}
|
||||
|
||||
c.client = client
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect implementation of communicator.Communicator interface
|
||||
func (c *Communicator) Disconnect() error {
|
||||
c.client = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout implementation of communicator.Communicator interface
|
||||
func (c *Communicator) Timeout() time.Duration {
|
||||
return c.connInfo.TimeoutVal
|
||||
}
|
||||
|
||||
// ScriptPath implementation of communicator.Communicator interface
|
||||
func (c *Communicator) ScriptPath() string {
|
||||
return c.connInfo.ScriptPath
|
||||
}
|
||||
|
||||
// Start implementation of communicator.Communicator interface
|
||||
func (c *Communicator) Start(rc *remote.Cmd) error {
|
||||
log.Printf("starting remote command: %s", rc.Command)
|
||||
|
||||
err := c.Connect(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shell, err := c.client.CreateShell()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd, err := shell.Execute(rc.Command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go runCommand(shell, cmd, rc)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCommand(shell *winrm.Shell, cmd *winrm.Command, rc *remote.Cmd) {
|
||||
defer shell.Close()
|
||||
|
||||
go io.Copy(rc.Stdout, cmd.Stdout)
|
||||
go io.Copy(rc.Stderr, cmd.Stderr)
|
||||
|
||||
cmd.Wait()
|
||||
rc.SetExited(cmd.ExitCode())
|
||||
}
|
||||
|
||||
// Upload implementation of communicator.Communicator interface
|
||||
func (c *Communicator) Upload(path string, input io.Reader) error {
|
||||
wcp, err := c.newCopyClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return wcp.Write(path, input)
|
||||
}
|
||||
|
||||
// UploadScript implementation of communicator.Communicator interface
|
||||
func (c *Communicator) UploadScript(path string, input io.Reader) error {
|
||||
return c.Upload(path, input)
|
||||
}
|
||||
|
||||
// UploadDir implementation of communicator.Communicator interface
|
||||
func (c *Communicator) UploadDir(dst string, src string) error {
|
||||
log.Printf("Upload dir '%s' to '%s'", src, dst)
|
||||
wcp, err := c.newCopyClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return wcp.Copy(src, dst)
|
||||
}
|
||||
|
||||
func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) {
|
||||
addr := fmt.Sprintf("%s:%d", c.endpoint.Host, c.endpoint.Port)
|
||||
return winrmcp.New(addr, &winrmcp.Config{
|
||||
Auth: winrmcp.Auth{
|
||||
User: c.connInfo.User,
|
||||
Password: c.connInfo.Password,
|
||||
},
|
||||
OperationTimeout: c.Timeout(),
|
||||
MaxOperationsPerShell: 15, // lowest common denominator
|
||||
})
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package winrm
|
|
@ -0,0 +1,109 @@
|
|||
package winrm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultUser is used if there is no user given
|
||||
DefaultUser = "Administrator"
|
||||
|
||||
// DefaultPort is used if there is no port given
|
||||
DefaultPort = 5985
|
||||
|
||||
// DefaultScriptPath is used as the path to copy the file to
|
||||
// for remote execution if not provided otherwise.
|
||||
DefaultScriptPath = "C:/Temp/script.cmd"
|
||||
|
||||
// DefaultTimeout is used if there is no timeout given
|
||||
DefaultTimeout = 5 * time.Minute
|
||||
)
|
||||
|
||||
// connectionInfo is decoded from the ConnInfo of the resource. These are the
|
||||
// only keys we look at. If a KeyFile is given, that is used instead
|
||||
// of a password.
|
||||
type connectionInfo struct {
|
||||
User string
|
||||
Password string
|
||||
Host string
|
||||
Port int
|
||||
HTTPS bool
|
||||
Insecure bool
|
||||
CACert *[]byte `mapstructure:"ca_cert"`
|
||||
Timeout string
|
||||
ScriptPath string `mapstructure:"script_path"`
|
||||
TimeoutVal time.Duration `mapstructure:"-"`
|
||||
}
|
||||
|
||||
// parseConnectionInfo is used to convert the ConnInfo of the InstanceState into
|
||||
// a ConnectionInfo struct
|
||||
func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) {
|
||||
connInfo := &connectionInfo{}
|
||||
decConf := &mapstructure.DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: connInfo,
|
||||
}
|
||||
dec, err := mapstructure.NewDecoder(decConf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := dec.Decode(s.Ephemeral.ConnInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if connInfo.User == "" {
|
||||
connInfo.User = DefaultUser
|
||||
}
|
||||
if connInfo.Port == 0 {
|
||||
connInfo.Port = DefaultPort
|
||||
}
|
||||
// We also check on script paths which point to the default Windows TEMP folder because
|
||||
// files which are put in there very early in the boot process could get cleaned/deleted
|
||||
// before you had the change to execute them.
|
||||
//
|
||||
// TODO (SvH) Needs some more debugging to fully understand the exact sequence of events
|
||||
// causing this...
|
||||
if connInfo.ScriptPath == "" || strings.HasPrefix(connInfo.ScriptPath, "C:/Windows/Temp") {
|
||||
connInfo.ScriptPath = DefaultScriptPath
|
||||
}
|
||||
if connInfo.Timeout != "" {
|
||||
connInfo.TimeoutVal = safeDuration(connInfo.Timeout, DefaultTimeout)
|
||||
} else {
|
||||
connInfo.TimeoutVal = DefaultTimeout
|
||||
}
|
||||
return connInfo, nil
|
||||
}
|
||||
|
||||
// safeDuration returns either the parsed duration or a default value
|
||||
func safeDuration(dur string, defaultDur time.Duration) time.Duration {
|
||||
d, err := time.ParseDuration(dur)
|
||||
if err != nil {
|
||||
log.Printf("Invalid duration '%s', using default of %s", dur, defaultDur)
|
||||
return defaultDur
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func formatDuration(duration time.Duration) string {
|
||||
h := int(duration.Hours())
|
||||
m := int(duration.Minutes()) - (h * 60)
|
||||
s := int(duration.Seconds()) - (h*3600 + m*60)
|
||||
|
||||
res := "PT"
|
||||
if h > 0 {
|
||||
res = fmt.Sprintf("%s%dH", res, h)
|
||||
}
|
||||
if m > 0 {
|
||||
res = fmt.Sprintf("%s%dM", res, m)
|
||||
}
|
||||
if s > 0 {
|
||||
res = fmt.Sprintf("%s%dS", res, s)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package winrm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestProvisioner_connInfo(t *testing.T) {
|
||||
r := &terraform.InstanceState{
|
||||
Ephemeral: terraform.EphemeralState{
|
||||
ConnInfo: map[string]string{
|
||||
"type": "winrm",
|
||||
"user": "Administrator",
|
||||
"password": "supersecret",
|
||||
"host": "127.0.0.1",
|
||||
"port": "5985",
|
||||
"https": "true",
|
||||
"timeout": "30s",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
conf, err := parseConnectionInfo(r)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if conf.User != "Administrator" {
|
||||
t.Fatalf("expected: %v: got: %v", "Administrator", conf)
|
||||
}
|
||||
if conf.Password != "supersecret" {
|
||||
t.Fatalf("expected: %v: got: %v", "supersecret", conf)
|
||||
}
|
||||
if conf.Host != "127.0.0.1" {
|
||||
t.Fatalf("expected: %v: got: %v", "127.0.0.1", conf)
|
||||
}
|
||||
if conf.Port != 5985 {
|
||||
t.Fatalf("expected: %v: got: %v", 5985, conf)
|
||||
}
|
||||
if conf.HTTPS != true {
|
||||
t.Fatalf("expected: %v: got: %v", true, conf)
|
||||
}
|
||||
if conf.Timeout != "30s" {
|
||||
t.Fatalf("expected: %v: got: %v", "30s", conf)
|
||||
}
|
||||
if conf.ScriptPath != DefaultScriptPath {
|
||||
t.Fatalf("expected: %v: got: %v", DefaultScriptPath, conf)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_formatDuration(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
InstanceState *terraform.InstanceState
|
||||
Result string
|
||||
}{
|
||||
"testSeconds": {
|
||||
InstanceState: &terraform.InstanceState{
|
||||
Ephemeral: terraform.EphemeralState{
|
||||
ConnInfo: map[string]string{
|
||||
"timeout": "90s",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Result: "PT1M30S",
|
||||
},
|
||||
"testMinutes": {
|
||||
InstanceState: &terraform.InstanceState{
|
||||
Ephemeral: terraform.EphemeralState{
|
||||
ConnInfo: map[string]string{
|
||||
"timeout": "5m",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Result: "PT5M",
|
||||
},
|
||||
"testHours": {
|
||||
InstanceState: &terraform.InstanceState{
|
||||
Ephemeral: terraform.EphemeralState{
|
||||
ConnInfo: map[string]string{
|
||||
"timeout": "1h",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Result: "PT1H",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
conf, err := parseConnectionInfo(tc.InstanceState)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
result := formatDuration(conf.TimeoutVal)
|
||||
if result != tc.Result {
|
||||
t.Fatalf("%s: expected: %s got: %s", name, tc.Result, result)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue