github.com/kevinklinger/open_terraform@v1.3.6/noninternal/communicator/winrm/communicator.go (about) 1 package winrm 2 3 import ( 4 "fmt" 5 "io" 6 "log" 7 "math/rand" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/kevinklinger/open_terraform/noninternal/communicator/remote" 13 "github.com/kevinklinger/open_terraform/noninternal/provisioners" 14 "github.com/masterzen/winrm" 15 "github.com/packer-community/winrmcp/winrmcp" 16 "github.com/zclconf/go-cty/cty" 17 ) 18 19 // Communicator represents the WinRM communicator 20 type Communicator struct { 21 connInfo *connectionInfo 22 client *winrm.Client 23 endpoint *winrm.Endpoint 24 rand *rand.Rand 25 } 26 27 // New creates a new communicator implementation over WinRM. 28 func New(v cty.Value) (*Communicator, error) { 29 connInfo, err := parseConnectionInfo(v) 30 if err != nil { 31 return nil, err 32 } 33 34 endpoint := &winrm.Endpoint{ 35 Host: connInfo.Host, 36 Port: int(connInfo.Port), 37 HTTPS: connInfo.HTTPS, 38 Insecure: connInfo.Insecure, 39 Timeout: connInfo.TimeoutVal, 40 } 41 if len(connInfo.CACert) > 0 { 42 endpoint.CACert = []byte(connInfo.CACert) 43 } 44 45 comm := &Communicator{ 46 connInfo: connInfo, 47 endpoint: endpoint, 48 // Seed our own rand source so that script paths are not deterministic 49 rand: rand.New(rand.NewSource(time.Now().UnixNano())), 50 } 51 52 return comm, nil 53 } 54 55 // Connect implementation of communicator.Communicator interface 56 func (c *Communicator) Connect(o provisioners.UIOutput) error { 57 // Set the client to nil since we'll (re)create it 58 c.client = nil 59 60 params := winrm.DefaultParameters 61 params.Timeout = formatDuration(c.Timeout()) 62 if c.connInfo.NTLM { 63 params.TransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} } 64 } 65 66 client, err := winrm.NewClientWithParameters( 67 c.endpoint, c.connInfo.User, c.connInfo.Password, params) 68 if err != nil { 69 return err 70 } 71 72 if o != nil { 73 o.Output(fmt.Sprintf( 74 "Connecting to remote host via WinRM...\n"+ 75 " Host: %s\n"+ 76 " Port: %d\n"+ 77 " User: %s\n"+ 78 " Password: %t\n"+ 79 " HTTPS: %t\n"+ 80 " Insecure: %t\n"+ 81 " NTLM: %t\n"+ 82 " CACert: %t", 83 c.connInfo.Host, 84 c.connInfo.Port, 85 c.connInfo.User, 86 c.connInfo.Password != "", 87 c.connInfo.HTTPS, 88 c.connInfo.Insecure, 89 c.connInfo.NTLM, 90 c.connInfo.CACert != "", 91 )) 92 } 93 94 log.Printf("[DEBUG] connecting to remote shell using WinRM") 95 shell, err := client.CreateShell() 96 if err != nil { 97 log.Printf("[ERROR] error creating shell: %s", err) 98 return err 99 } 100 101 err = shell.Close() 102 if err != nil { 103 log.Printf("[ERROR] error closing shell: %s", err) 104 return err 105 } 106 107 if o != nil { 108 o.Output("Connected!") 109 } 110 111 c.client = client 112 113 return nil 114 } 115 116 // Disconnect implementation of communicator.Communicator interface 117 func (c *Communicator) Disconnect() error { 118 c.client = nil 119 return nil 120 } 121 122 // Timeout implementation of communicator.Communicator interface 123 func (c *Communicator) Timeout() time.Duration { 124 return c.connInfo.TimeoutVal 125 } 126 127 // ScriptPath implementation of communicator.Communicator interface 128 func (c *Communicator) ScriptPath() string { 129 return strings.Replace( 130 c.connInfo.ScriptPath, "%RAND%", 131 strconv.FormatInt(int64(c.rand.Int31()), 10), -1) 132 } 133 134 // Start implementation of communicator.Communicator interface 135 func (c *Communicator) Start(rc *remote.Cmd) error { 136 rc.Init() 137 log.Printf("[DEBUG] starting remote command: %s", rc.Command) 138 139 // TODO: make sure communicators always connect first, so we can get output 140 // from the connection. 141 if c.client == nil { 142 log.Println("[WARN] winrm client not connected, attempting to connect") 143 if err := c.Connect(nil); err != nil { 144 return err 145 } 146 } 147 148 status, err := c.client.Run(rc.Command, rc.Stdout, rc.Stderr) 149 rc.SetExitStatus(status, err) 150 151 return nil 152 } 153 154 // Upload implementation of communicator.Communicator interface 155 func (c *Communicator) Upload(path string, input io.Reader) error { 156 wcp, err := c.newCopyClient() 157 if err != nil { 158 return err 159 } 160 log.Printf("[DEBUG] Uploading file to '%s'", path) 161 return wcp.Write(path, input) 162 } 163 164 // UploadScript implementation of communicator.Communicator interface 165 func (c *Communicator) UploadScript(path string, input io.Reader) error { 166 return c.Upload(path, input) 167 } 168 169 // UploadDir implementation of communicator.Communicator interface 170 func (c *Communicator) UploadDir(dst string, src string) error { 171 log.Printf("[DEBUG] Uploading dir '%s' to '%s'", src, dst) 172 wcp, err := c.newCopyClient() 173 if err != nil { 174 return err 175 } 176 return wcp.Copy(src, dst) 177 } 178 179 func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) { 180 addr := fmt.Sprintf("%s:%d", c.endpoint.Host, c.endpoint.Port) 181 182 config := winrmcp.Config{ 183 Auth: winrmcp.Auth{ 184 User: c.connInfo.User, 185 Password: c.connInfo.Password, 186 }, 187 Https: c.connInfo.HTTPS, 188 Insecure: c.connInfo.Insecure, 189 OperationTimeout: c.Timeout(), 190 MaxOperationsPerShell: 15, // lowest common denominator 191 } 192 193 if c.connInfo.NTLM { 194 config.TransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} } 195 } 196 197 if c.connInfo.CACert != "" { 198 config.CACertBytes = []byte(c.connInfo.CACert) 199 } 200 201 return winrmcp.New(addr, &config) 202 }