Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for remote_exec on Windows SSH #26865

Merged
merged 8 commits into from
Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 != "windows" {
hhofs marked this conversation as resolved.
Show resolved Hide resolved
// 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 != "windows" {
script.WriteString(DefaultShebang)
}

script.ReadFrom(reader)

if err := c.Upload(path, &script); err != nil {
return err
}
if c.connInfo.TargetPlatform != "windows" {
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
46 changes: 30 additions & 16 deletions communicator/ssh/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,36 @@ 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

// DefaultTargetPlatform is used if no target platform has been specified
DefaultTargetPlatform = "unix"
)

// 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 +113,15 @@ 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 connInfo.TargetPlatform == "" {
connInfo.TargetPlatform = DefaultTargetPlatform
}
hhofs marked this conversation as resolved.
Show resolved Hide resolved
if connInfo.ScriptPath == "" && connInfo.TargetPlatform == DefaultTargetPlatform {
hhofs marked this conversation as resolved.
Show resolved Hide resolved
connInfo.ScriptPath = DefaultUnixScriptPath
}
if connInfo.ScriptPath == "" && connInfo.TargetPlatform == "windows" {
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 != DefaultTargetPlatform {
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 @@ -161,7 +161,10 @@ var connectionBlockSupersetSchema = &configschema.Block{
Type: cty.String,
Optional: true,
},

"target_platform": {
Type: cty.String,
Optional: true,
},
hhofs marked this conversation as resolved.
Show resolved Hide resolved
// For type=ssh only (enforced in ssh communicator)
"private_key": {
Type: cty.String,
Expand Down
4 changes: 4 additions & 0 deletions website/docs/provisioners/connection.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ 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.
By default the script_path is set to `c:\windows\temp\terraform_%RAND%.cmd` if not provided,
so the SSH DefaultShell on windows is assumed to be CMD, if DefaultShell is set to powershell use `"script_path": "c:/windows/temp/terraform_%RAND%.ps1"`
see: https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration#configuring-the-default-shell-for-openssh-in-windows for more details
hhofs marked this conversation as resolved.
Show resolved Hide resolved

**Additional arguments only supported by the `winrm` connection type:**

Expand Down