mirror of
https://github.com/appleboy/drone-scp.git
synced 2026-06-04 10:15:05 +08:00
feat: upgrade to easyssh-proxy package. (#45)
* feat: upgrade to easyssh-proxy package. * add command timeout flag * upgrade easyssh to easyssh-proxy
This commit is contained in:
@@ -1,130 +0,0 @@
|
||||
package easyssh
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetKeyFile(t *testing.T) {
|
||||
// missing file
|
||||
_, err := getKeyFile("abc")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "open abc: no such file or directory", err.Error())
|
||||
|
||||
// wrong format
|
||||
_, err = getKeyFile("../tests/.ssh/id_rsa.pub")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "ssh: no key found", err.Error())
|
||||
|
||||
_, err = getKeyFile("../tests/.ssh/id_rsa")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestRunCommand(t *testing.T) {
|
||||
ssh := &MakeConfig{
|
||||
Server: "localhost",
|
||||
User: "drone-scp",
|
||||
Port: "22",
|
||||
KeyPath: "../tests/.ssh/id_rsa",
|
||||
}
|
||||
|
||||
output, err := ssh.Run("whoami")
|
||||
assert.Equal(t, "drone-scp\n", output)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSCPCommand(t *testing.T) {
|
||||
ssh := &MakeConfig{
|
||||
Server: "localhost",
|
||||
User: "drone-scp",
|
||||
Port: "22",
|
||||
KeyPath: "../tests/.ssh/id_rsa",
|
||||
}
|
||||
|
||||
err := ssh.Scp("../tests/a.txt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
u, err := user.Lookup("drone-scp")
|
||||
if err != nil {
|
||||
t.Fatalf("Lookup: %v", err)
|
||||
}
|
||||
|
||||
// check file exist
|
||||
if _, err := os.Stat(u.HomeDir + "/a.txt"); os.IsNotExist(err) {
|
||||
t.Fatalf("SCP-error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSCPCommandWithKey(t *testing.T) {
|
||||
ssh := &MakeConfig{
|
||||
Server: "localhost",
|
||||
User: "drone-scp",
|
||||
Port: "22",
|
||||
Key: `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA4e2D/qPN08pzTac+a8ZmlP1ziJOXk45CynMPtva0rtK/RB26
|
||||
VbfAF0hIJji7ltvnYnqCU9oFfvEM33cTn7T96+od8ib/Vz25YU8ZbstqtIskPuwC
|
||||
bv3K0mAHgsviJyRD7yM+QKTbBQEgbGuW6gtbMKhiYfiIB4Dyj7AdS/fk3v26wDgz
|
||||
7SHI5OBqu9bv1KhxQYdFEnU3PAtAqeccgzNpbH3eYLyGzuUxEIJlhpZ/uU2G9ppj
|
||||
/cSrONVPiI8Ahi4RrlZjmP5l57/sq1ClGulyLpFcMw68kP5FikyqHpHJHRBNgU57
|
||||
1y0Ph33SjBbs0haCIAcmreWEhGe+/OXnJe6VUQIDAQABAoIBAH97emORIm9DaVSD
|
||||
7mD6DqA7c5m5Tmpgd6eszU08YC/Vkz9oVuBPUwDQNIX8tT0m0KVs42VVPIyoj874
|
||||
bgZMJoucC1G8V5Bur9AMxhkShx9g9A7dNXJTmsKilRpk2TOk7wBdLp9jZoKoZBdJ
|
||||
jlp6FfaazQjjKD6zsCsMATwAoRCBpBNsmT6QDN0n0bIgY0tE6YGQaDdka0dAv68G
|
||||
R0VZrcJ9voT6+f+rgJLoojn2DAu6iXaM99Gv8FK91YCymbQlXXgrk6CyS0IHexN7
|
||||
V7a3k767KnRbrkqd3o6JyNun/CrUjQwHs1IQH34tvkWScbseRaFehcAm6mLT93RP
|
||||
muauvMECgYEA9AXGtfDMse0FhvDPZx4mx8x+vcfsLvDHcDLkf/lbyPpu97C27b/z
|
||||
ia07bu5TAXesUZrWZtKA5KeRE5doQSdTOv1N28BEr8ZwzDJwfn0DPUYUOxsN2iIy
|
||||
MheO5A45Ko7bjKJVkZ61Mb1UxtqCTF9mqu9R3PBdJGthWOd+HUvF460CgYEA7QRf
|
||||
Z8+vpGA+eSuu29e0xgRKnRzed5zXYpcI4aERc3JzBgO4Z0er9G8l66OWVGdMfpe6
|
||||
CBajC5ToIiT8zqoYxXwqJgN+glir4gJe3mm8J703QfArZiQrdk0NTi5bY7+vLLG/
|
||||
knTrtpdsKih6r3kjhuPPaAsIwmMxIydFvATKjLUCgYEAh/y4EihRSk5WKC8GxeZt
|
||||
oiZ58vT4z+fqnMIfyJmD5up48JuQNcokw/LADj/ODiFM7GUnWkGxBrvDA3H67WQm
|
||||
49bJjs8E+BfUQFdTjYnJRlpJZ+7Zt1gbNQMf5ENw5CCchTDqEq6pN0DVf8PBnSIF
|
||||
KvkXW9KvdV5J76uCAn15mDkCgYA1y8dHzbjlCz9Cy2pt1aDfTPwOew33gi7U3skS
|
||||
RTerx29aDyAcuQTLfyrROBkX4TZYiWGdEl5Bc7PYhCKpWawzrsH2TNa7CRtCOh2E
|
||||
R+V/84+GNNf04ALJYCXD9/ugQVKmR1XfDRCvKeFQFE38Y/dvV2etCswbKt5tRy2p
|
||||
xkCe/QKBgQCkLqafD4S20YHf6WTp3jp/4H/qEy2X2a8gdVVBi1uKkGDXr0n+AoVU
|
||||
ib4KbP5ovZlrjL++akMQ7V2fHzuQIFWnCkDA5c2ZAqzlM+ZN+HRG7gWur7Bt4XH1
|
||||
7XC9wlRna4b3Ln8ew3q1ZcBjXwD4ppbTlmwAfQIaZTGJUgQbdsO9YA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`,
|
||||
}
|
||||
|
||||
err := ssh.Scp("../tests/a.txt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
u, err := user.Lookup("drone-scp")
|
||||
if err != nil {
|
||||
t.Fatalf("Lookup: %v", err)
|
||||
}
|
||||
|
||||
// check file exist
|
||||
if _, err := os.Stat(u.HomeDir + "/a.txt"); os.IsNotExist(err) {
|
||||
t.Fatalf("SCP-error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSCPCommandWithPassword(t *testing.T) {
|
||||
ssh := &MakeConfig{
|
||||
Server: "localhost",
|
||||
User: "drone-scp",
|
||||
Port: "22",
|
||||
Password: "1234",
|
||||
}
|
||||
|
||||
err := ssh.Scp("../tests/b.txt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
u, err := user.Lookup("drone-scp")
|
||||
if err != nil {
|
||||
t.Fatalf("Lookup: %v", err)
|
||||
}
|
||||
|
||||
// check file exist
|
||||
if _, err := os.Stat(u.HomeDir + "/b.txt"); os.IsNotExist(err) {
|
||||
t.Fatalf("SCP-error: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,12 @@ func main() {
|
||||
Usage: "connection timeout",
|
||||
EnvVar: "PLUGIN_TIMEOUT,SCP_TIMEOUT",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "command.timeout,T",
|
||||
Usage: "command timeout",
|
||||
EnvVar: "PLUGIN_COMMAND_TIMEOUT,SSH_COMMAND_TIMEOUT",
|
||||
Value: 60,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "key, k",
|
||||
Usage: "ssh private key",
|
||||
@@ -192,16 +198,17 @@ func run(c *cli.Context) error {
|
||||
Link: c.String("build.link"),
|
||||
},
|
||||
Config: Config{
|
||||
Host: c.StringSlice("host"),
|
||||
Port: c.String("port"),
|
||||
Username: c.String("username"),
|
||||
Password: c.String("password"),
|
||||
Timeout: c.Duration("timeout"),
|
||||
Key: c.String("key"),
|
||||
KeyPath: c.String("key-path"),
|
||||
Target: c.StringSlice("target"),
|
||||
Source: c.StringSlice("source"),
|
||||
Remove: c.Bool("rm"),
|
||||
Host: c.StringSlice("host"),
|
||||
Port: c.String("port"),
|
||||
Username: c.String("username"),
|
||||
Password: c.String("password"),
|
||||
Timeout: c.Duration("timeout"),
|
||||
CommandTimeout: c.Int("command.timeout"),
|
||||
Key: c.String("key"),
|
||||
KeyPath: c.String("key-path"),
|
||||
Target: c.StringSlice("target"),
|
||||
Source: c.StringSlice("source"),
|
||||
Remove: c.Bool("rm"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/appleboy/com/random"
|
||||
"github.com/appleboy/drone-scp/easyssh"
|
||||
"github.com/appleboy/easyssh-proxy"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -37,16 +37,17 @@ type (
|
||||
|
||||
// Config for the plugin.
|
||||
Config struct {
|
||||
Host []string
|
||||
Port string
|
||||
Username string
|
||||
Password string
|
||||
Key string
|
||||
KeyPath string
|
||||
Timeout time.Duration
|
||||
Target []string
|
||||
Source []string
|
||||
Remove bool
|
||||
Host []string
|
||||
Port string
|
||||
Username string
|
||||
Password string
|
||||
Key string
|
||||
KeyPath string
|
||||
Timeout time.Duration
|
||||
CommandTimeout int
|
||||
Target []string
|
||||
Source []string
|
||||
Remove bool
|
||||
}
|
||||
|
||||
// Plugin values.
|
||||
@@ -145,9 +146,8 @@ func (p Plugin) Exec() error {
|
||||
|
||||
// Call Scp method with file you want to upload to remote server.
|
||||
p.log(host, "scp file to server.")
|
||||
err = ssh.Scp(tar)
|
||||
err := ssh.Scp(tar, dest)
|
||||
|
||||
// Handle errors
|
||||
if err != nil {
|
||||
errChannel <- err
|
||||
}
|
||||
@@ -157,7 +157,7 @@ func (p Plugin) Exec() error {
|
||||
if p.Config.Remove {
|
||||
p.log(host, "Remove target folder:", target)
|
||||
|
||||
_, err := ssh.Run(fmt.Sprintf("rm -rf %s", target))
|
||||
_, _, _, err := ssh.Run(fmt.Sprintf("rm -rf %s", target), p.Config.CommandTimeout)
|
||||
|
||||
if err != nil {
|
||||
errChannel <- err
|
||||
@@ -166,15 +166,18 @@ func (p Plugin) Exec() error {
|
||||
|
||||
// mkdir path
|
||||
p.log(host, "create folder", target)
|
||||
response, _ := ssh.Run(fmt.Sprintf("mkdir -p %s", target))
|
||||
_, errStr, _, err := ssh.Run(fmt.Sprintf("mkdir -p %s", target), p.Config.CommandTimeout)
|
||||
if err != nil {
|
||||
errChannel <- err
|
||||
}
|
||||
|
||||
if response != "" {
|
||||
errChannel <- errors.New(response)
|
||||
if len(errStr) != 0 {
|
||||
errChannel <- fmt.Errorf(errStr)
|
||||
}
|
||||
|
||||
// untar file
|
||||
p.log(host, "untar file", dest)
|
||||
_, err = ssh.Run(fmt.Sprintf("tar -xf %s -C %s", dest, target))
|
||||
_, _, _, err = ssh.Run(fmt.Sprintf("tar -xf %s -C %s", dest, target), p.Config.CommandTimeout)
|
||||
|
||||
if err != nil {
|
||||
errChannel <- err
|
||||
@@ -183,7 +186,7 @@ func (p Plugin) Exec() error {
|
||||
|
||||
// remove tar file
|
||||
p.log(host, "remove file", dest)
|
||||
_, err = ssh.Run(fmt.Sprintf("rm -rf %s", dest))
|
||||
_, _, _, err = ssh.Run(fmt.Sprintf("rm -rf %s", dest), p.Config.CommandTimeout)
|
||||
|
||||
if err != nil {
|
||||
errChannel <- err
|
||||
|
||||
+32
-40
@@ -71,12 +71,13 @@ func TestSCPFileFromPublicKey(t *testing.T) {
|
||||
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
Host: []string{"localhost"},
|
||||
Username: "drone-scp",
|
||||
Port: "22",
|
||||
KeyPath: "tests/.ssh/id_rsa",
|
||||
Source: []string{"tests/a.txt", "tests/b.txt"},
|
||||
Target: []string{u.HomeDir + "/test"},
|
||||
Host: []string{"localhost"},
|
||||
Username: "drone-scp",
|
||||
Port: "22",
|
||||
KeyPath: "tests/.ssh/id_rsa",
|
||||
Source: []string{"tests/a.txt", "tests/b.txt"},
|
||||
Target: []string{u.HomeDir + "/test"},
|
||||
CommandTimeout: 60,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -117,12 +118,13 @@ func TestSCPWildcardFileList(t *testing.T) {
|
||||
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
Host: []string{"localhost"},
|
||||
Username: "drone-scp",
|
||||
Port: "22",
|
||||
KeyPath: "tests/.ssh/id_rsa",
|
||||
Source: []string{"tests/global/*"},
|
||||
Target: []string{u.HomeDir + "/abc"},
|
||||
Host: []string{"localhost"},
|
||||
Username: "drone-scp",
|
||||
Port: "22",
|
||||
KeyPath: "tests/.ssh/id_rsa",
|
||||
Source: []string{"tests/global/*"},
|
||||
Target: []string{u.HomeDir + "/abc"},
|
||||
CommandTimeout: 60,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -188,12 +190,13 @@ func TestSCPWildcardFileList(t *testing.T) {
|
||||
func TestIncorrectPassword(t *testing.T) {
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
Host: []string{"localhost"},
|
||||
Username: "drone-scp",
|
||||
Port: "22",
|
||||
Password: "123456",
|
||||
Source: []string{"tests/a.txt", "tests/b.txt"},
|
||||
Target: []string{"/home"},
|
||||
Host: []string{"localhost"},
|
||||
Username: "drone-scp",
|
||||
Port: "22",
|
||||
Password: "123456",
|
||||
Source: []string{"tests/a.txt", "tests/b.txt"},
|
||||
Target: []string{"/home"},
|
||||
CommandTimeout: 60,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -204,28 +207,13 @@ func TestIncorrectPassword(t *testing.T) {
|
||||
func TestNoPermissionCreateFolder(t *testing.T) {
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
Host: []string{"localhost"},
|
||||
Username: "drone-scp",
|
||||
Port: "22",
|
||||
KeyPath: "tests/.ssh/id_rsa",
|
||||
Source: []string{"tests/a.txt", "tests/b.txt"},
|
||||
Target: []string{"/etc/test"},
|
||||
},
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestSourceNotFound(t *testing.T) {
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
Host: []string{"localhost"},
|
||||
Username: "drone-scp",
|
||||
Port: "22",
|
||||
KeyPath: "tests/.ssh/id_rsa",
|
||||
Source: []string{"tests/aa.txt", "tests/b.txt"},
|
||||
Target: []string{"/test"},
|
||||
Host: []string{"localhost"},
|
||||
Username: "drone-scp",
|
||||
Port: "22",
|
||||
KeyPath: "tests/.ssh/id_rsa",
|
||||
Source: []string{"tests/a.txt", "tests/b.txt"},
|
||||
Target: []string{"/etc/test"},
|
||||
CommandTimeout: 60,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -246,4 +234,8 @@ func TestGlobList(t *testing.T) {
|
||||
paterns = []string{"tests/?.txt"}
|
||||
expects = []string{"tests/a.txt", "tests/b.txt"}
|
||||
assert.Equal(t, expects, globList(paterns))
|
||||
|
||||
paterns = []string{"tests/aa.txt", "tests/b.txt"}
|
||||
expects = []string{"tests/b.txt"}
|
||||
assert.Equal(t, expects, globList(paterns))
|
||||
}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Bo-Yi Wu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
.PHONY: test drone-ssh build fmt vet errcheck lint install update release-dirs release-build release-copy release-check release coverage
|
||||
|
||||
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
|
||||
|
||||
all: build
|
||||
|
||||
fmt:
|
||||
find . -name "*.go" -type f -not -path "./vendor/*" | xargs gofmt -s -w
|
||||
|
||||
vet:
|
||||
go vet $(PACKAGES)
|
||||
|
||||
errcheck:
|
||||
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/kisielk/errcheck; \
|
||||
fi
|
||||
errcheck $(PACKAGES)
|
||||
|
||||
lint:
|
||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/golang/lint/golint; \
|
||||
fi
|
||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||
|
||||
unconvert:
|
||||
@hash unconvert > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/mdempsky/unconvert; \
|
||||
fi
|
||||
for PKG in $(PACKAGES); do unconvert -v $$PKG || exit 1; done;
|
||||
|
||||
test:
|
||||
for PKG in $(PACKAGES); do go test -v -cover -coverprofile $$GOPATH/src/$$PKG/coverage.txt $$PKG || exit 1; done;
|
||||
|
||||
html:
|
||||
go tool cover -html=coverage.txt
|
||||
|
||||
coverage:
|
||||
sed -i '/main.go/d' .cover/coverage.txt
|
||||
curl -s https://codecov.io/bash > .codecov && \
|
||||
chmod +x .codecov && \
|
||||
./.codecov -f .cover/coverage.txt
|
||||
|
||||
clean:
|
||||
go clean -x -i ./...
|
||||
rm -rf coverage.txt $(EXECUTABLE) $(DIST) vendor
|
||||
|
||||
ssh-server:
|
||||
adduser -h /home/drone-scp -s /bin/bash -D -S drone-scp
|
||||
echo drone-scp:1234 | chpasswd
|
||||
mkdir -p /home/drone-scp/.ssh
|
||||
chmod 700 /home/drone-scp/.ssh
|
||||
cp tests/.ssh/id_rsa.pub /home/drone-scp/.ssh/authorized_keys
|
||||
chown -R drone-scp /home/drone-scp/.ssh
|
||||
# install ssh and start server
|
||||
apk add --update openssh openrc
|
||||
rm -rf /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_dsa_key
|
||||
./tests/entrypoint.sh /usr/sbin/sshd -D &
|
||||
|
||||
version:
|
||||
@echo $(VERSION)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# easyssh-proxy
|
||||
|
||||
[](https://godoc.org/github.com/appleboy/easyssh-proxy) [](http://drone.wu-boy.com/appleboy/easyssh-proxy) [](https://codecov.io/gh/appleboy/easyssh-proxy) [](https://goreportcard.com/report/github.com/appleboy/easyssh-proxy)
|
||||
|
||||
easyssh-proxy provides a simple implementation of some SSH protocol features in Go
|
||||
+86
-36
@@ -35,6 +35,14 @@ type MakeConfig struct {
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type sshConfig struct {
|
||||
User string
|
||||
Key string
|
||||
KeyPath string
|
||||
Password string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// returns ssh.Signer from user you running app home path + cutted key path.
|
||||
// (ex. pubkey,err := getKeyFile("/.ssh/id_rsa") )
|
||||
func getKeyFile(keypath string) (ssh.Signer, error) {
|
||||
@@ -51,14 +59,13 @@ func getKeyFile(keypath string) (ssh.Signer, error) {
|
||||
return pubkey, nil
|
||||
}
|
||||
|
||||
// connects to remote server using MakeConfig struct and returns *ssh.Session
|
||||
func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) {
|
||||
func getSSHConfig(config sshConfig) *ssh.ClientConfig {
|
||||
// auths holds the detected ssh auth methods
|
||||
auths := []ssh.AuthMethod{}
|
||||
|
||||
// figure out what auths are requested, what is supported
|
||||
if ssh_conf.Password != "" {
|
||||
auths = append(auths, ssh.Password(ssh_conf.Password))
|
||||
if config.Password != "" {
|
||||
auths = append(auths, ssh.Password(config.Password))
|
||||
}
|
||||
|
||||
if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
@@ -66,22 +73,33 @@ func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) {
|
||||
defer sshAgent.Close()
|
||||
}
|
||||
|
||||
if ssh_conf.KeyPath != "" {
|
||||
if pubkey, err := getKeyFile(ssh_conf.KeyPath); err == nil {
|
||||
if config.KeyPath != "" {
|
||||
if pubkey, err := getKeyFile(config.KeyPath); err == nil {
|
||||
auths = append(auths, ssh.PublicKeys(pubkey))
|
||||
}
|
||||
}
|
||||
|
||||
if ssh_conf.Key != "" {
|
||||
signer, _ := ssh.ParsePrivateKey([]byte(ssh_conf.Key))
|
||||
if config.Key != "" {
|
||||
signer, _ := ssh.ParsePrivateKey([]byte(config.Key))
|
||||
auths = append(auths, ssh.PublicKeys(signer))
|
||||
}
|
||||
|
||||
config := &ssh.ClientConfig{
|
||||
Timeout: ssh_conf.Timeout,
|
||||
User: ssh_conf.User,
|
||||
return &ssh.ClientConfig{
|
||||
Timeout: config.Timeout,
|
||||
User: config.User,
|
||||
Auth: auths,
|
||||
}
|
||||
}
|
||||
|
||||
// connect to remote server using MakeConfig struct and returns *ssh.Session
|
||||
func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) {
|
||||
config := getSSHConfig(sshConfig{
|
||||
User: ssh_conf.User,
|
||||
Key: ssh_conf.Key,
|
||||
KeyPath: ssh_conf.KeyPath,
|
||||
Password: ssh_conf.Password,
|
||||
Timeout: ssh_conf.Timeout,
|
||||
})
|
||||
|
||||
client, err := ssh.Dial("tcp", net.JoinHostPort(ssh_conf.Server, ssh_conf.Port), config)
|
||||
if err != nil {
|
||||
@@ -99,63 +117,95 @@ func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) {
|
||||
// Stream returns one channel that combines the stdout and stderr of the command
|
||||
// as it is run on the remote machine, and another that sends true when the
|
||||
// command is done. The sessions and channels will then be closed.
|
||||
func (ssh_conf *MakeConfig) Stream(command string) (output chan string, done chan bool, err error) {
|
||||
func (ssh_conf *MakeConfig) Stream(command string, timeout int) (stdout chan string, stderr chan string, done chan bool, err error) {
|
||||
// connect to remote host
|
||||
session, err := ssh_conf.connect()
|
||||
if err != nil {
|
||||
return output, done, err
|
||||
return stdout, stderr, done, err
|
||||
}
|
||||
// connect to both outputs (they are of type io.Reader)
|
||||
outReader, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
return output, done, err
|
||||
return stdout, stderr, done, err
|
||||
}
|
||||
errReader, err := session.StderrPipe()
|
||||
if err != nil {
|
||||
return output, done, err
|
||||
return stdout, stderr, done, err
|
||||
}
|
||||
// combine outputs, create a line-by-line scanner
|
||||
outputReader := io.MultiReader(outReader, errReader)
|
||||
stdoutReader := io.MultiReader(outReader)
|
||||
stderrReader := io.MultiReader(errReader)
|
||||
err = session.Start(command)
|
||||
scanner := bufio.NewScanner(outputReader)
|
||||
stdoutScanner := bufio.NewScanner(stdoutReader)
|
||||
stderrScanner := bufio.NewScanner(stderrReader)
|
||||
// continuously send the command's output over the channel
|
||||
outputChan := make(chan string)
|
||||
stdoutChan := make(chan string)
|
||||
stderrChan := make(chan string)
|
||||
done = make(chan bool)
|
||||
go func(scanner *bufio.Scanner, out chan string, done chan bool) {
|
||||
defer close(outputChan)
|
||||
|
||||
go func(stdoutScanner, stderrScanner *bufio.Scanner, stdoutChan, stderrChan chan string, done chan bool) {
|
||||
defer close(stdoutChan)
|
||||
defer close(stderrChan)
|
||||
defer close(done)
|
||||
for scanner.Scan() {
|
||||
outputChan <- scanner.Text()
|
||||
|
||||
timeoutChan := time.After(time.Duration(timeout) * time.Second)
|
||||
res := make(chan bool, 1)
|
||||
|
||||
go func() {
|
||||
for stdoutScanner.Scan() {
|
||||
stdoutChan <- stdoutScanner.Text()
|
||||
}
|
||||
for stderrScanner.Scan() {
|
||||
stderrChan <- stderrScanner.Text()
|
||||
}
|
||||
// close all of our open resources
|
||||
res <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-res:
|
||||
stdoutChan <- ""
|
||||
stderrChan <- ""
|
||||
done <- true
|
||||
case <-timeoutChan:
|
||||
stdoutChan <- ""
|
||||
stderrChan <- "Run Command Timeout!"
|
||||
done <- false
|
||||
}
|
||||
// close all of our open resources
|
||||
done <- true
|
||||
|
||||
session.Close()
|
||||
}(scanner, outputChan, done)
|
||||
return outputChan, done, err
|
||||
}(stdoutScanner, stderrScanner, stdoutChan, stderrChan, done)
|
||||
return stdoutChan, stderrChan, done, err
|
||||
}
|
||||
|
||||
// Run command on remote machine and returns its stdout as a string
|
||||
func (ssh_conf *MakeConfig) Run(command string) (outStr string, err error) {
|
||||
outChan, doneChan, err := ssh_conf.Stream(command)
|
||||
func (ssh_conf *MakeConfig) Run(command string, timeout int) (outStr string, errStr string, isTimeout bool, err error) {
|
||||
stdoutChan, stderrChan, doneChan, err := ssh_conf.Stream(command, timeout)
|
||||
if err != nil {
|
||||
return outStr, err
|
||||
return outStr, errStr, isTimeout, err
|
||||
}
|
||||
// read from the output channel until the done signal is passed
|
||||
stillGoing := true
|
||||
for stillGoing {
|
||||
select {
|
||||
case <-doneChan:
|
||||
case isTimeout = <-doneChan:
|
||||
stillGoing = false
|
||||
case line := <-outChan:
|
||||
outStr += line + "\n"
|
||||
case outline := <-stdoutChan:
|
||||
if outline != "" {
|
||||
outStr += outline + "\n"
|
||||
}
|
||||
case errline := <-stderrChan:
|
||||
if errline != "" {
|
||||
errStr += errline + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
// return the concatenation of all signals from the output channel
|
||||
return outStr, err
|
||||
return outStr, errStr, isTimeout, err
|
||||
}
|
||||
|
||||
// Scp uploads sourceFile to remote machine like native scp console app.
|
||||
func (ssh_conf *MakeConfig) Scp(sourceFile string) error {
|
||||
func (ssh_conf *MakeConfig) Scp(sourceFile string, etargetFile string) error {
|
||||
session, err := ssh_conf.connect()
|
||||
|
||||
if err != nil {
|
||||
@@ -163,7 +213,7 @@ func (ssh_conf *MakeConfig) Scp(sourceFile string) error {
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
targetFile := filepath.Base(sourceFile)
|
||||
targetFile := filepath.Base(etargetFile)
|
||||
|
||||
src, srcErr := os.Open(sourceFile)
|
||||
|
||||
@@ -192,7 +242,7 @@ func (ssh_conf *MakeConfig) Scp(sourceFile string) error {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := session.Run(fmt.Sprintf("scp -t %s", targetFile)); err != nil {
|
||||
if err := session.Run(fmt.Sprintf("scp -tr %s", etargetFile)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Vendored
+12
-4
@@ -10,6 +10,14 @@
|
||||
"version": "master",
|
||||
"versionExact": "master"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "NCdmzR+clcl2/1jUPn0vFeqjwjk=",
|
||||
"path": "github.com/appleboy/easyssh-proxy",
|
||||
"revision": "89c61a4555c1578454f75ae406f4e3cdded275d2",
|
||||
"revisionTime": "2017-03-04T06:27:13Z",
|
||||
"version": "=1.0.0",
|
||||
"versionExact": "1.0.0"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=",
|
||||
"path": "github.com/davecgh/go-spew/spew",
|
||||
@@ -71,14 +79,14 @@
|
||||
{
|
||||
"checksumSHA1": "fsrFs762jlaILyqqQImS1GfvIvw=",
|
||||
"path": "golang.org/x/crypto/ssh",
|
||||
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
|
||||
"revisionTime": "2017-02-08T20:51:15Z"
|
||||
"revision": "40541ccb1c6e64c947ed6f606b8a6cb4b67d7436",
|
||||
"revisionTime": "2017-02-12T21:20:41Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "SJ3Ma3Ozavxpbh1usZWBCnzMKIc=",
|
||||
"path": "golang.org/x/crypto/ssh/agent",
|
||||
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
|
||||
"revisionTime": "2017-02-08T20:51:15Z"
|
||||
"revision": "40541ccb1c6e64c947ed6f606b8a6cb4b67d7436",
|
||||
"revisionTime": "2017-02-12T21:20:41Z"
|
||||
}
|
||||
],
|
||||
"rootPath": "github.com/appleboy/drone-scp"
|
||||
|
||||
Reference in New Issue
Block a user