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 }