Open k4ml opened 7 years ago
I try to add the ssh and ssh/agent library - https://github.com/k4ml/anko/commit/9f646360ab6152dc6490f192af8687638d6dde05
Now I'm stuck with this error on go build:-
# github.com/mattn/anko/builtins/ssh
builtins/ssh/ssh.go:13: cannot make type ssh.ClientConfig
I'm trying to make this anko script to work:-
var ssh, agent = import("ssh"), import("ssh/agent")
var ioutil, net, os = import("io/ioutil"), import("net"), import("os")
func privateKeyPath() {
return os.Getenv("HOME") + "/.ssh/kamalkey.pem"
}
func parsePrivateKey(keyPath) {
buff, _ = ioutil.ReadFile(keyPath)
return ssh.ParsePrivateKey(buff)
}
func makeSshConfig(user) {
socket = os.Getenv("SSH_AUTH_SOCK")
conn, err = net.Dial("unix", socket)
agentClient = agent.NewClient(conn)
config = make(ssh.ClientConfig)
config.Set("user", user)
#config = make(ssh.ClientConfig{
# User: user,
# Auth: []ssh.AuthMethod{
# ssh.PublicKeysCallBack(agentClient.Signers),
# },
#})
return config, nil
}
func main() {
config, err = makeSshConfig("kamal")
client, err = ssh.Dial("tcp", "myserver:22", config)
}
main()
please keep package namespaces as same as golang.
Do you think this can be closed?
@mattn any thoughts?
I have achieved some decent mileage implementing Anko extensions using factory methods and registering them in the Anko env
.
e.g. for SSH commands (code below), the factory method is:
func NewSSHCommandClient(host, userName string) *SSHCommandClient
in the package sshcommand
.
So I register this in the env
like this:
_ = e.Define("sshcommand", sshcommand.NewSSHCommandClient)
My script then looks like this:
sshConn = sshcommand("my-host", "my-user").Connect()
printf("SSH Connected: %s\n", sshConn)
shell = sshConn.Shell()
output = shell.WriteCommandWithRead("pwd")
printf("PWD: %s\n", output)
It's a bit rough but it works. I put more cleanliness into extensions I use more, like Redis/DynamoDB etc.
SSHCommand Code:
package sshcommand
import (
"bytes"
"fmt"
"github.com/elliotchance/sshtunnel"
"golang.org/x/crypto/ssh"
"io"
"log"
"net"
"pql/util"
"strings"
"time"
)
var (
modes = ssh.TerminalModes{
ssh.ECHO: 0, // Disable echoing
ssh.IGNCR: 1, // Ignore CR on input.
}
)
type SSHCommandClient struct {
host string
port int
userName string
userPassword string
privateKey string
sshConfig *ssh.ClientConfig
connection *ssh.Client
timeout time.Duration
}
func NewSSHCommandClient(host, userName string) *SSHCommandClient {
return &SSHCommandClient{
host: host,
port: 22,
userName: userName,
timeout: 10 * time.Second,
}
}
func (s *SSHCommandClient) String() string {
return fmt.Sprintf("%s@%s:%d?connected=%t", s.userName, s.host, s.port, s.connection != nil)
}
func (s *SSHCommandClient) Connect() *SSHCommandClient {
s.init()
if client, err := ssh.Dial("tcp", net.JoinHostPort(s.host, fmt.Sprintf("%d", s.port)), s.sshConfig); err != nil {
panic(err)
} else {
s.connection = client
}
return s
}
func (s *SSHCommandClient) init() {
// Authentication
config := &ssh.ClientConfig{
User: s.userName,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: s.timeout,
BannerCallback: func(message string) error {
log.Printf("Banner: %s\n", message)
return nil
},
}
if s.userPassword != "" {
config.Auth = []ssh.AuthMethod{
ssh.Password(s.userPassword),
}
} else if s.privateKey != "" {
bts := []byte(util.StringFromFile(s.privateKey))
if key, err := ssh.ParsePrivateKey(bts); err != nil {
panic(err)
} else {
config.Auth = []ssh.AuthMethod{
ssh.PublicKeys(key),
}
}
} else {
config.Auth = []ssh.AuthMethod{
sshtunnel.SSHAgent(),
}
}
s.sshConfig = config
}
func (s *SSHCommandClient) WithPort(p int) *SSHCommandClient {
s.port = p
return s
}
func (s *SSHCommandClient) WithTimeout(t string) *SSHCommandClient {
if to, err := time.ParseDuration(t); err == nil {
s.timeout = to
}
return s
}
func (s *SSHCommandClient) WithPrivateKey(key string) *SSHCommandClient {
s.privateKey = key
return s
}
func (s *SSHCommandClient) WithPassword(pass string) *SSHCommandClient {
s.privateKey = pass
return s
}
func (s *SSHCommandClient) Exec(command string) string {
// Create a session. It is one session per command.
if session, err := s.connection.NewSession(); err != nil {
panic(err)
} else {
defer session.Close()
if bs, err := session.CombinedOutput(command); err != nil {
panic(err)
} else {
return string(bs)
}
}
}
func (s *SSHCommandClient) Close() {
s.connection.Close()
}
type SSHShell struct {
client *SSHCommandClient
session *ssh.Session
stdIn io.WriteCloser
stdOutErr io.Reader
ps1 string
}
func (h *SSHShell) Prompt() string {
return h.ps1
}
func (h *SSHShell) Close() {
h.session.Close()
}
func (h *SSHShell) WriteCommandWithRead(cmd string) string {
h.WriteCommand(cmd)
return h.ReadOutput()
}
func (h *SSHShell) WriteCommand(cmd string) {
if _, err := h.stdIn.Write([]byte(cmd)); err != nil {
panic(err)
}
}
func (h *SSHShell) ReadOutput() string {
var b strings.Builder
for {
buff := make([]byte, 1024, 1024)
if n, err := h.stdOutErr.Read(buff); err != nil {
if n > 0 {
b.Write(buff[:n])
}
if err == io.EOF {
break
} else {
panic(err)
}
} else {
if n > 0 {
s := string(buff[:n])
b.WriteString(s)
if strings.Contains(strings.TrimSpace(s), h.ps1) {
break
}
}
}
}
return b.String()
}
func (s *SSHCommandClient) Shell() *SSHShell {
// Create a session. It is one session per command.
if session, err := s.connection.NewSession(); err != nil {
panic(err)
} else {
if writer, err := session.StdinPipe(); err != nil {
panic(err)
} else {
if stdOut, err := session.StdoutPipe(); err != nil {
panic(err)
} else {
if stdErr, err := session.StderrPipe(); err != nil {
panic(err)
} else {
if err := session.RequestPty("vt100", 80, 120, modes); err != nil {
panic(err)
} else {
if err := session.Shell(); err != nil {
panic(err)
} else {
return &SSHShell{
ps1: "$",
client: s,
session: session,
stdIn: writer,
stdOutErr: io.MultiReader(stdOut, stdErr),
}
}
}
}
}
}
}
}
func (s *SSHCommandClient) Start(command string) string {
// Create a session. It is one session per command.
if session, err := s.connection.NewSession(); err != nil {
panic(err)
} else {
defer session.Close()
var b bytes.Buffer
session.Stdout = &b
session.Stderr = &b
if err := session.Start(command); err != nil {
panic(err)
} else {
return string(b.String())
}
}
}
func remoteRun(user string, addr string, privateKey string, cmd string) (string, error) {
// privateKey could be read from a file, or retrieved from another storage
// source, such as the Secret Service / GNOME Keyring
key, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return "", err
}
// Authentication
config := &ssh.ClientConfig{
User: user,
// https://github.com/golang/go/issues/19767
// as clientConfig is non-permissive by default
// you can set ssh.InsercureIgnoreHostKey to allow any host
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{
ssh.PublicKeys(key),
},
//alternatively, you could use a password
/*
Auth: []ssh.AuthMethod{
ssh.Password("PASSWORD"),
},
*/
}
// Connect
client, err := ssh.Dial("tcp", net.JoinHostPort(addr, "22"), config)
if err != nil {
return "", err
}
// Create a session. It is one session per command.
session, err := client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
var b bytes.Buffer // import "bytes"
session.Stdout = &b // get output
// you can also pass what gets input to the stdin, allowing you to pipe
// content from client to server
// session.Stdin = bytes.NewBufferString("My input")
// Shell starts a login shell on the remote host. A Session only
// accepts one call to Run, Start, Shell, Output, or CombinedOutput.
// Start runs cmd on the remote host. Typically, the remote
// server passes cmd to the shell for interpretation.
// A Session only accepts one call to Run, Start or Shell.
// CombinedOutput runs cmd on the remote host and returns its combined
// standard output and standard error.
// Finally, run the command
bs, err := session.CombinedOutput(cmd)
return string(bs), err
}
I wonder if it possible to add ssh library. Currently making ssh connection and run remote command is too much boilerplate in Go - https://gist.github.com/erikdubbelboer/f62a109d8e8798a11eb89ed494491953.
If we can simplify this in anko, it can be a practical alternative for some scripting work in Go (anko).