171 lines
5.2 KiB
Go
171 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/silenceper/pool"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
const SshDialTimeout = time.Duration(10 * time.Second)
|
|
const SshInactivityTimeout = time.Duration(time.Minute)
|
|
|
|
type Ssh struct {
|
|
name string
|
|
signer ssh.Signer
|
|
config *ssh.ClientConfig
|
|
client *ssh.Client
|
|
session *ssh.Session
|
|
in io.WriteCloser
|
|
out io.Reader
|
|
err io.Reader
|
|
}
|
|
|
|
func NewSsh(name, addr, user, key string) (*Ssh, error) {
|
|
log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key}).Debugf("starting")
|
|
defer log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key}).Debugf("done")
|
|
|
|
s := &Ssh{
|
|
name: name,
|
|
}
|
|
|
|
k, err := os.ReadFile(key)
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key, "call": "os.ReadFile", "error": err}).Errorf("")
|
|
return s, err
|
|
}
|
|
|
|
parsedKey, err := ssh.ParseRawPrivateKey(k)
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key, "call": "ssh.ParseRawPrivateKey", "error": err}).Errorf("")
|
|
return s, err
|
|
}
|
|
|
|
s.signer, err = ssh.NewSignerFromKey(parsedKey)
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key, "call": "ssh.NewSignerFromKey", "error": err}).Errorf("")
|
|
return s, err
|
|
}
|
|
|
|
s.config = &ssh.ClientConfig{
|
|
User: user,
|
|
Auth: []ssh.AuthMethod{
|
|
ssh.PublicKeys(s.signer),
|
|
},
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
Timeout: SshDialTimeout,
|
|
}
|
|
|
|
s.client, err = ssh.Dial("tcp", addr, s.config)
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key, "call": "ssh.Dial", "error": err}).Errorf("")
|
|
return s, err
|
|
}
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
func (s *Ssh) Close() error {
|
|
log.WithFields(log.Fields{"name": s.name}).Debugf("starting")
|
|
defer log.WithFields(log.Fields{"name": s.name}).Debugf("done")
|
|
|
|
return s.client.Close()
|
|
}
|
|
|
|
func NewSshPool(name, addr, user, key string) (pool.Pool, error) {
|
|
log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key}).Debugf("starting")
|
|
defer log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key}).Debugf("done")
|
|
|
|
//factory Specify the method to create the connection
|
|
factory := func() (interface{}, error) { return NewSsh(name, addr, user, key) }
|
|
|
|
// close Specify the method to close the connection
|
|
close := func(v interface{}) error { return v.(*Ssh).Close() }
|
|
|
|
// Create a connection pool: Initialize the number of connections to 0, the maximum idle connection is 2, and the maximum concurrent connection is 25
|
|
poolConfig := &pool.Config{
|
|
InitialCap: 0,
|
|
MaxIdle: 2,
|
|
MaxCap: 25,
|
|
Factory: factory,
|
|
Close: close,
|
|
//Ping: ping,
|
|
//The maximum idle time of the connection, the connection exceeding this time will be closed, which can avoid the problem of automatic failure when connecting to EOF when idle
|
|
IdleTimeout: SshInactivityTimeout,
|
|
}
|
|
|
|
return pool.NewChannelPool(poolConfig)
|
|
}
|
|
|
|
func (s *Ssh) Exec(cmd string) (string, error) {
|
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd}).Debugf("starting")
|
|
defer log.WithFields(log.Fields{"name": s.name, "cmd": cmd}).Debugf("done")
|
|
|
|
if err := s.ExecPipe(cmd); err != nil {
|
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "ssh.ExecPipe", "error": err}).Errorf("")
|
|
return "", err
|
|
}
|
|
defer s.session.Close()
|
|
|
|
if err := s.session.Wait(); err != nil {
|
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Setenv", "error": err}).Errorf("")
|
|
return "", err
|
|
}
|
|
|
|
buf, err := io.ReadAll(s.out)
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "io.ReadAll", "error": err}).Errorf("")
|
|
return "", err
|
|
}
|
|
|
|
return string(buf), nil
|
|
|
|
}
|
|
|
|
func (s *Ssh) ExecPipe(cmd string) error {
|
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd}).Debugf("starting")
|
|
defer log.WithFields(log.Fields{"name": s.name, "cmd": cmd}).Debugf("done")
|
|
|
|
session, err := s.client.NewSession()
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "client.NewSession", "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
s.session = session
|
|
|
|
if err = s.session.Setenv("TZ", cfg.Timezone); err != nil {
|
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Setenv", "attr": cfg.Timezone, "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
if s.in, err = s.session.StdinPipe(); err != nil {
|
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.StdinPipe", "error": err}).Errorf("")
|
|
s.session.Close()
|
|
return err
|
|
}
|
|
|
|
if s.out, err = s.session.StdoutPipe(); err != nil {
|
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.StdoutPipe", "error": err}).Errorf("")
|
|
s.session.Close()
|
|
return err
|
|
}
|
|
|
|
if s.err, err = s.session.StderrPipe(); err != nil {
|
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.StderrPipe", "error": err}).Errorf("")
|
|
s.session.Close()
|
|
return err
|
|
}
|
|
|
|
if err = s.session.Start(cmd); err != nil {
|
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Start", "attr": cmd, "error": err}).Errorf("")
|
|
s.session.Close()
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|