woodpecker/vendor/github.com/melbahja/goph/client.go
qwerty287 9c6c4559a7
Add SSH backend (#861)
Add SSH backend that runs commands via SSH.

Close #848
2022-04-29 12:30:50 +02:00

213 lines
4.2 KiB
Go

// Copyright 2020 Mohammed El Bahja. All rights reserved.
// Use of this source code is governed by a MIT license.
package goph
import (
"context"
"fmt"
"io"
"net"
"os"
"time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
// Client represents Goph client.
type Client struct {
*ssh.Client
Config *Config
}
// Config for Client.
type Config struct {
Auth Auth
User string
Addr string
Port uint
Timeout time.Duration
Callback ssh.HostKeyCallback
}
// DefaultTimeout is the timeout of ssh client connection.
var DefaultTimeout = 20 * time.Second
// New starts a new ssh connection, the host public key must be in known hosts.
func New(user string, addr string, auth Auth) (c *Client, err error) {
callback, err := DefaultKnownHosts()
if err != nil {
return
}
c, err = NewConn(&Config{
User: user,
Addr: addr,
Port: 22,
Auth: auth,
Timeout: DefaultTimeout,
Callback: callback,
})
return
}
// NewUnknown starts a ssh connection get client without cheking knownhosts.
// PLEASE AVOID USING THIS, UNLESS YOU KNOW WHAT ARE YOU DOING!
// if there a "man in the middle proxy", this can harm you!
// You can add the key to know hosts and use New() func instead!
func NewUnknown(user string, addr string, auth Auth) (*Client, error) {
return NewConn(&Config{
User: user,
Addr: addr,
Port: 22,
Auth: auth,
Timeout: DefaultTimeout,
Callback: ssh.InsecureIgnoreHostKey(),
})
}
// NewConn returns new client and error if any.
func NewConn(config *Config) (c *Client, err error) {
c = &Client{
Config: config,
}
c.Client, err = Dial("tcp", config)
return
}
// Dial starts a client connection to SSH server based on config.
func Dial(proto string, c *Config) (*ssh.Client, error) {
return ssh.Dial(proto, net.JoinHostPort(c.Addr, fmt.Sprint(c.Port)), &ssh.ClientConfig{
User: c.User,
Auth: c.Auth,
Timeout: c.Timeout,
HostKeyCallback: c.Callback,
})
}
// Run starts a new SSH session and runs the cmd, it returns CombinedOutput and err if any.
func (c Client) Run(cmd string) ([]byte, error) {
var (
err error
sess *ssh.Session
)
if sess, err = c.NewSession(); err != nil {
return nil, err
}
defer sess.Close()
return sess.CombinedOutput(cmd)
}
// Run starts a new SSH session with context and runs the cmd. It returns CombinedOutput and err if any.
func (c Client) RunContext(ctx context.Context, name string) ([]byte, error) {
cmd, err := c.CommandContext(ctx, name)
if err != nil {
return nil, err
}
return cmd.CombinedOutput()
}
// Command returns new Cmd and error if any.
func (c Client) Command(name string, args ...string) (*Cmd, error) {
var (
sess *ssh.Session
err error
)
if sess, err = c.NewSession(); err != nil {
return nil, err
}
return &Cmd{
Path: name,
Args: args,
Session: sess,
Context: context.Background(),
}, nil
}
// Command returns new Cmd with context and error, if any.
func (c Client) CommandContext(ctx context.Context, name string, args ...string) (*Cmd, error) {
cmd, err := c.Command(name, args...)
if err != nil {
return cmd, err
}
cmd.Context = ctx
return cmd, nil
}
// NewSftp returns new sftp client and error if any.
func (c Client) NewSftp(opts ...sftp.ClientOption) (*sftp.Client, error) {
return sftp.NewClient(c.Client, opts...)
}
// Close client net connection.
func (c Client) Close() error {
return c.Client.Close()
}
// Upload a local file to remote server!
func (c Client) Upload(localPath string, remotePath string) (err error) {
local, err := os.Open(localPath)
if err != nil {
return
}
defer local.Close()
ftp, err := c.NewSftp()
if err != nil {
return
}
defer ftp.Close()
remote, err := ftp.Create(remotePath)
if err != nil {
return
}
defer remote.Close()
_, err = io.Copy(remote, local)
return
}
// Download file from remote server!
func (c Client) Download(remotePath string, localPath string) (err error) {
local, err := os.Create(localPath)
if err != nil {
return
}
defer local.Close()
ftp, err := c.NewSftp()
if err != nil {
return
}
defer ftp.Close()
remote, err := ftp.Open(remotePath)
if err != nil {
return
}
defer remote.Close()
if _, err = io.Copy(local, remote); err != nil {
return
}
return local.Sync()
}