This commit is contained in:
Josh Komoroske 2018-03-06 05:58:57 +00:00 committed by GitHub
commit d8eac07970
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 1 deletions

View File

@ -71,6 +71,11 @@ func main() {
EnvVar: "PLUGIN_PORT,SSH_PORT", EnvVar: "PLUGIN_PORT,SSH_PORT",
Value: 22, Value: 22,
}, },
cli.DurationFlag{
Name: "retry-timeout",
Usage: "connection retry timeout",
EnvVar: "PLUGIN_RETRY_TIMEOUT",
},
cli.BoolFlag{ cli.BoolFlag{
Name: "sync", Name: "sync",
Usage: "sync mode", Usage: "sync mode",
@ -194,6 +199,7 @@ func run(c *cli.Context) error {
Password: c.String("password"), Password: c.String("password"),
Host: c.StringSlice("host"), Host: c.StringSlice("host"),
Port: c.Int("port"), Port: c.Int("port"),
RetryTimeout: c.Duration("retry-timeout"),
Timeout: c.Duration("timeout"), Timeout: c.Duration("timeout"),
CommandTimeout: c.Int("command.timeout"), CommandTimeout: c.Int("command.timeout"),
Script: c.StringSlice("script"), Script: c.StringSlice("script"),

View File

@ -10,6 +10,7 @@ import (
"github.com/appleboy/easyssh-proxy" "github.com/appleboy/easyssh-proxy"
"io" "io"
"net"
) )
const ( const (
@ -29,6 +30,7 @@ type (
Host []string Host []string
Port int Port int
Timeout time.Duration Timeout time.Duration
RetryTimeout time.Duration
CommandTimeout int CommandTimeout int
Script []string Script []string
Secrets []string Secrets []string
@ -90,7 +92,7 @@ func (p Plugin) exec(host string, wg *sync.WaitGroup, errChannel chan error) {
p.log(host, "======END======") p.log(host, "======END======")
} }
stdoutChan, stderrChan, doneChan, errChan, err := ssh.Stream(strings.Join(p.Config.Script, "\n"), p.Config.CommandTimeout) stdoutChan, stderrChan, doneChan, errChan, err := retryStream(ssh, p)
if err != nil { if err != nil {
errChannel <- err errChannel <- err
} else { } else {
@ -179,3 +181,34 @@ func (p Plugin) Exec() error {
return nil return nil
} }
func retryStream(ssh *easyssh.MakeConfig, p Plugin) (<-chan string, <-chan string, <-chan bool, <-chan error, error) {
var (
timeout = time.After(p.Config.RetryTimeout)
wait = time.Second
)
for {
stdoutChan, stderrChan, doneChan, errChan, err := ssh.Stream(strings.Join(p.Config.Script, "\n"), p.Config.CommandTimeout)
// If there was no error, return all channels
if err == nil {
return stdoutChan, stderrChan, doneChan, errChan, nil
}
// If the error was not a net.OpError, return that error
if _, ok := err.(*net.OpError); !ok {
return nil, nil, nil, nil, err
}
select {
case <-timeout:
return nil, nil, nil, nil, err
case <-time.After(wait):
break
}
// Double our back-off time
wait *= 2
}
}

View File

@ -8,6 +8,8 @@ import (
"github.com/appleboy/easyssh-proxy" "github.com/appleboy/easyssh-proxy"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"strings" "strings"
"time"
) )
func TestMissingHostOrUser(t *testing.T) { func TestMissingHostOrUser(t *testing.T) {
@ -457,6 +459,56 @@ func TestEnvOutput(t *testing.T) {
assert.Equal(t, unindent(expected), unindent(buffer.String())) assert.Equal(t, unindent(expected), unindent(buffer.String()))
} }
// TestConnectTimeoutRetry tests that when a network error occurs, the connect
// is retried until it either succeeds or the configured timeout is hit.
func TestConnectTimeoutRetry(t *testing.T) {
start := time.Now()
plugin := Plugin{
Config: Config{
Host: []string{"localhost"},
UserName: "drone-scp",
Port: 2200,
KeyPath: "./tests/.ssh/id_rsa",
Script: []string{"exit"},
RetryTimeout: 15 * time.Second,
Sync: true,
},
}
err := plugin.Exec()
assert.NotNil(t, err)
end := time.Now()
assert.WithinDuration(t, start.Local().Add(15*time.Second), end, 2*time.Second)
}
// TestConnectTimeoutRetry tests that when a non-network error occurs, the connect
// is not retried and instead returns the error immediately.
func TestConnectTimeoutImmediate(t *testing.T) {
start := time.Now()
plugin := Plugin{
Config: Config{
Host: []string{"localhost"},
UserName: "drone-scp",
Port: 22,
Key: "",
Script: []string{"exit"},
RetryTimeout: 60 * time.Second,
Sync: true,
},
}
err := plugin.Exec()
assert.NotNil(t, err)
end := time.Now()
assert.WithinDuration(t, start.Local().Add(time.Second), end, 2*time.Second)
}
func unindent(text string) string { func unindent(text string) string {
return strings.TrimSpace(strings.Replace(text, "\t", "", -1)) return strings.TrimSpace(strings.Replace(text, "\t", "", -1))
} }