publish-vod/pkg/uploader/sftp/sftp.go

97 lines
2.5 KiB
Go
Raw Normal View History

// Package sftp contains the implementation for an SFTP uploader
package sftp
import (
"context"
"fmt"
"net"
"os"
"path"
"git.luzifer.io/luzifer/publish-vod/pkg/uploader"
"github.com/Luzifer/go_helpers/v2/fieldcollection"
"github.com/sirupsen/logrus"
pkgSFTP "github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
type (
impl struct{}
)
// New returns a SFTP Uploader
func New() uploader.Uploader { return impl{} }
func (impl) Prepare(context.Context, uploader.Opts) error { return nil }
func (impl) UploadFile(_ context.Context, opts uploader.Opts) error {
socket := os.Getenv("SSH_AUTH_SOCK")
conn, err := net.Dial("unix", socket)
if err != nil {
return fmt.Errorf("opening SSH_AUTH_SOCK: %w", err)
}
agentClient := agent.NewClient(conn)
config := &ssh.ClientConfig{
User: opts.Config.MustString("user", nil),
Auth: []ssh.AuthMethod{
// Use a callback rather than PublicKeys so we only consult the
// agent once the remote server wants it.
ssh.PublicKeysCallback(agentClient.Signers),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), //#nosec G106
}
client, err := ssh.Dial("tcp", opts.Config.MustString("host", nil), config)
if err != nil {
return fmt.Errorf("dialing SSH server: %w", err)
}
defer func() {
if err := client.Close(); err != nil {
logrus.WithError(err).Error("closing SSH client (leaked fd)")
}
}()
sftpClient, err := pkgSFTP.NewClient(client)
if err != nil {
return fmt.Errorf("creating sftp client: %w", err)
}
defer func() {
if err := sftpClient.Close(); err != nil {
logrus.WithError(err).Error("closing SFTP client (leaked fd)")
}
}()
remoteFile := path.Join(opts.Config.MustString("path", nil), path.Base(opts.Filename))
f, err := sftpClient.OpenFile(remoteFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
if err != nil {
return fmt.Errorf("opening remote file: %w", err)
}
defer func() {
if err := f.Close(); err != nil {
logrus.WithError(err).Error("closing SFTP file (leaked fd)")
}
}()
progressReader := opts.ProgressBar.NewProxyReader(opts.Content)
if _, err = f.ReadFrom(progressReader); err != nil {
return fmt.Errorf("uploading video: %w", err)
}
opts.FinalMessage("Video uploaded to %s", remoteFile)
return nil
}
func (impl) ValidateConfig(config *fieldcollection.FieldCollection) error {
for _, key := range []string{"host", "path", "user"} {
if v, err := config.String(key); err != nil || v == "" {
return fmt.Errorf("key %q must be non-empty string", key)
}
}
return nil
}