天天看點

Go語言遠端執行ssh指令簡單封裝(支援帶互動指令)

使用包:golang.org/x/crypto/ssh

以下封裝一個發送指令的Cli結構體

package utils

import (
	"fmt"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
	"io"
	"net"
	"os"
	"time"
)

type Cli struct {
	IP         string      //IP位址
	Username   string      //使用者名
	Password   string      //密碼
	Port       int         //端口号
	client     *ssh.Client //ssh用戶端
	LastResult string      //最近一次Run的結果
}

//建立指令行對象
//@param ip IP位址
//@param username 使用者名
//@param password 密碼
//@param port 端口号,預設22
func New(ip string, username string, password string, port ...int) *Cli {
	cli := new(Cli)
	cli.IP = ip
	cli.Username = username
	cli.Password = password
	if len(port) <= 0 {
		cli.Port = 22
	} else {
		cli.Port = port[0]
	}
	return cli
}

//執行shell
//@param shell shell腳本指令
func (c Cli) Run(shell string) (string, error) {
	if c.client == nil {
		if err := c.connect(); err != nil {
			return "", err
		}
	}
	session, err := c.client.NewSession()
	if err != nil {
		return "", err
	}
	defer session.Close()
	buf, err := session.CombinedOutput(shell)

	c.LastResult = string(buf)
	return c.LastResult, err
}
//連接配接
func (c *Cli) connect() error {
	config := ssh.ClientConfig{
		User: c.Username,
		Auth: []ssh.AuthMethod{ssh.Password(c.Password)},
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
		Timeout: 10 * time.Second,
	}
	addr := fmt.Sprintf("%s:%d", c.IP, c.Port)
	sshClient, err := ssh.Dial("tcp", addr, &config)
	if err != nil {
		return err
	}
	c.client = sshClient
	return nil
}      

測試執行shell代碼

cli := New("IP", "使用者名", "密碼", 端口号)
    output, err := cli.Run("free -h")
    fmt.Printf("%v\n%v", output, err)      

還有類似top或者vim的指令是需要互動的,可以利用包golang.org/x/crypto/ssh/terminal實作

再封裝一個方法RunTerminal

//執行帶互動的指令
func (c *Cli) RunTerminal(shell string, stdout, stderr io.Writer) error {
    if c.client == nil {
        if err := c.connect(); err != nil {
            return err
        }
    }
    session, err := c.client.NewSession()
    if err != nil {
        return err
    }
    defer session.Close()

    fd := int(os.Stdin.Fd())
    oldState, err := terminal.MakeRaw(fd)
    if err != nil {
        panic(err)
    }
    defer terminal.Restore(fd, oldState)

    session.Stdout = stdout
    session.Stderr = stderr
    session.Stdin = os.Stdin

    termWidth, termHeight, err := terminal.GetSize(fd)
    if err != nil {
        panic(err)
    }
    // Set up terminal modes
    modes := ssh.TerminalModes{
        ssh.ECHO:          1,     // enable echoing
        ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    // Request pseudo terminal
    if err := session.RequestPty("xterm-256color", termHeight, termWidth, modes); err != nil {
        return err
    }

    session.Run(shell)
    return nil
}      

測試RunTerminal方法

cli := New("IP", "使用者名", "密碼", 端口号)      
cli.RunTerminal("top", os.Stdout, os.Stdin)