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