helper/communicator: WinRM stuff
This commit is contained in:
parent
0c0f876654
commit
7a39758054
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
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 {
|
||||||
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 (
|
||||||
// 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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
|
|
||||||
// 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
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package communicator
|
||||||
|
|
||||||
|
// WinRMConfig is configuration that can be returned at runtime to
|
||||||
|
// dynamically configure WinRM.
|
||||||
|
type WinRMConfig struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
Loading…
Reference in New Issue