github.com/code-to-go/safepool.lib@v0.0.0-20221205180519-ee25e63c226e/transport/gdrive_go (about)

     1  package transport
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/fs"
     7  	"net/http"
     8  
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/code-to-go/safepool.lib/core"
    15  
    16  	"github.com/pkg/sftp"
    17  	"golang.org/x/crypto/ssh"
    18  
    19  	"golang.org/x/oauth2"
    20  	"golang.org/x/oauth2/google"
    21  	"golang.org/x/oauth2/jwt"
    22  	"google.golang.org/api/drive/v3"
    23  	"google.golang.org/api/option"
    24  )
    25  
    26  type ServiceAccountConfig struct {
    27  	ClientEmail  string
    28  	PrivateKey   []byte
    29  	PrivateKeyID string
    30  	AuthURI      string
    31  	TokenURI     string
    32  }
    33  
    34  type GDriveConfig struct {
    35  	ServiceAccount ServiceAccountConfig
    36  
    37  	ClientID     string `json:"clientId" yaml:"clientId"`
    38  	ClientSecret string `json:"clientSecret" yaml:"clientSecret"`
    39  
    40  	Password string `json:"password" yaml:"password"`
    41  	KeyPath  string `json:"keyPath" yaml:"keyPath"`
    42  	Base     string `json:"base" yaml:"base"`
    43  }
    44  
    45  type GDrive struct {
    46  	c    *sftp.Client
    47  	base string
    48  	url  string
    49  }
    50  
    51  var endpoints = oauth2.Endpoint{
    52  	AuthURL:  "https://accounts.google.com/o/oauth2/auth",
    53  	TokenURL: "https://accounts.google.com/o/oauth2/token",
    54  }
    55  
    56  func getJwtConfig(c GDriveConfig, scopes []string) *jwt.Config {
    57  	cfg := &jwt.Config{
    58  		Email:        c.ServiceAccount.ClientEmail,
    59  		PrivateKey:   c.ServiceAccount.PrivateKey,
    60  		PrivateKeyID: c.ServiceAccount.PrivateKeyID,
    61  		TokenURL:     f.TokenURL,
    62  	}
    63  	if cfg.TokenURL == "" {
    64  		cfg.TokenURL = JWTTokenURL
    65  	}
    66  	return cfg
    67  }
    68  
    69  func getClient(c GDriveConfig) *http.Client {
    70  	oa := &oauth2.Config{}
    71  
    72  	config := &oauth2.Config{
    73  		ClientID:     c.ClientID,
    74  		ClientSecret: c.ClientSecret,
    75  	}
    76  
    77  	// The file token.json stores the user's access and refresh tokens, and is
    78  	// created automatically when the authorization flow completes for the first
    79  	// time.
    80  	tokFile := "token.json"
    81  	tok, err := tokenFromFile(tokFile)
    82  	if err != nil {
    83  		tok = getTokenFromWeb(config)
    84  		saveToken(tokFile, tok)
    85  	}
    86  	return config.Client(context.Background(), tok)
    87  }
    88  
    89  func NewGDrive(config GDriveConfig) (Exchanger, error) {
    90  	addr := config.Addr
    91  	if !strings.ContainsRune(addr, ':') {
    92  		addr = fmt.Sprintf("%s:22", addr)
    93  	}
    94  
    95  	var url string
    96  	var auth []ssh.AuthMethod
    97  	if config.Password != "" {
    98  		auth = append(auth, ssh.Password(config.Password))
    99  		url = fmt.Sprintf("sftp://%s@%s/%s", config.Username, config.Addr, config.Base)
   100  	}
   101  	if config.KeyPath != "" {
   102  		key, err := os.ReadFile(config.KeyPath)
   103  		if err != nil {
   104  			return nil, fmt.Errorf("cannot load key file %s: %v", config.KeyPath, err)
   105  		}
   106  
   107  		signer, err := ssh.ParsePrivateKey(key)
   108  		if err != nil {
   109  			return nil, fmt.Errorf("invalid key file %s: %v", config.KeyPath, err)
   110  		}
   111  		auth = append(auth, ssh.PublicKeys(signer))
   112  		url = fmt.Sprintf("sftp://!%s@%s/%s", filepath.Base(config.KeyPath), config.Addr, config.Base)
   113  	}
   114  	if len(auth) == 0 {
   115  		return nil, fmt.Errorf("no auth method provided for sftp connection to %s", config.Addr)
   116  	}
   117  
   118  	cc := &ssh.ClientConfig{
   119  		User: config.Username,
   120  		Auth: []ssh.AuthMethod{
   121  			ssh.Password(config.Password),
   122  		},
   123  		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
   124  	}
   125  
   126  	client, err := ssh.Dial("tcp", addr, cc)
   127  	if err != nil {
   128  		return nil, fmt.Errorf("cannot connect to %s: %v", addr, err)
   129  	}
   130  	c, err := sftp.NewClient(client)
   131  	if err != nil {
   132  		return nil, fmt.Errorf("cannot create a sftp client for %s: %v", addr, err)
   133  	}
   134  
   135  	base := config.Base
   136  	if base == "" {
   137  		base = "/"
   138  	}
   139  	return &GDrive{c, base, url}, nil
   140  }
   141  
   142  func (g *GDrive) Read(name string, rang *Range, dest io.Writer) error {
   143  	f, err := g.c.Open(path.Join(g.base, name))
   144  	if core.IsErr(err, "cannot open file on sftp server %v:%v", g) {
   145  		return err
   146  	}
   147  
   148  	if rang == nil {
   149  		_, err = io.Copy(dest, f)
   150  	} else {
   151  		left := rang.To - rang.From
   152  		f.Seek(rang.From, 0)
   153  		var b [4096]byte
   154  
   155  		for left > 0 && err == nil {
   156  			var sz int64
   157  			if rang.From-rang.To > 4096 {
   158  				sz = 4096
   159  			} else {
   160  				sz = rang.From - rang.To
   161  			}
   162  			_, err = f.Read(b[0:sz])
   163  			dest.Write(b[0:sz])
   164  			left -= sz
   165  		}
   166  	}
   167  	if core.IsErr(err, "cannot read from %s/%s:%v", g, name) {
   168  		return err
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  func (s *GDrive) Write(name string, source io.Reader) error {
   175  	return nil
   176  }
   177  
   178  func (s *GDrive) ReadDir(prefix string, opts ListOption) ([]fs.FileInfo, error) {
   179  	dir, prefix := path.Split(prefix)
   180  	result, err := s.c.ReadDir(dir)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	var infos []fs.FileInfo
   186  	for _, item := range result {
   187  		if strings.HasPrefix(item.Name(), prefix) {
   188  			infos = append(infos, item)
   189  		}
   190  	}
   191  
   192  	return infos, nil
   193  }
   194  
   195  func (s *GDrive) Stat(name string) (os.FileInfo, error) {
   196  	return s.c.Stat(path.Join(s.base, name))
   197  }
   198  
   199  func (s *GDrive) Rename(old, new string) error {
   200  	return s.c.Rename(path.Join(s.base, old), path.Join(s.base, new))
   201  }
   202  
   203  func (s *GDrive) Delete(name string) error {
   204  	return s.c.Remove(path.Join(s.base, name))
   205  }
   206  
   207  func (s *GDrive) Close() error {
   208  	return s.c.Close()
   209  }
   210  
   211  func (s *GDrive) String() string {
   212  	return s.url
   213  }