| package winrm |
| |
| import ( |
| "fmt" |
| "io" |
| "log" |
| "math/rand" |
| "strconv" |
| "strings" |
| "time" |
| |
| "github.com/hashicorp/terraform/internal/communicator/remote" |
| "github.com/hashicorp/terraform/internal/provisioners" |
| "github.com/masterzen/winrm" |
| "github.com/packer-community/winrmcp/winrmcp" |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| // Communicator represents the WinRM communicator |
| type Communicator struct { |
| connInfo *connectionInfo |
| client *winrm.Client |
| endpoint *winrm.Endpoint |
| rand *rand.Rand |
| } |
| |
| // New creates a new communicator implementation over WinRM. |
| func New(v cty.Value) (*Communicator, error) { |
| connInfo, err := parseConnectionInfo(v) |
| if err != nil { |
| return nil, err |
| } |
| |
| endpoint := &winrm.Endpoint{ |
| Host: connInfo.Host, |
| Port: int(connInfo.Port), |
| HTTPS: connInfo.HTTPS, |
| Insecure: connInfo.Insecure, |
| Timeout: connInfo.TimeoutVal, |
| } |
| if len(connInfo.CACert) > 0 { |
| endpoint.CACert = []byte(connInfo.CACert) |
| } |
| |
| comm := &Communicator{ |
| connInfo: connInfo, |
| endpoint: endpoint, |
| // Seed our own rand source so that script paths are not deterministic |
| rand: rand.New(rand.NewSource(time.Now().UnixNano())), |
| } |
| |
| return comm, nil |
| } |
| |
| // Connect implementation of communicator.Communicator interface |
| func (c *Communicator) Connect(o provisioners.UIOutput) error { |
| // Set the client to nil since we'll (re)create it |
| c.client = nil |
| |
| params := winrm.DefaultParameters |
| params.Timeout = formatDuration(c.Timeout()) |
| if c.connInfo.NTLM { |
| params.TransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} } |
| } |
| |
| 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"+ |
| " NTLM: %t\n"+ |
| " CACert: %t", |
| c.connInfo.Host, |
| c.connInfo.Port, |
| c.connInfo.User, |
| c.connInfo.Password != "", |
| c.connInfo.HTTPS, |
| c.connInfo.Insecure, |
| c.connInfo.NTLM, |
| c.connInfo.CACert != "", |
| )) |
| } |
| |
| log.Printf("[DEBUG] connecting to remote shell using WinRM") |
| shell, err := client.CreateShell() |
| if err != nil { |
| log.Printf("[ERROR] error creating shell: %s", err) |
| return err |
| } |
| |
| err = shell.Close() |
| if err != nil { |
| log.Printf("[ERROR] error closing shell: %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 strings.Replace( |
| c.connInfo.ScriptPath, "%RAND%", |
| strconv.FormatInt(int64(c.rand.Int31()), 10), -1) |
| } |
| |
| // Start implementation of communicator.Communicator interface |
| func (c *Communicator) Start(rc *remote.Cmd) error { |
| rc.Init() |
| log.Printf("[DEBUG] starting remote command: %s", rc.Command) |
| |
| // TODO: make sure communicators always connect first, so we can get output |
| // from the connection. |
| if c.client == nil { |
| log.Println("[WARN] winrm client not connected, attempting to connect") |
| if err := c.Connect(nil); err != nil { |
| return err |
| } |
| } |
| |
| status, err := c.client.Run(rc.Command, rc.Stdout, rc.Stderr) |
| rc.SetExitStatus(status, err) |
| |
| return nil |
| } |
| |
| // 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 |
| } |
| log.Printf("[DEBUG] Uploading file to '%s'", path) |
| 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("[DEBUG] Uploading 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) |
| |
| config := winrmcp.Config{ |
| Auth: winrmcp.Auth{ |
| User: c.connInfo.User, |
| Password: c.connInfo.Password, |
| }, |
| Https: c.connInfo.HTTPS, |
| Insecure: c.connInfo.Insecure, |
| OperationTimeout: c.Timeout(), |
| MaxOperationsPerShell: 15, // lowest common denominator |
| } |
| |
| if c.connInfo.NTLM { |
| config.TransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} } |
| } |
| |
| if c.connInfo.CACert != "" { |
| config.CACertBytes = []byte(c.connInfo.CACert) |
| } |
| |
| return winrmcp.New(addr, &config) |
| } |