简介
基于上篇 Golang实现一个批量自动化执行树莓派指令的软件(1)文本加密&配置&命令行交互实现, 这篇实现的是指令, 即通过ssh执行linux指令的实现。
环境描述
运行环境: Windows, 基于Golang, 暂时没有使用什么不可跨平台接口, 理论上支持Linux/MacOS
目标终端:树莓派DebianOS(主要做用它测试)
实现
接口定义
go
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
}
接口实现
go
package sshutil
import (
"fmt"
"golang.org/x/crypto/ssh"
"io"
"time"
)
type Commander struct {
sshClient *ssh.Client
started bool
canceled 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 = recvStr
err = 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.Session
recvBytes []byte
err error
canceled bool
)
if session, err = c.sshClient.NewSession(); err != nil {
callback("", err)
return
}
c.started = true
go func(session *ssh.Session) {
select {
case <-c.finished:
case <-c.canceled:
canceled = true
if e := session.Signal(ssh.SIGINT); nil != err {
fmt.Println("emit abort fail: ", e.Error())
}
}
}(session)
defer func() {
c.started = false
if !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
}
测试用例
go
package sshutil
import (
"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 string
output, 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.WaitGroup
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())
}
}()
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.WaitGroup
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())
}
}()
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