Module I used
※こちらはLinux環境でしか使えないので、WSL等で実行してみてください。
How to install
$ go get github.com/google/goexpect
$ go get github.com/google/goterm/term
How to Use
今回はsshを使っていきたいと思います。
Googleさんが丁寧に載せてくださった例が見事に動かなかったので、sshとtelnetの箇所を参考にしながら以下のようにコードを書きました。
package main
import (
"flag"
"fmt"
"log"
"regexp"
"time"
"golang.org/x/crypto/ssh"
"github.com/google/goexpect"
"github.com/google/goterm/term"
)
const (
timeout = 10 * time.Minute
)
var (
addr = flag.String("address", "", "address of telnet server")
user = flag.String("user", "user", "username to use")
pass1 = flag.String("pass1", "pass1", "password to use")
pass2 = flag.String("pass2", "pass2", "alternate password to use")
cmd = flag.String("cmd", "", "command to run")
promptRE = regexp.MustCompile("#")
promptRE_2 = regexp.MustCompile(":")
)
func main() {
flag.Parse()
fmt.Println(term.Bluef("SSH Example"))
sshClt, err := ssh.Dial("tcp", *addr, &ssh.ClientConfig{
User: *user,
Auth: []ssh.AuthMethod{ssh.Password(*pass1)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
})
if err != nil {
log.Fatalf("ssh.Dial(%q) failed: %v", *addr, err)
}
defer sshClt.Close()
e, _, err := expect.SpawnSSH(sshClt, timeout)
if err != nil {
log.Fatal(err)
}
defer e.Close()
e.Expect(promptRE, timeout)
e.Send(*cmd + "\n")
e.Expect(promptRE_2, timeout)
e.Send("y" + "\n")
result, _, _ := e.Expect(promptRE, timeout)
e.Send("exit\n")
fmt.Println(term.Greenf("%s: result: %s\n", *cmd, result))
fmt.Println(term.Greenf("All done"))
}
解説
今回は、remote serverにて/root下に消す用のdirectoryである「for_rm」を作成しておきます。
以下のように実行をします。
(IP, Port, User, Passwordは実際使ったものとは異なります。)
$ go run practice.go -address 192.168.1.2:22 -user root -pass1 password -pass2 password -cmd "rm -r /root/for_rm"
すると、ここで入力したパラメータが
var (
addr = flag.String("address", "", "address of telnet server")
user = flag.String("user", "user", "username to use")
pass1 = flag.String("pass1", "pass1", "password to use")
pass2 = flag.String("pass2", "pass2", "alternate password to use")
cmd = flag.String("cmd", "", "command to run")
)
こちらに入ります。
flag.Parse()
func main() {
flag.Parse()
flag.Parse()をすることで、実行時のオプション指定の名前に対応する変数に対して、ポインタ指定をすると値を呼び出せるようになります。
ssh.Dial()
sshClt, err := ssh.Dial("tcp", *addr, &ssh.ClientConfig{
User: *user,
Auth: []ssh.AuthMethod{ssh.Password(*pass1)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
})
if err != nil {
log.Fatalf("ssh.Dial(%q) failed: %v", *addr, err)
}
defer sshClt.Close()
ここではおなじみの
"golang.org/x/crypto/ssh"
の使い方と同じようにDialしてあげます。
expect.SpawnSSH()
Spawn()を用いて、ptyを通してbashに対してcommandを投げてくれるインスタンスを生成します。
pty等に関しては、こちらにある内容がイメージしやすいのではないでしょうか。
http://eng-manima.blogspot.com/2014/09/pexpect.html
こちらの記事は、今回使っているGoexpectのpython版であるpexpectというものの解説です。
e, _, err := expect.SpawnSSH(sshClt, timeout)
if err != nil {
log.Fatal(err)
}
defer e.Close()
ExpectとSend
上記のサイトでも説明がありますように、
Expect:bashから返ってくるもの
Send:実行するcommand
となっています。
/* 残りのvar
promptRE = regexp.MustCompile("#")
promptRE_2 = regexp.MustCompile(":")
*/
e.Expect(promptRE, timeout)
e.Send(*cmd + "\n")
e.Expect(promptRE_2, timeout)
e.Send("y" + "\n")
result, _, _ := e.Expect(promptRE, timeout)
e.Send("exit\n")
Linuxでは、ログインして最初に帰ってくるものは ["user"@"hostname" ~]# のような形です。
e.Expect(promptRE, timeout)
とすることで、promptRE すなわち regexp.MustCompile("#")
つまり、# がついたものが返ってくることを明示的に示し(ubuntuの時は$に変えるなど、前もって決め打ちしなければなりません)
それに対して、e.Send(*cmd + "\n") によって、example.goの実行時に渡したcommandを送ります。
したがって
["user"@"hostname" ~]# rm -r /root/rm_fm
を実行することと同様のことをしています。
次では、実際のLinuxでは、「rm: ディレクトリ `for_rm/' を削除しますか? 」といったものが返ってくるので
promptRE_2 = regexp.MustCompile(":") によって、: を含むものが返ってくることを明示的に示し、
(例えば、「su -」を投げると、「Password: 」といったものが返ってくるので、: が無難だと思います。)
これに対して、e.Send("y" + "\n") をします。
すなわち
rm: ディレクトリ `for_rm/' を削除しますか? y
を実行することと同様のことを行われます。
こうして、無事対話式に対応して、directoryを消すことができました。
まとめ
windows対応版が欲しい...