简介
基于上篇 Golang实现一个批量自动化执行树莓派指令的软件(1)文本加密&配置&命令行交互实现, 这篇实现的是指令, 即通过ssh执行linux指令的实现。
环境描述
运行环境: Windows, 基于Golang, 暂时没有使用什么不可跨平台接口, 理论上支持Linux/MacOS
目标终端:树莓派DebianOS(主要做用它测试)
实现
接口定义
type ICommander interface {/*Command: 堵塞直到执行完毕*/Command(cmdStr string) (output string, err error)/*CommandWithCallback:background 为 true 时 异步函数, 执行完毕之后自动调用回调background 为 false时 堵塞直到执行完毕之后调用回调*/CommandWithCallback(cmdStr string, callback func(output string, err error), background bool) error/*Cancel : 任务取消, 执行到一半的任务断开场景*/Cancel() error/*Desroy : 无论如何直接关闭客户端*/Destroy() error
}
接口实现
package sshutilimport ("fmt""golang.org/x/crypto/ssh""io""time"
)type Commander struct {sshClient *ssh.Clientstarted boolcanceled chan struct{}finished chan struct{}
}func NewCommander(cfg SSHConfig) (*Commander, error) {sshClient, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", cfg.IP, cfg.Port), cfg.sshClientConfig)if err != nil {return nil, err}return &Commander{sshClient: sshClient,canceled: make(chan struct{}),finished: make(chan struct{}),}, nil
}func (c *Commander) Command(cmd string) (output string, err error) {c.doCommand(cmd, func(recvStr string, e error) {output = recvStrerr = e})return
}func (c *Commander) CommandWithCallback(cmd string, callback func(output string, e error), background bool) (err error) {if background {go c.doCommand(cmd, callback)} else {c.doCommand(cmd, callback)}return
}func (c *Commander) doCommand(cmd string, callback func(output string, e error)) {var (session *ssh.SessionrecvBytes []byteerr errorcanceled bool)if session, err = c.sshClient.NewSession(); err != nil {callback("", err)return}c.started = truego func(session *ssh.Session) {select {case <-c.finished:case <-c.canceled:canceled = trueif e := session.Signal(ssh.SIGINT); nil != err {fmt.Println("emit abort fail: ", e.Error())}}}(session)defer func() {c.started = falseif !canceled {c.finished <- struct{}{}}if e := session.Close(); nil != e && e != io.EOF {fmt.Println("session close fail: ", e.Error())}}()// err = session.Start(command) // ssh库的异步方式if recvBytes, err = session.CombinedOutput(cmd); err != nil {if canceled {err = fmt.Errorf("user canceled")}callback(string(recvBytes), err)return}callback(string(recvBytes), err)
}func (c *Commander) Cancel() error {if c.started {select {case c.canceled <- struct{}{}:case <-time.After(time.Second): // 取消时间过长,取消失败return fmt.Errorf("time out waiting for cancel")}}return nil
}func (c *Commander) Destroy() error {var err = c.Cancel()close(c.finished)close(c.canceled)err = c.sshClient.Close()return err
}
测试用例
package sshutilimport ("fmt""golang.org/x/crypto/ssh""sync""testing""time"
)func newCommander() (*Commander, error) {config := &ssh.ClientConfig{User: "pi",Auth: []ssh.AuthMethod{ssh.Password("a123456"),},HostKeyCallback: ssh.InsecureIgnoreHostKey(),}sshCfg := SSHConfig{IP: "192.168.3.2",Port: 22,User: "pi",Password: "a123456",sshClientConfig: config,}commander, err := NewCommander(sshCfg)return commander, err
}func TestEasyCommandAndDestroy(t *testing.T) {commander, err := newCommander()if nil != err {fmt.Println("new command fail: ", err.Error())return}defer func() {e := commander.Destroy()if nil != err {fmt.Println("fail to destroy, ", e.Error())}}()var output stringoutput, err = commander.Command("ls /")fmt.Println("command result: ", output)
}func TestCommandWithCallbackWithoutAsync(t *testing.T) {commander, err := newCommander()if nil != err {fmt.Println("new command fail: ", err.Error())return}defer func() {e := commander.Destroy()if nil != err {fmt.Println("fail to destroy, ", e.Error())}}()err = commander.CommandWithCallback("sleep 4; ls /", func(output string, e error) {if nil != e {fmt.Println("fail, ", e.Error())} else {fmt.Println("result: ", output)}}, false)if nil != err {fmt.Println("do command fail: ", err.Error())return}
}func TestCommandWithCallbackAsync(t *testing.T) {var waiter sync.WaitGroupcommander, err := newCommander()if nil != err {fmt.Println("new command fail: ", err.Error())return}defer func() {e := commander.Destroy()if nil != err {fmt.Println("fail to destroy, ", e.Error())}}()waiter.Add(1)err = commander.CommandWithCallback("sleep 4; ls /", func(output string, e error) {if nil != e {fmt.Println("fail, ", e.Error())} else {fmt.Println("result: ", output)}waiter.Done()}, true)if nil != err {fmt.Println("do command fail: ", err.Error())return}fmt.Println("waiting finished<--------")waiter.Wait()fmt.Println("waiting finished------>")
}func TestCommandWithCallbackAndCancel(t *testing.T) {var waiter sync.WaitGroupcommander, err := newCommander()if nil != err {fmt.Println("new command fail: ", err.Error())return}defer func() {e := commander.Destroy()if nil != err {fmt.Println("fail to destroy, ", e.Error())}}()waiter.Add(1)err = commander.CommandWithCallback("sleep 10; ls /", func(output string, e error) {if nil != e {fmt.Println("fail, ", e.Error())} else {fmt.Println("result: ", output)}waiter.Done()}, true)if nil != err {fmt.Println("do command fail: ", err.Error())return}fmt.Println("waiting finished<--------")time.Sleep(time.Second * 2)fmt.Println("canceling...")fmt.Println("canceled, ", commander.Cancel())waiter.Wait()fmt.Println("waiting finished------>")
}
代码源
https://gitee.com/grayhsu/ssh_remote_access