communicator/ssh: Add support for Windows targets (#26865)

This commit is contained in:
hhofs 2020-11-12 16:00:48 +01:00 committed by GitHub
parent 5e18e44037
commit 5b99a56fde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 72 additions and 41 deletions

View File

@ -139,13 +139,15 @@ func (c *Communicator) Connect(o terraform.UIOutput) (err error) {
" Private key: %t\n"+
" Certificate: %t\n"+
" SSH Agent: %t\n"+
" Checking Host Key: %t",
" Checking Host Key: %t\n"+
" Target Platform: %s\n",
c.connInfo.Host, c.connInfo.User,
c.connInfo.Password != "",
c.connInfo.PrivateKey != "",
c.connInfo.Certificate != "",
c.connInfo.Agent,
c.connInfo.HostKey != "",
c.connInfo.TargetPlatform,
))
if c.connInfo.BastionHost != "" {
@ -343,7 +345,7 @@ func (c *Communicator) Start(cmd *remote.Cmd) error {
session.Stdout = cmd.Stdout
session.Stderr = cmd.Stderr
if !c.config.noPty {
if !c.config.noPty && c.connInfo.TargetPlatform != TargetPlatformWindows {
// Request a PTY
termModes := ssh.TerminalModes{
ssh.ECHO: 0, // do not echo
@ -425,35 +427,35 @@ func (c *Communicator) UploadScript(path string, input io.Reader) error {
if err != nil {
return fmt.Errorf("Error reading script: %s", err)
}
var script bytes.Buffer
if string(prefix) != "#!" {
if string(prefix) != "#!" && c.connInfo.TargetPlatform != TargetPlatformWindows {
script.WriteString(DefaultShebang)
}
script.ReadFrom(reader)
if err := c.Upload(path, &script); err != nil {
return err
}
if c.connInfo.TargetPlatform != TargetPlatformWindows {
var stdout, stderr bytes.Buffer
cmd := &remote.Cmd{
Command: fmt.Sprintf("chmod 0777 %s", path),
Stdout: &stdout,
Stderr: &stderr,
}
if err := c.Start(cmd); err != nil {
return fmt.Errorf(
"Error chmodding script file to 0777 in remote "+
"machine: %s", err)
}
var stdout, stderr bytes.Buffer
cmd := &remote.Cmd{
Command: fmt.Sprintf("chmod 0777 %s", path),
Stdout: &stdout,
Stderr: &stderr,
if err := cmd.Wait(); err != nil {
return fmt.Errorf(
"Error chmodding script file to 0777 in remote "+
"machine %v: %s %s", err, stdout.String(), stderr.String())
}
}
if err := c.Start(cmd); err != nil {
return fmt.Errorf(
"Error chmodding script file to 0777 in remote "+
"machine: %s", err)
}
if err := cmd.Wait(); err != nil {
return fmt.Errorf(
"Error chmodding script file to 0777 in remote "+
"machine %v: %s %s", err, stdout.String(), stderr.String())
}
return nil
}

View File

@ -29,29 +29,38 @@ const (
// DefaultPort is used if there is no port given
DefaultPort = 22
// DefaultScriptPath is used as the path to copy the file to
// for remote execution if not provided otherwise.
DefaultScriptPath = "/tmp/terraform_%RAND%.sh"
// DefaultUnixScriptPath is used as the path to copy the file to
// for remote execution on unix if not provided otherwise.
DefaultUnixScriptPath = "/tmp/terraform_%RAND%.sh"
// DefaultWindowsScriptPath is used as the path to copy the file to
// for remote execution on windows if not provided otherwise.
DefaultWindowsScriptPath = "C:/windows/temp/terraform_%RAND%.cmd"
// DefaultTimeout is used if there is no timeout given
DefaultTimeout = 5 * time.Minute
// TargetPlatformUnix used for cleaner code, and is used if no target platform has been specified
TargetPlatformUnix = "unix"
//TargetPlatformWindows used for cleaner code
TargetPlatformWindows = "windows"
)
// connectionInfo is decoded from the ConnInfo of the resource. These are the
// only keys we look at. If a PrivateKey is given, that is used instead
// of a password.
type connectionInfo struct {
User string
Password string
PrivateKey string `mapstructure:"private_key"`
Certificate string `mapstructure:"certificate"`
Host string
HostKey string `mapstructure:"host_key"`
Port int
Agent bool
Timeout string
ScriptPath string `mapstructure:"script_path"`
TimeoutVal time.Duration `mapstructure:"-"`
User string
Password string
PrivateKey string `mapstructure:"private_key"`
Certificate string `mapstructure:"certificate"`
Host string
HostKey string `mapstructure:"host_key"`
Port int
Agent bool
Timeout string
ScriptPath string `mapstructure:"script_path"`
TimeoutVal time.Duration `mapstructure:"-"`
TargetPlatform string `mapstructure:"target_platform"`
BastionUser string `mapstructure:"bastion_user"`
BastionPassword string `mapstructure:"bastion_password"`
@ -106,8 +115,19 @@ func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) {
if connInfo.Port == 0 {
connInfo.Port = DefaultPort
}
if connInfo.ScriptPath == "" {
connInfo.ScriptPath = DefaultScriptPath
// Set default targetPlatform to unix if it's empty
if connInfo.TargetPlatform == "" {
connInfo.TargetPlatform = TargetPlatformUnix
} else if connInfo.TargetPlatform != TargetPlatformUnix && connInfo.TargetPlatform != TargetPlatformWindows {
return nil, fmt.Errorf("target_platform for provisioner has to be either %s or %s", TargetPlatformUnix, TargetPlatformWindows)
}
// Choose an appropriate default script path based on the target platform. There is no single
// suitable default script path which works on both UNIX and Windows targets.
if connInfo.ScriptPath == "" && connInfo.TargetPlatform == TargetPlatformUnix {
connInfo.ScriptPath = DefaultUnixScriptPath
}
if connInfo.ScriptPath == "" && connInfo.TargetPlatform == TargetPlatformWindows {
connInfo.ScriptPath = DefaultWindowsScriptPath
}
if connInfo.Timeout != "" {
connInfo.TimeoutVal = safeDuration(connInfo.Timeout, DefaultTimeout)

View File

@ -50,7 +50,10 @@ func TestProvisioner_connInfo(t *testing.T) {
if conf.Timeout != "30s" {
t.Fatalf("bad: %v", conf)
}
if conf.ScriptPath != DefaultScriptPath {
if conf.ScriptPath != DefaultUnixScriptPath {
t.Fatalf("bad: %v", conf)
}
if conf.TargetPlatform != TargetPlatformUnix {
t.Fatalf("bad: %v", conf)
}
if conf.BastionHost != "127.0.1.1" {

View File

@ -161,8 +161,11 @@ var connectionBlockSupersetSchema = &configschema.Block{
Type: cty.String,
Optional: true,
},
// For type=ssh only (enforced in ssh communicator)
"target_platform": {
Type: cty.String,
Optional: true,
},
"private_key": {
Type: cty.String,
Optional: true,

View File

@ -123,6 +123,9 @@ block would create a dependency cycle.
* `host_key` - The public key from the remote host or the signing CA, used to
verify the connection.
* `target_platform` - The target platform to connect to. Valid values are `windows` and `unix`. Defaults to `unix` if not set.
If the platform is set to `windows`, the default `script_path` is `c:\windows\temp\terraform_%RAND%.cmd`, assuming [the SSH default shell](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration#configuring-the-default-shell-for-openssh-in-windows) is `cmd.exe`. If the SSH default shell is PowerShell, set `script_path` to `"c:/windows/temp/terraform_%RAND%.ps1"`
**Additional arguments only supported by the `winrm` connection type:**
@ -163,4 +166,4 @@ The `ssh` connection also supports the following fields to facilitate connnectio
* `bastion_certificate` - The contents of a signed CA Certificate. The certificate argument
must be used in conjunction with a `bastion_private_key`. These can be loaded from
a file on disk using the [the `file` function](/docs/configuration/functions/file.html).
a file on disk using the [the `file` function](/docs/configuration/functions/file.html).