zrepl/sshbytestream/ssh.go

124 lines
2.6 KiB
Go
Raw Normal View History

2017-04-14 19:26:32 +02:00
package sshbytestream
import (
2017-04-26 20:25:53 +02:00
"fmt"
"io"
"os"
"runtime"
"syscall"
"github.com/zrepl/zrepl/util"
)
type Error struct {
Stderr []byte
WaitErr error
}
func (e Error) Error() string {
return fmt.Sprintf("ssh command failed with error: %v. stderr:\n%s\n", e.WaitErr, e.Stderr)
}
2017-04-29 20:09:09 +02:00
type SSHTransport struct {
Host string
User string
Port uint16
IdentityFile string
SSHCommand string
Options []string
2017-04-29 20:09:09 +02:00
}
var SSHCommand string = "ssh"
var SSHServerAliveInterval uint = 60
func Incoming() (wc io.ReadWriteCloser, err error) {
2017-04-14 19:26:32 +02:00
// derivce ReadWriteCloser from stdin & stdout
return IncomingReadWriteCloser{}, nil
2017-04-14 19:26:32 +02:00
}
type IncomingReadWriteCloser struct{}
func (f IncomingReadWriteCloser) Read(p []byte) (n int, err error) {
return os.Stdin.Read(p)
2017-04-14 19:26:32 +02:00
}
func (f IncomingReadWriteCloser) Write(p []byte) (n int, err error) {
return os.Stdout.Write(p)
}
2017-04-14 19:26:32 +02:00
2017-04-26 20:25:53 +02:00
func (f IncomingReadWriteCloser) Close() (err error) {
if err = os.Stdin.Close(); err != nil {
return
}
if err = os.Stdout.Close(); err != nil {
return
}
return
2017-04-14 19:26:32 +02:00
}
type OutgoingSSHByteStream struct {
c *util.IOCommand
}
func Outgoing(remote SSHTransport) (s OutgoingSSHByteStream, err error) {
sshArgs := make([]string, 0, 2*len(remote.Options)+4)
sshArgs = append(sshArgs,
"-p", fmt.Sprintf("%d", remote.Port),
"-q",
"-i", remote.IdentityFile,
"-o", "BatchMode=yes",
"-o", fmt.Sprintf("ServerAliveInterval=%d", SSHServerAliveInterval),
)
2017-04-26 20:25:53 +02:00
for _, option := range remote.Options {
sshArgs = append(sshArgs, "-o", option)
}
sshArgs = append(sshArgs, fmt.Sprintf("%s@%s", remote.User, remote.Host))
var sshCommand = SSHCommand
if len(remote.SSHCommand) > 0 {
sshCommand = SSHCommand
}
if s.c, err = util.NewIOCommand(sshCommand, sshArgs, util.IOCommandStderrBufSize); err != nil {
return
}
// Clear environment of cmd, ssh shall not rely on SSH_AUTH_SOCK, etc.
s.c.Cmd.Env = []string{}
err = s.c.Start()
return
}
func (s OutgoingSSHByteStream) Read(p []byte) (n int, err error) {
return s.c.Read(p)
}
func (s OutgoingSSHByteStream) Write(p []byte) (n int, err error) {
return s.c.Write(p)
}
func (s OutgoingSSHByteStream) Close() (err error) {
err = s.c.Close()
if err == nil || s.c.ExitResult == nil {
return
}
// SSH catches SIGTERM and has different exit codes on different platforms
ws := s.c.ExitResult.WaitStatus
switch runtime.GOOS {
case "linux":
if ws.ExitStatus() == 128+int(syscall.SIGTERM) { // OpenSSH_7.5p1, OpenSSL 1.1.0f 25 May 2017 Arch Linux
err = nil
}
case "freebsd": // OpenSSH_7.2p2, OpenSSL 1.0.2k-freebsd 26 Jan 2017
if ws.ExitStatus() == 255 {
err = nil
}
default: // TODO
}
2017-04-14 19:26:32 +02:00
return
2017-04-14 19:26:32 +02:00
}