Skip to content

Commit

Permalink
Merge pull request #26895 from hashicorp/alisdair/backport-26865-0.14
Browse files Browse the repository at this point in the history
communicator/ssh: Add support for Windows targets
  • Loading branch information
alisdair committed Feb 16, 2021
2 parents 05e3a5a + 3cb12e0 commit 3762dac
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 40 deletions.
46 changes: 24 additions & 22 deletions communicator/ssh/communicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 := 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())
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
}

Expand Down
52 changes: 36 additions & 16 deletions communicator/ssh/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion communicator/ssh/provisioner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
5 changes: 4 additions & 1 deletion terraform/eval_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:**

Expand Down

0 comments on commit 3762dac

Please sign in to comment.