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:
Bo-Yi Wu
2017-03-06 12:35:57 +08:00
committed by GitHub
parent ef521d1e98
commit 04c639cfa8
9 changed files with 255 additions and 239 deletions
-130
View File
@@ -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)
}
}
+17 -10
View File
@@ -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"),
},
}
+22 -19
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,5 @@
# easyssh-proxy
[![GoDoc](https://godoc.org/github.com/appleboy/easyssh-proxy?status.svg)](https://godoc.org/github.com/appleboy/easyssh-proxy) [![Build Status](http://drone.wu-boy.com/api/badges/appleboy/easyssh-proxy/status.svg)](http://drone.wu-boy.com/appleboy/easyssh-proxy) [![codecov](https://codecov.io/gh/appleboy/easyssh-proxy/branch/master/graph/badge.svg)](https://codecov.io/gh/appleboy/easyssh-proxy) [![Go Report Card](https://goreportcard.com/badge/github.com/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
View File
@@ -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
}
+12 -4
View File
@@ -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"