helper/communicator: WinRM stuff
This commit is contained in:
parent
0c0f876654
commit
7a39758054
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/masterzen/winrm/winrm"
|
||||
"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
|
||||
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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -101,13 +102,8 @@ func (c *Communicator) Upload(path string, input io.Reader) error {
|
|||
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 {
|
||||
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
|
||||
log.Printf("Uploading dir '%s' to '%s'", src, dst)
|
||||
wcp, err := c.newCopyClient()
|
||||
if err != nil {
|
||||
|
@ -116,6 +112,10 @@ func (c *Communicator) UploadDir(dst string, src string) error {
|
|||
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) {
|
||||
addr := fmt.Sprintf("%s:%d", c.endpoint.Host, c.endpoint.Port)
|
||||
return winrmcp.New(addr, &winrmcp.Config{
|
||||
|
|
|
@ -12,7 +12,9 @@ import (
|
|||
// Config is the common configuration that communicators allow within
|
||||
// a builder.
|
||||
type Config struct {
|
||||
Type string `mapstructure:"communicator"`
|
||||
Type string `mapstructure:"communicator"`
|
||||
|
||||
// SSH
|
||||
SSHHost string `mapstructure:"ssh_host"`
|
||||
SSHPort int `mapstructure:"ssh_port"`
|
||||
SSHUsername string `mapstructure:"ssh_username"`
|
||||
|
@ -20,6 +22,13 @@ type Config struct {
|
|||
SSHPrivateKey string `mapstructure:"ssh_private_key_file"`
|
||||
SSHPty bool `mapstructure:"ssh_pty"`
|
||||
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 {
|
||||
|
@ -27,6 +36,22 @@ func (c *Config) Prepare(ctx *interpolate.Context) []error {
|
|||
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 {
|
||||
c.SSHPort = 22
|
||||
}
|
||||
|
@ -37,21 +62,36 @@ func (c *Config) Prepare(ctx *interpolate.Context) []error {
|
|||
|
||||
// Validation
|
||||
var errs []error
|
||||
if c.Type == "ssh" {
|
||||
if c.SSHUsername == "" {
|
||||
errs = append(errs, errors.New("An ssh_username must be specified"))
|
||||
}
|
||||
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))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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