[
  {
    "path": ".gitignore",
    "content": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n*.test\n*.prof\n"
  },
  {
    "path": "README.md",
    "content": "# easyssh\n\n## Description\n\nPackage easyssh provides a simple implementation of some SSH protocol features in Go.\nYou can simply run command on remote server or upload a file even simple than native console SSH client.\nDo not need to think about Dials, sessions, defers and public keys...Let easyssh will be think about it!\n\n## So easy to use!\n\n[Run a command on remote server and get STDOUT output](https://github.com/hypersleep/easyssh/blob/master/example/run.go)\n\n[Upload a file to remote server](https://github.com/hypersleep/easyssh/blob/master/example/scp.go)\n"
  },
  {
    "path": "easyssh.go",
    "content": "// Package easyssh provides a simple implementation of some SSH protocol\n// features in Go. You can simply run a command on a remote server or get a file\n// even simpler than native console SSH client. You don't need to think about\n// Dials, sessions, defers, or public keys... Let easyssh think about it!\npackage easyssh\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/ssh\"\n\t\"golang.org/x/crypto/ssh/agent\"\n)\n\n// Contains main authority information.\n// User field should be a name of user on remote server (ex. john in ssh john@example.com).\n// Server field should be a remote machine address (ex. example.com in ssh john@example.com)\n// Key is a path to private key on your local machine.\n// Port is SSH server port on remote machine.\n// Note: easyssh looking for private key in user's home directory (ex. /home/john + Key).\n// Then ensure your Key begins from '/' (ex. /.ssh/id_rsa)\ntype MakeConfig struct {\n\tUser     string\n\tServer   string\n\tKey      string\n\tPort     string\n\tPassword string\n}\n\n// returns ssh.Signer from user you running app home path + cutted key path.\n// (ex. pubkey,err := getKeyFile(\"/.ssh/id_rsa\") )\nfunc getKeyFile(keypath string) (ssh.Signer, error) {\n\tusr, err := user.Current()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfile := usr.HomeDir + keypath\n\tbuf, err := ioutil.ReadFile(file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpubkey, err := ssh.ParsePrivateKey(buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pubkey, nil\n}\n\n// connects to remote server using MakeConfig struct and returns *ssh.Session\nfunc (ssh_conf *MakeConfig) connect() (*ssh.Session, error) {\n\t// auths holds the detected ssh auth methods\n\tauths := []ssh.AuthMethod{}\n\n\t// figure out what auths are requested, what is supported\n\tif ssh_conf.Password != \"\" {\n\t\tauths = append(auths, ssh.Password(ssh_conf.Password))\n\t}\n\n\tif sshAgent, err := net.Dial(\"unix\", os.Getenv(\"SSH_AUTH_SOCK\")); err == nil {\n\t\tauths = append(auths, ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers))\n\t\tdefer sshAgent.Close()\n\t}\n\n\tif pubkey, err := getKeyFile(ssh_conf.Key); err == nil {\n\t\tauths = append(auths, ssh.PublicKeys(pubkey))\n\t}\n\n\tconfig := &ssh.ClientConfig{\n\t\tUser: ssh_conf.User,\n\t\tAuth: auths,\n\t}\n\n\tclient, err := ssh.Dial(\"tcp\", ssh_conf.Server+\":\"+ssh_conf.Port, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsession, err := client.NewSession()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn session, nil\n}\n\n// Stream returns one channel that combines the stdout and stderr of the command\n// as it is run on the remote machine, and another that sends true when the\n// command is done. The sessions and channels will then be closed.\nfunc (ssh_conf *MakeConfig) Stream(command string, timeout int) (stdout chan string, stderr chan string, done chan bool, err error) {\n\t// connect to remote host\n\tsession, err := ssh_conf.connect()\n\tif err != nil {\n\t\treturn stdout, stderr, done, err\n\t}\n\t// connect to both outputs (they are of type io.Reader)\n\toutReader, err := session.StdoutPipe()\n\tif err != nil {\n\t\treturn stdout, stderr, done, err\n\t}\n\terrReader, err := session.StderrPipe()\n\tif err != nil {\n\t\treturn stdout, stderr, done, err\n\t}\n\t// combine outputs, create a line-by-line scanner\n\tstdoutReader := io.MultiReader(outReader)\n\tstderrReader := io.MultiReader(errReader)\n\terr = session.Start(command)\n\tstdoutScanner := bufio.NewScanner(stdoutReader)\n\tstderrScanner := bufio.NewScanner(stderrReader)\n\t// continuously send the command's output over the channel\n\tstdoutChan := make(chan string)\n\tstderrChan := make(chan string)\n\tdone = make(chan bool)\n\n\tgo func(stdoutScanner, stderrScanner *bufio.Scanner, stdoutChan, stderrChan chan string, done chan bool) {\n\t\tdefer close(stdoutChan)\n\t\tdefer close(stderrChan)\n\t\tdefer close(done)\n\n\t\ttimeoutChan := time.After(time.Duration(timeout) * time.Second)\n\t\tres := make(chan bool, 1)\n\n\t\tgo func() {\n\t\t\tfor stdoutScanner.Scan() {\n\t\t\t\tstdoutChan <- stdoutScanner.Text()\n\t\t\t}\n\t\t\tfor stderrScanner.Scan() {\n\t\t\t\tstderrChan <- stderrScanner.Text()\n\t\t\t}\n\t\t\t// close all of our open resources\n\t\t\tres <- true\n\t\t}()\n\n\t\tselect {\n\t\tcase <-res:\n\t\t\tstdoutChan <- \"\"\n\t\t\tstderrChan <- \"\"\n\t\t\tdone <- true\n\t\tcase <-timeoutChan:\n\t\t\tstdoutChan <- \"\"\n\t\t\tstderrChan <- \"Run Command Timeout!\"\n\t\t\tdone <- false\n\t\t}\n\n\t\tsession.Close()\n\t}(stdoutScanner, stderrScanner, stdoutChan, stderrChan, done)\n\treturn stdoutChan, stderrChan, done, err\n}\n\n// Runs command on remote machine and returns its stdout as a string\nfunc (ssh_conf *MakeConfig) Run(command string, timeout int) (outStr string, errStr string, isTimeout bool, err error) {\n\tstdoutChan, stderrChan, doneChan, err := ssh_conf.Stream(command, timeout)\n\tif err != nil {\n\t\treturn outStr, errStr, isTimeout, err\n\t}\n\t// read from the output channel until the done signal is passed\n\tstillGoing := true\n\tfor stillGoing {\n\t\tselect {\n\t\tcase isTimeout = <-doneChan:\n\t\t\tstillGoing = false\n\t\tcase outline := <-stdoutChan:\n\t\t\toutStr += outline + \"\\n\"\n\t\tcase errline := <-stderrChan:\n\t\t\terrStr += errline + \"\\n\"\n\t\t}\n\t}\n\t// return the concatenation of all signals from the output channel\n\treturn outStr, errStr, isTimeout, err\n}\n\n// Scp uploads sourceFile to remote machine like native scp console app.\nfunc (ssh_conf *MakeConfig) Scp(sourceFile string, etargetFile string) error {\n\tsession, err := ssh_conf.connect()\n\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer session.Close()\n\n\ttargetFile := filepath.Base(etargetFile)\n\n\tsrc, srcErr := os.Open(sourceFile)\n\n\tif srcErr != nil {\n\t\treturn srcErr\n\t}\n\n\tsrcStat, statErr := src.Stat()\n\n\tif statErr != nil {\n\t\treturn statErr\n\t}\n\n\tgo func() {\n\t\tw, _ := session.StdinPipe()\n\n\t\tfmt.Fprintln(w, \"C0644\", srcStat.Size(), targetFile)\n\n\t\tif srcStat.Size() > 0 {\n\t\t\tio.Copy(w, src)\n\t\t\tfmt.Fprint(w, \"\\x00\")\n\t\t\tw.Close()\n\t\t} else {\n\t\t\tfmt.Fprint(w, \"\\x00\")\n\t\t\tw.Close()\n\t\t}\n\t}()\n\n\tif err := session.Run(fmt.Sprintf(\"scp -tr %s\", etargetFile)); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "easyssh_test.go",
    "content": "package easyssh\n\nimport (\n\t\"testing\"\n)\n\nvar sshConfig = &MakeConfig{\n\tUser:     \"root\",\n\tServer:   \"172.30.19.2\",\n\tPassword: \"password\",\n\t//Key:  \"/.ssh/id_rsa\",\n\tPort: \"22\",\n}\n\nfunc TestStream(t *testing.T) {\n\tt.Parallel()\n\t// input command/output string pairs\n\ttestCases := [][]string{\n\t\t{`for i in $(seq 1 5); do echo \"$i\"; done`, \"12345\"},\n\t\t{`echo \"test\"`, \"test\"},\n\t}\n\tfor _, testCase := range testCases {\n\t\toutChannel, errChannel, done, err := sshConfig.Stream(testCase[0], 10)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Stream failed: %s\", err)\n\t\t}\n\t\tstillGoing := true\n\t\tstdout := \"\"\n\t\tstderr := \"\"\n\t\tfor stillGoing {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\tstillGoing = false\n\t\t\tcase line := <-outChannel:\n\t\t\t\tstdout += line\n\t\t\tcase line := <-errChannel:\n\t\t\t\tstderr += line\n\t\t\t}\n\t\t}\n\t\tif stdout != testCase[1] {\n\t\t\tt.Error(\"Output didn't match expected: %s,%s\", stdout, stderr)\n\t\t}\n\t}\n}\n\nfunc TestRun(t *testing.T) {\n\tt.Parallel()\n\tcommands := []string{\n\t\t\"echo test\", `for i in $(ls); do echo \"$i\"; done`, \"ls\",\n\t}\n\tfor _, cmd := range commands {\n\t\tstdout, stderr, istimeout, err := sshConfig.Run(cmd, 10)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Run failed: %s\", err)\n\t\t}\n\t\tif stdout == \"\" {\n\t\t\tt.Errorf(\"Output was empty for command: %s,%s,%s\", cmd, stdout, stderr, istimeout)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "example/run.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/hypersleep/easyssh\"\n)\n\nfunc main() {\n\t// Create MakeConfig instance with remote username, server address and path to private key.\n\tssh := &easyssh.MakeConfig{\n\t\tUser:   \"john\",\n\t\tServer: \"example.com\",\n\t\t// Optional key or Password without either we try to contact your agent SOCKET\n\t\t//Password: \"password\",\n\t\tKey:  \"/.ssh/id_rsa\",\n\t\tPort: \"22\",\n\t}\n\n\t// Call Run method with command you want to run on remote server.\n\tstdout, stderr, done, err := ssh.Run(\"ps ax\", 60)\n\t// Handle errors\n\tif err != nil {\n\t\tpanic(\"Can't run remote command: \" + err.Error())\n\t} else {\n\t\tfmt.Println(\"don is :\", done, \"stdout is :\", stdout, \";   stderr is :\", stderr)\n\t}\n\n}\n"
  },
  {
    "path": "example/scp.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/hypersleep/easyssh\"\n)\n\nfunc main() {\n\t// Create MakeConfig instance with remote username, server address and path to private key.\n\tssh := &easyssh.MakeConfig{\n\t\tUser:     \"root\",\n\t\tServer:   \"example.com\",\n\t\tPassword: \"123qwe\",\n\t\tPort:     \"22\",\n\t}\n\n\t// Call Scp method with file you want to upload to remote server.\n\terr := ssh.Scp(\"/root/source.csv\", \"/tmp/target.csv\")\n\n\t// Handle errors\n\tif err != nil {\n\t\tpanic(\"Can't run remote command: \" + err.Error())\n\t} else {\n\t\tfmt.Println(\"success\")\n\n\t}\n}\n"
  }
]