Repository: hypersleep/easyssh Branch: master Commit: 70879c819ea1 Files: 6 Total size: 9.1 KB Directory structure: gitextract_a379wuus/ ├── .gitignore ├── README.md ├── easyssh.go ├── easyssh_test.go └── example/ ├── run.go └── scp.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof ================================================ FILE: README.md ================================================ # easyssh ## Description Package easyssh provides a simple implementation of some SSH protocol features in Go. You can simply run command on remote server or upload a file even simple than native console SSH client. Do not need to think about Dials, sessions, defers and public keys...Let easyssh will be think about it! ## So easy to use! [Run a command on remote server and get STDOUT output](https://github.com/hypersleep/easyssh/blob/master/example/run.go) [Upload a file to remote server](https://github.com/hypersleep/easyssh/blob/master/example/scp.go) ================================================ FILE: easyssh.go ================================================ // Package easyssh provides a simple implementation of some SSH protocol // features in Go. You can simply run a command on a remote server or get a file // even simpler than native console SSH client. You don't need to think about // Dials, sessions, defers, or public keys... Let easyssh think about it! package easyssh import ( "bufio" "fmt" "io" "io/ioutil" "net" "os" "os/user" "path/filepath" "time" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" ) // Contains main authority information. // User field should be a name of user on remote server (ex. john in ssh john@example.com). // Server field should be a remote machine address (ex. example.com in ssh john@example.com) // Key is a path to private key on your local machine. // Port is SSH server port on remote machine. // Note: easyssh looking for private key in user's home directory (ex. /home/john + Key). // Then ensure your Key begins from '/' (ex. /.ssh/id_rsa) type MakeConfig struct { User string Server string Key string Port string 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 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 sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil { auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)) defer sshAgent.Close() } if pubkey, err := getKeyFile(ssh_conf.Key); err == nil { auths = append(auths, ssh.PublicKeys(pubkey)) } config := &ssh.ClientConfig{ User: ssh_conf.User, Auth: auths, } client, err := ssh.Dial("tcp", ssh_conf.Server+":"+ssh_conf.Port, config) if err != nil { return nil, err } session, err := client.NewSession() if err != nil { return nil, err } return session, nil } // 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, 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 stdout, stderr, done, err } // connect to both outputs (they are of type io.Reader) outReader, err := session.StdoutPipe() if err != nil { return stdout, stderr, done, err } errReader, err := session.StderrPipe() if err != nil { return stdout, stderr, done, err } // combine outputs, create a line-by-line scanner stdoutReader := io.MultiReader(outReader) stderrReader := io.MultiReader(errReader) err = session.Start(command) stdoutScanner := bufio.NewScanner(stdoutReader) stderrScanner := bufio.NewScanner(stderrReader) // continuously send the command's output over the channel stdoutChan := make(chan string) stderrChan := make(chan string) done = make(chan bool) go func(stdoutScanner, stderrScanner *bufio.Scanner, stdoutChan, stderrChan chan string, done chan bool) { defer close(stdoutChan) defer close(stderrChan) defer close(done) 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 } session.Close() }(stdoutScanner, stderrScanner, stdoutChan, stderrChan, done) return stdoutChan, stderrChan, done, err } // Runs command on remote machine and returns its stdout as a string 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, errStr, isTimeout, err } // read from the output channel until the done signal is passed stillGoing := true for stillGoing { select { case isTimeout = <-doneChan: stillGoing = false case outline := <-stdoutChan: outStr += outline + "\n" case errline := <-stderrChan: errStr += errline + "\n" } } // return the concatenation of all signals from the output channel return outStr, errStr, isTimeout, err } // Scp uploads sourceFile to remote machine like native scp console app. func (ssh_conf *MakeConfig) Scp(sourceFile string, etargetFile string) error { session, err := ssh_conf.connect() if err != nil { return err } defer session.Close() targetFile := filepath.Base(etargetFile) src, srcErr := os.Open(sourceFile) if srcErr != nil { return srcErr } srcStat, statErr := src.Stat() if statErr != nil { return statErr } go func() { w, _ := session.StdinPipe() fmt.Fprintln(w, "C0644", srcStat.Size(), targetFile) if srcStat.Size() > 0 { io.Copy(w, src) fmt.Fprint(w, "\x00") w.Close() } else { fmt.Fprint(w, "\x00") w.Close() } }() if err := session.Run(fmt.Sprintf("scp -tr %s", etargetFile)); err != nil { return err } return nil } ================================================ FILE: easyssh_test.go ================================================ package easyssh import ( "testing" ) var sshConfig = &MakeConfig{ User: "root", Server: "172.30.19.2", Password: "password", //Key: "/.ssh/id_rsa", Port: "22", } func TestStream(t *testing.T) { t.Parallel() // input command/output string pairs testCases := [][]string{ {`for i in $(seq 1 5); do echo "$i"; done`, "12345"}, {`echo "test"`, "test"}, } for _, testCase := range testCases { outChannel, errChannel, done, err := sshConfig.Stream(testCase[0], 10) if err != nil { t.Errorf("Stream failed: %s", err) } stillGoing := true stdout := "" stderr := "" for stillGoing { select { case <-done: stillGoing = false case line := <-outChannel: stdout += line case line := <-errChannel: stderr += line } } if stdout != testCase[1] { t.Error("Output didn't match expected: %s,%s", stdout, stderr) } } } func TestRun(t *testing.T) { t.Parallel() commands := []string{ "echo test", `for i in $(ls); do echo "$i"; done`, "ls", } for _, cmd := range commands { stdout, stderr, istimeout, err := sshConfig.Run(cmd, 10) if err != nil { t.Errorf("Run failed: %s", err) } if stdout == "" { t.Errorf("Output was empty for command: %s,%s,%s", cmd, stdout, stderr, istimeout) } } } ================================================ FILE: example/run.go ================================================ package main import ( "fmt" "github.com/hypersleep/easyssh" ) func main() { // Create MakeConfig instance with remote username, server address and path to private key. ssh := &easyssh.MakeConfig{ User: "john", Server: "example.com", // Optional key or Password without either we try to contact your agent SOCKET //Password: "password", Key: "/.ssh/id_rsa", Port: "22", } // Call Run method with command you want to run on remote server. stdout, stderr, done, err := ssh.Run("ps ax", 60) // Handle errors if err != nil { panic("Can't run remote command: " + err.Error()) } else { fmt.Println("don is :", done, "stdout is :", stdout, "; stderr is :", stderr) } } ================================================ FILE: example/scp.go ================================================ package main import ( "fmt" "github.com/hypersleep/easyssh" ) func main() { // Create MakeConfig instance with remote username, server address and path to private key. ssh := &easyssh.MakeConfig{ User: "root", Server: "example.com", Password: "123qwe", Port: "22", } // Call Scp method with file you want to upload to remote server. err := ssh.Scp("/root/source.csv", "/tmp/target.csv") // Handle errors if err != nil { panic("Can't run remote command: " + err.Error()) } else { fmt.Println("success") } }