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
}

接口实现

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
}

测试用例

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

其他

参考

最近更新

  1. TCP协议是安全的吗?

    2024-04-26 21:44:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-26 21:44:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-26 21:44:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-26 21:44:03       20 阅读

热门阅读

  1. Vite与Vue 3快速上手指南

    2024-04-26 21:44:03       10 阅读
  2. vue 实现 word 下载的方式

    2024-04-26 21:44:03       14 阅读
  3. 部署之缓存问题

    2024-04-26 21:44:03       15 阅读
  4. android 判断文件是否存在

    2024-04-26 21:44:03       15 阅读
  5. 华为 obs相关

    2024-04-26 21:44:03       13 阅读
  6. C语言数组

    2024-04-26 21:44:03       14 阅读
  7. 站点服务ISiteService

    2024-04-26 21:44:03       16 阅读
  8. 【转载】C#集成JWT快速入门

    2024-04-26 21:44:03       15 阅读
  9. 计算机网络基础

    2024-04-26 21:44:03       15 阅读
  10. Spring Cloud Ribbon面试题

    2024-04-26 21:44:03       17 阅读
  11. QBoxLayout的addWidget(QWidget * w)会改变w的parent()

    2024-04-26 21:44:03       16 阅读
  12. 深度学习和强化学习的区别

    2024-04-26 21:44:03       16 阅读
  13. IDEA常用的快捷键

    2024-04-26 21:44:03       13 阅读
  14. 【k8s】(八)kubernetes1.29.4离线部署之-测试验证

    2024-04-26 21:44:03       18 阅读
  15. K8S Centos7 安装 K8S 1.26单机版

    2024-04-26 21:44:03       19 阅读