Commit 7a397580 authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

helper/communicator: WinRM stuff

parent 0c0f8766
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"os"
"github.com/masterzen/winrm/winrm" "github.com/masterzen/winrm/winrm"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
...@@ -92,7 +93,7 @@ func runCommand(shell *winrm.Shell, cmd *winrm.Command, rc *packer.RemoteCmd) { ...@@ -92,7 +93,7 @@ func runCommand(shell *winrm.Shell, cmd *winrm.Command, rc *packer.RemoteCmd) {
} }
// Upload implementation of communicator.Communicator interface // 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, _ *os.FileInfo) error {
wcp, err := c.newCopyClient() wcp, err := c.newCopyClient()
if err != nil { if err != nil {
return err return err
...@@ -101,13 +102,8 @@ func (c *Communicator) Upload(path string, input io.Reader) error { ...@@ -101,13 +102,8 @@ func (c *Communicator) Upload(path string, input io.Reader) error {
return wcp.Write(path, input) 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 // UploadDir implementation of communicator.Communicator interface
func (c *Communicator) UploadDir(dst string, src string) error { func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
log.Printf("Uploading dir '%s' to '%s'", src, dst) log.Printf("Uploading dir '%s' to '%s'", src, dst)
wcp, err := c.newCopyClient() wcp, err := c.newCopyClient()
if err != nil { if err != nil {
...@@ -116,6 +112,10 @@ func (c *Communicator) UploadDir(dst string, src string) error { ...@@ -116,6 +112,10 @@ func (c *Communicator) UploadDir(dst string, src string) error {
return wcp.Copy(src, dst) return wcp.Copy(src, dst)
} }
func (c *Communicator) Download(src string, dst io.Writer) error {
panic("download not implemented")
}
func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) { func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) {
addr := fmt.Sprintf("%s:%d", c.endpoint.Host, c.endpoint.Port) addr := fmt.Sprintf("%s:%d", c.endpoint.Host, c.endpoint.Port)
return winrmcp.New(addr, &winrmcp.Config{ return winrmcp.New(addr, &winrmcp.Config{
......
...@@ -12,7 +12,9 @@ import ( ...@@ -12,7 +12,9 @@ import (
// Config is the common configuration that communicators allow within // Config is the common configuration that communicators allow within
// a builder. // a builder.
type Config struct { type Config struct {
Type string `mapstructure:"communicator"` Type string `mapstructure:"communicator"`
// SSH
SSHHost string `mapstructure:"ssh_host"` SSHHost string `mapstructure:"ssh_host"`
SSHPort int `mapstructure:"ssh_port"` SSHPort int `mapstructure:"ssh_port"`
SSHUsername string `mapstructure:"ssh_username"` SSHUsername string `mapstructure:"ssh_username"`
...@@ -20,6 +22,13 @@ type Config struct { ...@@ -20,6 +22,13 @@ type Config struct {
SSHPrivateKey string `mapstructure:"ssh_private_key_file"` SSHPrivateKey string `mapstructure:"ssh_private_key_file"`
SSHPty bool `mapstructure:"ssh_pty"` SSHPty bool `mapstructure:"ssh_pty"`
SSHTimeout time.Duration `mapstructure:"ssh_timeout"` SSHTimeout time.Duration `mapstructure:"ssh_timeout"`
// WinRM
WinRMUser string `mapstructure:"winrm_username"`
WinRMPassword string `mapstructure:"winrm_password"`
WinRMHost string `mapstructure:"winrm_host"`
WinRMPort int `mapstructure:"winrm_port"`
WinRMTimeout time.Duration `mapstructure:"winrm_timeout"`
} }
func (c *Config) Prepare(ctx *interpolate.Context) []error { func (c *Config) Prepare(ctx *interpolate.Context) []error {
...@@ -27,6 +36,22 @@ func (c *Config) Prepare(ctx *interpolate.Context) []error { ...@@ -27,6 +36,22 @@ func (c *Config) Prepare(ctx *interpolate.Context) []error {
c.Type = "ssh" c.Type = "ssh"
} }
var errs []error
switch c.Type {
case "ssh":
if es := c.prepareSSH(ctx); len(es) > 0 {
errs = append(errs, es...)
}
case "winrm":
if es := c.prepareWinRM(ctx); len(es) > 0 {
errs = append(errs, es...)
}
}
return errs
}
func (c *Config) prepareSSH(ctx *interpolate.Context) []error {
if c.SSHPort == 0 { if c.SSHPort == 0 {
c.SSHPort = 22 c.SSHPort = 22
} }
...@@ -37,21 +62,36 @@ func (c *Config) Prepare(ctx *interpolate.Context) []error { ...@@ -37,21 +62,36 @@ func (c *Config) Prepare(ctx *interpolate.Context) []error {
// Validation // Validation
var errs []error var errs []error
if c.Type == "ssh" { if c.SSHUsername == "" {
if c.SSHUsername == "" { errs = append(errs, errors.New("An ssh_username must be specified"))
errs = append(errs, errors.New("An ssh_username must be specified")) }
}
if c.SSHPrivateKey != "" { if c.SSHPrivateKey != "" {
if _, err := os.Stat(c.SSHPrivateKey); err != nil { if _, err := os.Stat(c.SSHPrivateKey); err != nil {
errs = append(errs, fmt.Errorf( errs = append(errs, fmt.Errorf(
"ssh_private_key_file is invalid: %s", err)) "ssh_private_key_file is invalid: %s", err))
} else if _, err := SSHFileSigner(c.SSHPrivateKey); err != nil { } else if _, err := SSHFileSigner(c.SSHPrivateKey); err != nil {
errs = append(errs, fmt.Errorf( errs = append(errs, fmt.Errorf(
"ssh_private_key_file is invalid: %s", err)) "ssh_private_key_file is invalid: %s", err))
}
} }
} }
return errs return errs
} }
func (c *Config) prepareWinRM(ctx *interpolate.Context) []error {
if c.WinRMPort == 0 {
c.WinRMPort = 5985
}
if c.WinRMTimeout == 0 {
c.WinRMTimeout = 30 * time.Minute
}
var errs []error
if c.WinRMUser == "" {
errs = append(errs, errors.New("winrm_username must be specified."))
}
return errs
}
package communicator
import (
"errors"
"fmt"
"log"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/winrm"
"github.com/mitchellh/packer/packer"
)
// StepConnectWinRM is a multistep Step implementation that waits for WinRM
// to become available. It gets the connection information from a single
// configuration when creating the step.
//
// Uses:
// ui packer.Ui
//
// Produces:
// communicator packer.Communicator
type StepConnectWinRM struct {
// All the fields below are documented on StepConnect
Config *Config
Host func(multistep.StateBag) (string, error)
WinRMConfig func(multistep.StateBag) (*WinRMConfig, error)
}
func (s *StepConnectWinRM) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
var comm packer.Communicator
var err error
cancel := make(chan struct{})
waitDone := make(chan bool, 1)
go func() {
ui.Say("Waiting for WinRM to become available...")
comm, err = s.waitForWinRM(state, cancel)
waitDone <- true
}()
log.Printf("Waiting for WinRM, up to timeout: %s", s.Config.WinRMTimeout)
timeout := time.After(s.Config.WinRMTimeout)
WaitLoop:
for {
// Wait for either WinRM to become available, a timeout to occur,
// or an interrupt to come through.
select {
case <-waitDone:
if err != nil {
ui.Error(fmt.Sprintf("Error waiting for WinRM: %s", err))
return multistep.ActionHalt
}
ui.Say("Connected to WinRM!")
state.Put("communicator", comm)
break WaitLoop
case <-timeout:
err := fmt.Errorf("Timeout waiting for WinRM.")
state.Put("error", err)
ui.Error(err.Error())
close(cancel)
return multistep.ActionHalt
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
// The step sequence was cancelled, so cancel waiting for WinRM
// and just start the halting process.
close(cancel)
log.Println("Interrupt detected, quitting waiting for WinRM.")
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *StepConnectWinRM) Cleanup(multistep.StateBag) {
}
func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan struct{}) (packer.Communicator, error) {
var comm packer.Communicator
for {
select {
case <-cancel:
log.Println("[INFO] WinRM wait cancelled. Exiting loop.")
return nil, errors.New("WinRM wait cancelled")
case <-time.After(5 * time.Second):
}
host, err := s.Host(state)
if err != nil {
log.Printf("[DEBUG] Error getting WinRM host: %s", err)
continue
}
port := s.Config.WinRMPort
user := s.Config.WinRMUser
password := s.Config.WinRMPassword
if s.WinRMConfig != nil {
config, err := s.WinRMConfig(state)
if err != nil {
log.Printf("[DEBUG] Error getting WinRM config: %s", err)
continue
}
if config.Username != "" {
user = config.Username
}
if config.Password != "" {
password = config.Password
}
}
log.Println("[INFO] Attempting WinRM connection...")
comm, err = winrm.New(&winrm.Config{
Host: host,
Port: port,
Username: user,
Password: password,
Timeout: s.Config.WinRMTimeout,
})
if err != nil {
log.Printf("[ERROR] WinRM connection err: %s", err)
continue
}
break
}
return comm, nil
}
package communicator
// WinRMConfig is configuration that can be returned at runtime to
// dynamically configure WinRM.
type WinRMConfig struct {
Username string
Password string
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment