• Golang实现一个批量自动化执行树莓派指令的软件(2)指令


    简介

    基于上篇 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    接口实现

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111

    测试用例

    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------>")
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138

    代码源

    https://gitee.com/grayhsu/ssh_remote_access

    其他

    参考

  • 相关阅读:
    vue 路由报错
    基础算法篇——前缀和与差分
    十、【React-Router6】Component 汇总
    一种基于Redis时间和权重关联的分布式优先级队列方法
    高德地图2.0使用SvgMarker.Shape.IconFont方法将iconfont矢量图作为图标
    深度学习——LSTM
    一文讲透支付宝沙箱的基本应用
    数据结构与算法拾遗九(异或运算)
    马斯克震撼演讲:我想创立一个新世界
    UniApp文件上传(SpringBoot+Minio)
  • 原文地址:https://blog.csdn.net/halo_hsuh/article/details/138183704