mirror of
https://github.com/certimate-go/certimate.git
synced 2026-08-29 02:02:04 +08:00
feat: add ftp client
This commit is contained in:
@@ -45,6 +45,7 @@ require (
|
||||
github.com/google/go-querystring v1.2.0
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.190
|
||||
github.com/jdcloud-api/jdcloud-sdk-go v1.64.0
|
||||
github.com/jlaffaye/ftp v0.2.0
|
||||
github.com/kong/go-kong v0.73.1
|
||||
github.com/luthermonson/go-proxmox v0.4.0
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
@@ -217,6 +218,8 @@ require (
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||
|
||||
@@ -530,6 +530,7 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslC
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
@@ -544,6 +545,8 @@ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjh
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
@@ -585,6 +588,8 @@ github.com/jdcloud-api/jdcloud-sdk-go v1.64.0 h1:xZc/ZRcrOhDx9Ra9htu6ui2gUUttmLs
|
||||
github.com/jdcloud-api/jdcloud-sdk-go v1.64.0/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
|
||||
github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jlaffaye/ftp"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
cli *ftp.ServerConn
|
||||
|
||||
wdMu sync.Mutex
|
||||
}
|
||||
|
||||
func NewClient(config *Config) (*Client, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("the configuration of FTP client is nil")
|
||||
}
|
||||
|
||||
client, err := createFtpClient(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ftp: %w", err)
|
||||
}
|
||||
|
||||
return &Client{cli: client}, nil
|
||||
}
|
||||
|
||||
func (c *Client) RawClient() *ftp.ServerConn {
|
||||
return c.cli
|
||||
}
|
||||
|
||||
func (c *Client) ChangeDir(ctx context.Context, path string) error {
|
||||
_, err := wrapFuncCtx(ctx, func() (struct{}, error) {
|
||||
c.wdMu.Lock()
|
||||
defer c.wdMu.Unlock()
|
||||
|
||||
path = filepath.ToSlash(path)
|
||||
err := c.cli.ChangeDir(path)
|
||||
return struct{}{}, err
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("ftp: failed to change directory: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) CurrentDir(ctx context.Context) (string, error) {
|
||||
currentDir, err := wrapFuncCtx(ctx, func() (string, error) {
|
||||
c.wdMu.Lock()
|
||||
defer c.wdMu.Unlock()
|
||||
|
||||
return c.cli.CurrentDir()
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ftp: failed to get current directory: %w", err)
|
||||
}
|
||||
|
||||
return currentDir, nil
|
||||
}
|
||||
|
||||
func (c *Client) Delete(ctx context.Context, path string) error {
|
||||
_, err := wrapFuncCtx(ctx, func() (struct{}, error) {
|
||||
path = filepath.Clean(path)
|
||||
filename := filepath.Base(path)
|
||||
if filename != path {
|
||||
c.wdMu.Lock()
|
||||
defer c.wdMu.Unlock()
|
||||
|
||||
currentDir, err := c.cli.CurrentDir()
|
||||
if err != nil {
|
||||
return struct{}{}, err
|
||||
}
|
||||
|
||||
targetDir := filepath.Dir(path)
|
||||
if targetDir != currentDir {
|
||||
if err := c.cli.ChangeDir(targetDir); err != nil {
|
||||
return struct{}{}, err
|
||||
}
|
||||
defer c.cli.ChangeDir(currentDir)
|
||||
}
|
||||
}
|
||||
|
||||
err := c.cli.Delete(filename)
|
||||
return struct{}{}, err
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("ftp: failed to delete file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Mkdir(ctx context.Context, path string) error {
|
||||
_, err := wrapFuncCtx(ctx, func() (struct{}, error) {
|
||||
c.wdMu.Lock()
|
||||
defer c.wdMu.Unlock()
|
||||
|
||||
err := c.cli.MakeDir(path)
|
||||
return struct{}{}, err
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("ftp: failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) MkdirAll(ctx context.Context, path string) error {
|
||||
if path == "" || path == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := wrapFuncCtx(ctx, func() (struct{}, error) {
|
||||
c.wdMu.Lock()
|
||||
defer c.wdMu.Unlock()
|
||||
|
||||
currentDir, err := c.cli.CurrentDir()
|
||||
if err != nil {
|
||||
return struct{}{}, err
|
||||
}
|
||||
|
||||
path = filepath.ToSlash(filepath.Clean(path))
|
||||
if path == "/" || currentDir == path {
|
||||
return struct{}{}, nil
|
||||
}
|
||||
|
||||
defer c.cli.ChangeDir(currentDir)
|
||||
|
||||
dirs := strings.Split(path, "/")
|
||||
for i, dir := range dirs {
|
||||
if i == 0 && filepath.IsAbs(path) {
|
||||
if err := c.cli.ChangeDir("/"); err != nil {
|
||||
return struct{}{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if dir == "" || dir == "." {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.cli.ChangeDir(dir); err != nil {
|
||||
if err := c.cli.MakeDir(dir); err != nil {
|
||||
return struct{}{}, err
|
||||
}
|
||||
|
||||
if err := c.cli.ChangeDir(dir); err != nil {
|
||||
return struct{}{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return struct{}{}, nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("ftp: failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Retrieve(ctx context.Context, path string) (*File, error) {
|
||||
file, err := wrapFuncCtx(ctx, func() (*File, error) {
|
||||
path = filepath.Clean(path)
|
||||
filename := filepath.Base(path)
|
||||
if filename != path {
|
||||
c.wdMu.Lock()
|
||||
defer c.wdMu.Unlock()
|
||||
|
||||
currentDir, err := c.cli.CurrentDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetDir := filepath.Dir(path)
|
||||
if targetDir != currentDir {
|
||||
if err := c.cli.ChangeDir(targetDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer c.cli.ChangeDir(currentDir)
|
||||
}
|
||||
}
|
||||
|
||||
return c.cli.Retr(filename)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ftp: failed to retrieve file: %w", err)
|
||||
}
|
||||
|
||||
return file, err
|
||||
}
|
||||
|
||||
func (c *Client) Store(ctx context.Context, path string, reader io.Reader, offset uint64) error {
|
||||
_, err := wrapFuncCtx(ctx, func() (struct{}, error) {
|
||||
path = filepath.Clean(path)
|
||||
filename := filepath.Base(path)
|
||||
if filename != path {
|
||||
c.wdMu.Lock()
|
||||
defer c.wdMu.Unlock()
|
||||
|
||||
currentDir, err := c.cli.CurrentDir()
|
||||
if err != nil {
|
||||
return struct{}{}, err
|
||||
}
|
||||
|
||||
targetDir := filepath.Dir(path)
|
||||
if targetDir != currentDir {
|
||||
if err := c.cli.ChangeDir(targetDir); err != nil {
|
||||
return struct{}{}, err
|
||||
}
|
||||
defer c.cli.ChangeDir(currentDir)
|
||||
}
|
||||
}
|
||||
|
||||
err := c.cli.StorFrom(filename, reader, offset)
|
||||
return struct{}{}, err
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("ftp: failed to store file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) StoreString(ctx context.Context, path string, data string) error {
|
||||
reader := strings.NewReader(data)
|
||||
return c.Store(ctx, path, reader, 0)
|
||||
}
|
||||
|
||||
func (c *Client) StoreBytes(ctx context.Context, path string, data []byte) error {
|
||||
reader := bytes.NewReader(data)
|
||||
return c.Store(ctx, path, reader, 0)
|
||||
}
|
||||
|
||||
func (c *Client) Quit(ctx context.Context) error {
|
||||
_, err := wrapFuncCtx(ctx, func() (struct{}, error) {
|
||||
c.cli.Logout()
|
||||
return struct{}{}, c.cli.Quit()
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("ftp: failed to quit: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createFtpClient(config *Config) (*ftp.ServerConn, error) {
|
||||
client, err := ftp.Dial(resolveAddr(config.Host, config.Port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.Username != "" || config.Password != "" {
|
||||
if err = client.Login(config.Username, config.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func resolveAddr(host string, port int) string {
|
||||
if port == 0 {
|
||||
port = defaultPort
|
||||
}
|
||||
return net.JoinHostPort(host, strconv.Itoa(port))
|
||||
}
|
||||
|
||||
func wrapFuncCtx[T any](ctx context.Context, fn func() (T, error)) (T, error) {
|
||||
type result struct {
|
||||
res T
|
||||
err error
|
||||
}
|
||||
|
||||
done := make(chan result, 1)
|
||||
|
||||
go func() {
|
||||
res, err := fn()
|
||||
done <- result{res, err}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
var res T
|
||||
return res, ctx.Err()
|
||||
case r := <-done:
|
||||
return r.res, r.err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ftp
|
||||
|
||||
const (
|
||||
defaultPort int = 21
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
Port: defaultPort,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"github.com/jlaffaye/ftp"
|
||||
)
|
||||
|
||||
type File = ftp.Response
|
||||
@@ -27,17 +27,21 @@ func NewClient(config *Config) (*Client, error) {
|
||||
|
||||
client, err := createS3Client(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("s3: %w", err)
|
||||
}
|
||||
|
||||
return &Client{cli: client}, nil
|
||||
}
|
||||
|
||||
func (c *Client) PutObject(ctx context.Context, bucket, key string, reader io.Reader, size int64) error {
|
||||
func (c *Client) RawClient() *minio.Client {
|
||||
return c.cli
|
||||
}
|
||||
|
||||
func (c *Client) PutObject(ctx context.Context, bucket, key string, reader io.Reader, size uint64) error {
|
||||
putOpts := minio.PutObjectOptions{
|
||||
DisableMultipart: true,
|
||||
}
|
||||
_, err := c.cli.PutObject(ctx, bucket, key, reader, size, putOpts)
|
||||
_, err := c.cli.PutObject(ctx, bucket, key, reader, int64(size), putOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("s3: failed to put object: %w", err)
|
||||
}
|
||||
@@ -47,12 +51,12 @@ func (c *Client) PutObject(ctx context.Context, bucket, key string, reader io.Re
|
||||
|
||||
func (c *Client) PutObjectString(ctx context.Context, bucket, key string, data string) error {
|
||||
reader := strings.NewReader(data)
|
||||
return c.PutObject(ctx, bucket, key, reader, reader.Size())
|
||||
return c.PutObject(ctx, bucket, key, reader, uint64(reader.Size()))
|
||||
}
|
||||
|
||||
func (c *Client) PutObjectBytes(ctx context.Context, bucket, key string, data []byte) error {
|
||||
reader := bytes.NewReader(data)
|
||||
return c.PutObject(ctx, bucket, key, reader, reader.Size())
|
||||
return c.PutObject(ctx, bucket, key, reader, uint64(reader.Size()))
|
||||
}
|
||||
|
||||
func (c *Client) RemoveObject(ctx context.Context, bucket, key string) error {
|
||||
@@ -73,7 +77,7 @@ func createS3Client(config *Config) (*minio.Client, error) {
|
||||
case SignatureV2:
|
||||
clientCred = credentials.NewStaticV2(config.AccessKey, config.SecretKey, "")
|
||||
default:
|
||||
return nil, fmt.Errorf("s3: unsupported signature version: '%s'", config.SignatureVersion)
|
||||
return nil, fmt.Errorf("unsupported signature version: '%s'", config.SignatureVersion)
|
||||
}
|
||||
|
||||
endpoint, secure := resolveEndpoint(config.Endpoint)
|
||||
@@ -92,7 +96,7 @@ func createS3Client(config *Config) (*minio.Client, error) {
|
||||
|
||||
client, err := minio.New(endpoint, clientOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("s3: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
|
||||
@@ -22,14 +22,23 @@ func NewClient(config *Config) (*Client, error) {
|
||||
|
||||
client, err := createSmtpClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("smtp: %w", err)
|
||||
}
|
||||
|
||||
return &Client{cli: client}, nil
|
||||
}
|
||||
|
||||
func (c *Client) RawClient() *mail.Client {
|
||||
return c.cli
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
return c.cli.Close()
|
||||
err := c.cli.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("smtp: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Send(ctx context.Context, msg *Message) error {
|
||||
@@ -87,7 +96,7 @@ func createSmtpClient(config *Config) (*mail.Client, error) {
|
||||
|
||||
client, err := mail.NewClient(config.Host, clientOptions...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("smtp: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.ErrorHandlerRegistry.RegisterHandler("smtp.qq.com", "QUIT", &wQQMailQuitErrorHandler{})
|
||||
|
||||
@@ -31,12 +31,20 @@ func NewClient(config *Config) (*Client, error) {
|
||||
conns[i].Close()
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("ssh: %w", err)
|
||||
}
|
||||
|
||||
return &Client{conns: conns, clis: clis}, nil
|
||||
}
|
||||
|
||||
func (c *Client) RawClient() *ssh.Client {
|
||||
if len(c.clis) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.clis[len(c.clis)-1]
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
errs := make([]error, 0)
|
||||
|
||||
@@ -57,20 +65,12 @@ func (c *Client) Close() error {
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
} else if len(errs) == 1 {
|
||||
return errs[0]
|
||||
return fmt.Errorf("ssh: %w", errs[0])
|
||||
} else {
|
||||
return errors.Join(errs...)
|
||||
return fmt.Errorf("ssh: %w", errors.Join(errs...))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetClient() *ssh.Client {
|
||||
if len(c.clis) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.clis[len(c.clis)-1]
|
||||
}
|
||||
|
||||
func createConnsAndSshClients(config *Config) (conns []net.Conn, clis []*ssh.Client, err error) {
|
||||
conns = make([]net.Conn, 0)
|
||||
clis = make([]*ssh.Client, 0)
|
||||
@@ -134,7 +134,7 @@ func createConnsAndSshClients(config *Config) (conns []net.Conn, clis []*ssh.Cli
|
||||
|
||||
func createSshClientWithConn(config *ServerConfig, conn net.Conn) (*ssh.Client, error) {
|
||||
if conn == nil {
|
||||
return nil, fmt.Errorf("ssh: nil conn")
|
||||
return nil, fmt.Errorf("nil conn")
|
||||
}
|
||||
|
||||
authMethodType := lo.
|
||||
@@ -147,17 +147,17 @@ func createSshClientWithConn(config *ServerConfig, conn net.Conn) (*ssh.Client,
|
||||
case AuthMethodTypeNone:
|
||||
{
|
||||
if config.Username == "" {
|
||||
return nil, fmt.Errorf("ssh: unset username")
|
||||
return nil, fmt.Errorf("missing username")
|
||||
}
|
||||
}
|
||||
|
||||
case AuthMethodTypePassword:
|
||||
{
|
||||
if config.Username == "" {
|
||||
return nil, fmt.Errorf("ssh: unset username")
|
||||
return nil, fmt.Errorf("missing username")
|
||||
}
|
||||
if config.Password == "" {
|
||||
return nil, fmt.Errorf("ssh: unset password")
|
||||
return nil, fmt.Errorf("missing password")
|
||||
}
|
||||
|
||||
password := config.Password
|
||||
@@ -183,10 +183,10 @@ func createSshClientWithConn(config *ServerConfig, conn net.Conn) (*ssh.Client,
|
||||
case AuthMethodTypeKey:
|
||||
{
|
||||
if config.Username == "" {
|
||||
return nil, fmt.Errorf("ssh: unset username")
|
||||
return nil, fmt.Errorf("missing username")
|
||||
}
|
||||
if config.Key == "" {
|
||||
return nil, fmt.Errorf("ssh: unset key")
|
||||
return nil, fmt.Errorf("missing key")
|
||||
}
|
||||
|
||||
key := config.Key
|
||||
@@ -200,14 +200,14 @@ func createSshClientWithConn(config *ServerConfig, conn net.Conn) (*ssh.Client,
|
||||
signer, err = ssh.ParsePrivateKey([]byte(key))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssh: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authMethods = append(authMethods, ssh.PublicKeys(signer))
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("ssh: unsupported auth method '%s'", authMethodType)
|
||||
return nil, fmt.Errorf("unsupported auth method '%s'", authMethodType)
|
||||
}
|
||||
|
||||
addr := resolveAddr(config.Host, config.Port)
|
||||
@@ -217,7 +217,7 @@ func createSshClientWithConn(config *ServerConfig, conn net.Conn) (*ssh.Client,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssh: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ssh.NewClient(sshConn, chans, reqs), nil
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
|
||||
type ServerConfig struct {
|
||||
// SSH 主机。
|
||||
// 零值时默认值 "localhost"。
|
||||
SshHost string `json:"sshHost,omitempty"`
|
||||
// SSH 端口。
|
||||
// 零值时默认值 22。
|
||||
@@ -66,8 +65,8 @@ func (p *provider) Present(domain, token, keyAuth string) error {
|
||||
|
||||
defer client.Close()
|
||||
|
||||
challengeFilePath := filepath.Join(p.config.WebRootPath, http01.ChallengePath(token))
|
||||
if err := xssh.WriteRemoteString(client.GetClient(), challengeFilePath, keyAuth, p.config.UseSCP); err != nil {
|
||||
challengePath := filepath.Join(p.config.WebRootPath, http01.ChallengePath(token))
|
||||
if err := xssh.WriteRemoteString(client.RawClient(), challengePath, keyAuth, p.config.UseSCP); err != nil {
|
||||
return fmt.Errorf("failed to write file in webroot for HTTP challenge: %w", err)
|
||||
}
|
||||
|
||||
@@ -83,8 +82,8 @@ func (p *provider) CleanUp(domain, token, keyAuth string) error {
|
||||
defer client.Close()
|
||||
|
||||
// 删除质询文件
|
||||
challengeFilePath := filepath.Join(p.config.WebRootPath, http01.ChallengePath(token))
|
||||
xssh.RemoveRemote(client.GetClient(), challengeFilePath, p.config.UseSCP)
|
||||
challengePath := filepath.Join(p.config.WebRootPath, http01.ChallengePath(token))
|
||||
xssh.RemoveRemote(client.RawClient(), challengePath, p.config.UseSCP)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
|
||||
type ServerConfig struct {
|
||||
// SSH 主机。
|
||||
// 零值时默认值 "localhost"。
|
||||
SshHost string `json:"sshHost,omitempty"`
|
||||
// SSH 端口。
|
||||
// 零值时默认值 22。
|
||||
@@ -124,7 +123,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
command = strings.ReplaceAll(command, "${CERTIMATE_DEPLOYER_CMDVAR_JKS_KEYPASS}", d.config.JksKeypass)
|
||||
command = strings.ReplaceAll(command, "${CERTIMATE_DEPLOYER_CMDVAR_JKS_STOREPASS}", d.config.JksStorepass)
|
||||
|
||||
stdout, stderr, err := xssh.RunCommand(client.GetClient(), command)
|
||||
stdout, stderr, err := xssh.RunCommand(client.RawClient(), command)
|
||||
d.logger.Debug("run pre-command", slog.String("stdout", stdout), slog.String("stderr", stderr))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute pre-command (stdout: %s, stderr: %s): %w ", stdout, stderr, err)
|
||||
@@ -135,26 +134,26 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
switch d.config.OutputFormat {
|
||||
case OUTPUT_FORMAT_PEM:
|
||||
{
|
||||
if err := xssh.WriteRemoteString(client.GetClient(), d.config.OutputCertPath, certPEM, d.config.UseSCP); err != nil {
|
||||
if err := xssh.WriteRemoteString(client.RawClient(), d.config.OutputCertPath, certPEM, d.config.UseSCP); err != nil {
|
||||
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
|
||||
}
|
||||
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
|
||||
|
||||
if d.config.OutputServerCertPath != "" {
|
||||
if err := xssh.WriteRemoteString(client.GetClient(), d.config.OutputServerCertPath, serverCertPEM, d.config.UseSCP); err != nil {
|
||||
if err := xssh.WriteRemoteString(client.RawClient(), d.config.OutputServerCertPath, serverCertPEM, d.config.UseSCP); err != nil {
|
||||
return nil, fmt.Errorf("failed to save server certificate file: %w", err)
|
||||
}
|
||||
d.logger.Info("ssl server certificate file uploaded", slog.String("path", d.config.OutputServerCertPath))
|
||||
}
|
||||
|
||||
if d.config.OutputIntermediaCertPath != "" {
|
||||
if err := xssh.WriteRemoteString(client.GetClient(), d.config.OutputIntermediaCertPath, intermediaCertPEM, d.config.UseSCP); err != nil {
|
||||
if err := xssh.WriteRemoteString(client.RawClient(), d.config.OutputIntermediaCertPath, intermediaCertPEM, d.config.UseSCP); err != nil {
|
||||
return nil, fmt.Errorf("failed to save intermedia certificate file: %w", err)
|
||||
}
|
||||
d.logger.Info("ssl intermedia certificate file uploaded", slog.String("path", d.config.OutputIntermediaCertPath))
|
||||
}
|
||||
|
||||
if err := xssh.WriteRemoteString(client.GetClient(), d.config.OutputKeyPath, privkeyPEM, d.config.UseSCP); err != nil {
|
||||
if err := xssh.WriteRemoteString(client.RawClient(), d.config.OutputKeyPath, privkeyPEM, d.config.UseSCP); err != nil {
|
||||
return nil, fmt.Errorf("failed to upload private key file: %w", err)
|
||||
}
|
||||
d.logger.Info("ssl private key file uploaded", slog.String("path", d.config.OutputKeyPath))
|
||||
@@ -168,7 +167,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
}
|
||||
d.logger.Info("ssl certificate transformed to pfx")
|
||||
|
||||
if err := xssh.WriteRemote(client.GetClient(), d.config.OutputCertPath, pfxData, d.config.UseSCP); err != nil {
|
||||
if err := xssh.WriteRemote(client.RawClient(), d.config.OutputCertPath, pfxData, d.config.UseSCP); err != nil {
|
||||
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
|
||||
}
|
||||
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
|
||||
@@ -182,7 +181,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
}
|
||||
d.logger.Info("ssl certificate transformed to jks")
|
||||
|
||||
if err := xssh.WriteRemote(client.GetClient(), d.config.OutputCertPath, jksData, d.config.UseSCP); err != nil {
|
||||
if err := xssh.WriteRemote(client.RawClient(), d.config.OutputCertPath, jksData, d.config.UseSCP); err != nil {
|
||||
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
|
||||
}
|
||||
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
|
||||
@@ -204,7 +203,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
command = strings.ReplaceAll(command, "${CERTIMATE_DEPLOYER_CMDVAR_JKS_KEYPASS}", d.config.JksKeypass)
|
||||
command = strings.ReplaceAll(command, "${CERTIMATE_DEPLOYER_CMDVAR_JKS_STOREPASS}", d.config.JksStorepass)
|
||||
|
||||
stdout, stderr, err := xssh.RunCommand(client.GetClient(), command)
|
||||
stdout, stderr, err := xssh.RunCommand(client.RawClient(), command)
|
||||
d.logger.Debug("run post-command", slog.String("stdout", stdout), slog.String("stderr", stderr))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute post-command (stdout: %s, stderr: %s): %w ", stdout, stderr, err)
|
||||
|
||||
@@ -50,48 +50,3 @@ func TestCertHostnameUtil_IsMatch(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCertHostnameUtil_IsMatch(t *testing.T) {
|
||||
t.Run("IsMatch", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
pattern string
|
||||
hostname string
|
||||
expected bool
|
||||
}{
|
||||
{"*.example.com", "sub.example.com", true},
|
||||
{"*.example.com", "sub.sub.example.com", false},
|
||||
{"*.example.com", "*.example.com", true},
|
||||
{"*.example.com", ".example.com", true},
|
||||
{"*.example.com", "example.com", false},
|
||||
|
||||
{"*.*.example.com", "a.b.example.com", false},
|
||||
{"*.*.example.com", "a.example.com", false},
|
||||
{"*.*.example.com", "a.b.c.example.com", false},
|
||||
|
||||
{"example.com", "example.com", true},
|
||||
{"example.com", "wrong.com", false},
|
||||
|
||||
{"", "example.com", false},
|
||||
{"*.example.com", "", false},
|
||||
|
||||
{"*.sub.example.com", "a.sub.example.com", true},
|
||||
{"*.sub.example.com", "a.b.sub.example.com", false},
|
||||
{"*.sub.example.com", "sub.example.com", false},
|
||||
|
||||
{"*.Example.COM", "sub.example.com", true},
|
||||
{"*.EXAMPLE.COM", "SUB.EXAMPLE.COM", true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
result := xcerthostname.IsMatch(tc.pattern, tc.hostname)
|
||||
status := "✓"
|
||||
pf := t.Logf
|
||||
if result != tc.expected {
|
||||
status = "✗"
|
||||
pf = t.Errorf
|
||||
}
|
||||
|
||||
pf("%s Pattern: %-20s Hostname: %-20s Expected: %-5v Got: %-5v\n", status, tc.pattern, tc.hostname, tc.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user