diff --git a/.gitignore b/.gitignore index 3601bf6..d40cc52 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,5 @@ _testmain.go *.test *.prof vendor -drone-sftp +drone-scp coverage.txt diff --git a/Dockerfile b/Dockerfile index 9908c35..a9e59e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,8 @@ -FROM centurylink/ca-certs +FROM alpine:3.4 + +RUN apk update && \ + apk add ca-certificates && \ + rm -rf /var/cache/apk/* ADD drone-scp / diff --git a/README.md b/README.md index 6987b89..c2c9efd 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,12 @@ Execute from the working directory: ``` docker run --rm \ - -e PLUGIN_BASE_URL=http://example.com \ + -e PLUGIN_HOST=http://example.com \ -e PLUGIN_USERNAME=xxxxxxx \ - -e PLUGIN_TOKEN=xxxxxxx \ - -e PLUGIN_JOB=xxxxxxx \ + -e PLUGIN_PASSWORD=xxxxxxx \ + -e PLUGIN_PORT=xxxxxxx \ + -e PLUGIN_KEY="$(cat ${HOME}/.ssh/id_rsa)" + -e PLUGIN_FILE=xxxxxxx \ -e DRONE_REPO_OWNER=appleboy \ -e DRONE_REPO_NAME=go-hello \ -e DRONE_COMMIT_SHA=e5e82b5eb3737205c25955dcc3dcacc839b7be52 \ diff --git a/easyssh/easyssh.go b/easyssh/easyssh.go index b3002d5..3986536 100644 --- a/easyssh/easyssh.go +++ b/easyssh/easyssh.go @@ -8,10 +8,8 @@ import ( "bufio" "fmt" "io" - "io/ioutil" "net" "os" - "os/user" "path/filepath" "golang.org/x/crypto/ssh" @@ -33,28 +31,6 @@ type MakeConfig struct { Password string } -// 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) { - usr, err := user.Current() - if err != nil { - return nil, err - } - - file := usr.HomeDir + keypath - buf, err := ioutil.ReadFile(file) - if err != nil { - return nil, err - } - - pubkey, err := ssh.ParsePrivateKey(buf) - if err != nil { - return nil, err - } - - return pubkey, nil -} - // connects to remote server using MakeConfig struct and returns *ssh.Session func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) { // auths holds the detected ssh auth methods @@ -70,8 +46,9 @@ func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) { defer sshAgent.Close() } - if pubkey, err := getKeyFile(ssh_conf.Key); err == nil { - auths = append(auths, ssh.PublicKeys(pubkey)) + if ssh_conf.Key != "" { + signer, _ := ssh.ParsePrivateKey([]byte(ssh_conf.Key)) + auths = append(auths, ssh.PublicKeys(signer)) } config := &ssh.ClientConfig{ diff --git a/main.go b/main.go index f065750..151b871 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,11 @@ func main() { Usage: "Password for password-based authentication", EnvVar: "PLUGIN_PASSWORD,SCP_PASSWORD", }, + cli.StringFlag{ + Name: "key", + Usage: "ssh private key", + EnvVar: "PLUGIN_KEY,SCP_KEY,", + }, cli.StringFlag{ Name: "path", Value: "/", @@ -127,6 +132,7 @@ func run(c *cli.Context) error { Port: c.String("port"), Username: c.String("username"), Password: c.String("password"), + Key: c.String("key"), Path: c.String("path"), File: c.StringSlice("files"), }, diff --git a/plugin.go b/plugin.go index 8f7e4ba..6746b2e 100644 --- a/plugin.go +++ b/plugin.go @@ -2,8 +2,15 @@ package main import ( "errors" + "fmt" + "io/ioutil" "log" + "os" + "os/exec" + "path/filepath" "strings" + + "github.com/appleboy/drone-scp/easyssh" ) type ( @@ -31,6 +38,7 @@ type ( Port string Username string Password string + Key string Path string File []string } @@ -43,7 +51,7 @@ type ( } ) -func trimElement(keys []string) []string { +func trimPath(keys []string) []string { var newKeys []string for _, value := range keys { @@ -51,6 +59,7 @@ func trimElement(keys []string) []string { if len(value) == 0 { continue } + newKeys = append(newKeys, value) } @@ -60,11 +69,77 @@ func trimElement(keys []string) []string { // Exec executes the plugin. func (p Plugin) Exec() error { - if len(p.Config.Host) == 0 || len(p.Config.Username) == 0 || len(p.Config.Password) == 0 { + if len(p.Config.Host) == 0 || len(p.Config.Username) == 0 || (len(p.Config.Password) == 0 && len(p.Config.Key) == 0) { log.Println("missing ssh config") return errors.New("missing ssh config") } + files := trimPath(p.Config.File) + src := strings.Join(files, " ") + dest := fmt.Sprintf("%s-%s.tar", p.Repo.Name, p.Build.Commit[:7]) + + // create a temporary file for the archive + dir, err := ioutil.TempDir("", "") + if err != nil { + return err + } + tar := filepath.Join(dir, dest) + + // run archive command + log.Println("tar all files into " + tar) + cmd := exec.Command("tar", "-cf", tar, src) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return err + } + + // Create MakeConfig instance with remote username, server address and path to private key. + ssh := &easyssh.MakeConfig{ + Server: p.Config.Host, + User: p.Config.Username, + Password: p.Config.Password, + Port: p.Config.Port, + Key: p.Config.Key, + } + + // Call Scp method with file you want to upload to remote server. + log.Println("scp file to remote server remote server.") + err = ssh.Scp(tar) + + // Handle errors + if err != nil { + log.Println(err.Error()) + return err + } + + // mkdir path + log.Println("create remote folder " + p.Config.Path) + _, err = ssh.Run(fmt.Sprintf("mkdir -p %s", p.Config.Path)) + + if err != nil { + log.Println(err.Error()) + return err + } + + // untar file + log.Println("untar remote file " + dest) + _, err = ssh.Run(fmt.Sprintf("tar -xf %s -C %s", dest, p.Config.Path)) + + if err != nil { + log.Println(err.Error()) + return err + } + + // remove tar file + log.Println("remove remote file " + dest) + _, err = ssh.Run(fmt.Sprintf("rm -rf %s", dest)) + + if err != nil { + log.Println(err.Error()) + return err + } + return nil }