2015-11-08 15:29:58 +00:00
|
|
|
// Package hubic provides an interface to the Hubic object storage
|
|
|
|
// system.
|
|
|
|
package hubic
|
|
|
|
|
|
|
|
// This uses the normal swift mechanism to update the credentials and
|
|
|
|
// ignores the expires field returned by the Hubic API. This may need
|
Spelling fixes
Fix spelling of: above, already, anonymous, associated,
authentication, bandwidth, because, between, blocks, calculate,
candidates, cautious, changelog, cleaner, clipboard, command,
completely, concurrently, considered, constructs, corrupt, current,
daemon, dependencies, deprecated, directory, dispatcher, download,
eligible, ellipsis, encrypter, endpoint, entrieslist, essentially,
existing writers, existing, expires, filesystem, flushing, frequently,
hierarchy, however, implementation, implements, inaccurate,
individually, insensitive, longer, maximum, metadata, modified,
multipart, namedirfirst, nextcloud, obscured, opened, optional,
owncloud, pacific, passphrase, password, permanently, persimmon,
positive, potato, protocol, quota, receiving, recommends, referring,
requires, revisited, satisfied, satisfies, satisfy, semver,
serialized, session, storage, strategies, stringlist, successful,
supported, surprise, temporarily, temporary, transactions, unneeded,
update, uploads, wrapped
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2020-10-09 00:17:24 +00:00
|
|
|
// to be revisited after some actual experience.
|
2015-11-08 15:29:58 +00:00
|
|
|
|
|
|
|
import (
|
2019-09-04 19:21:10 +00:00
|
|
|
"context"
|
2015-11-08 15:29:58 +00:00
|
|
|
"encoding/json"
|
2021-11-04 10:12:57 +00:00
|
|
|
"errors"
|
2015-11-08 15:29:58 +00:00
|
|
|
"fmt"
|
2019-01-11 14:14:37 +00:00
|
|
|
"io/ioutil"
|
2015-11-08 15:29:58 +00:00
|
|
|
"net/http"
|
2019-01-11 14:14:37 +00:00
|
|
|
"strings"
|
2015-11-08 15:29:58 +00:00
|
|
|
"time"
|
|
|
|
|
2021-01-22 17:23:51 +00:00
|
|
|
swiftLib "github.com/ncw/swift/v2"
|
2019-07-28 17:47:38 +00:00
|
|
|
"github.com/rclone/rclone/backend/swift"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
|
|
"github.com/rclone/rclone/fs/config/configmap"
|
|
|
|
"github.com/rclone/rclone/fs/config/configstruct"
|
|
|
|
"github.com/rclone/rclone/fs/config/obscure"
|
|
|
|
"github.com/rclone/rclone/fs/fshttp"
|
|
|
|
"github.com/rclone/rclone/lib/oauthutil"
|
2015-11-08 15:29:58 +00:00
|
|
|
"golang.org/x/oauth2"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2016-02-28 19:57:19 +00:00
|
|
|
rcloneClientID = "api_hubic_svWP970PvSWbw5G3PzrAqZ6X2uHeZBPI"
|
2016-08-14 11:04:43 +00:00
|
|
|
rcloneEncryptedClientSecret = "leZKCcqy9movLhDWLVXX8cSLp_FzoiAPeEJOIOMRw1A5RuC4iLEPDYPWVF46adC_MVonnLdVEOTHVstfBOZ_lY4WNp8CK_YWlpRZ9diT5YI"
|
2015-11-08 15:29:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Globals
|
|
|
|
var (
|
|
|
|
// Description of how to auth for this app
|
|
|
|
oauthConfig = &oauth2.Config{
|
|
|
|
Scopes: []string{
|
2020-05-20 10:54:33 +00:00
|
|
|
"credentials.r", // Read OpenStack credentials
|
2015-11-08 15:29:58 +00:00
|
|
|
},
|
|
|
|
Endpoint: oauth2.Endpoint{
|
|
|
|
AuthURL: "https://api.hubic.com/oauth/auth/",
|
|
|
|
TokenURL: "https://api.hubic.com/oauth/token/",
|
|
|
|
},
|
|
|
|
ClientID: rcloneClientID,
|
2018-01-18 20:19:55 +00:00
|
|
|
ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
|
2015-11-08 15:29:58 +00:00
|
|
|
RedirectURL: oauthutil.RedirectLocalhostURL,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Register with Fs
|
|
|
|
func init() {
|
2016-02-18 11:35:25 +00:00
|
|
|
fs.Register(&fs.RegInfo{
|
2016-02-15 18:11:53 +00:00
|
|
|
Name: "hubic",
|
|
|
|
Description: "Hubic",
|
|
|
|
NewFs: NewFs,
|
2021-04-29 08:28:18 +00:00
|
|
|
Config: func(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) {
|
|
|
|
return oauthutil.ConfigOut("", &oauthutil.Options{
|
|
|
|
OAuth2Config: oauthConfig,
|
|
|
|
})
|
2015-11-08 15:29:58 +00:00
|
|
|
},
|
2020-08-01 23:32:21 +00:00
|
|
|
Options: append(oauthutil.SharedOptions, swift.SharedOptions...),
|
2015-11-08 15:29:58 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// credentials is the JSON returned from the Hubic API to read the
|
|
|
|
// OpenStack credentials
|
|
|
|
type credentials struct {
|
2020-05-20 10:54:33 +00:00
|
|
|
Token string `json:"token"` // OpenStack token
|
|
|
|
Endpoint string `json:"endpoint"` // OpenStack endpoint
|
2020-10-13 21:49:58 +00:00
|
|
|
Expires string `json:"expires"` // Expires date - e.g. "2015-11-09T14:24:56+01:00"
|
2015-11-08 15:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fs represents a remote hubic
|
|
|
|
type Fs struct {
|
|
|
|
fs.Fs // wrapped Fs
|
2017-01-13 17:21:47 +00:00
|
|
|
features *fs.Features // optional features
|
2015-11-08 15:29:58 +00:00
|
|
|
client *http.Client // client for oauth api
|
|
|
|
credentials credentials // returned from the Hubic API
|
|
|
|
expires time.Time // time credentials expire
|
|
|
|
}
|
|
|
|
|
|
|
|
// Object describes a swift object
|
|
|
|
type Object struct {
|
|
|
|
*swift.Object
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return a string version
|
|
|
|
func (o *Object) String() string {
|
|
|
|
if o == nil {
|
|
|
|
return "<nil>"
|
|
|
|
}
|
|
|
|
return o.Object.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------
|
|
|
|
|
|
|
|
// String converts this Fs to a string
|
|
|
|
func (f *Fs) String() string {
|
|
|
|
if f.Fs == nil {
|
|
|
|
return "Hubic"
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("Hubic %s", f.Fs.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// getCredentials reads the OpenStack Credentials using the Hubic API
|
|
|
|
//
|
|
|
|
// The credentials are read into the Fs
|
2019-09-04 19:21:10 +00:00
|
|
|
func (f *Fs) getCredentials(ctx context.Context) (err error) {
|
2021-02-03 17:41:27 +00:00
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.hubic.com/1.0/account/credentials", nil)
|
2015-11-08 15:29:58 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
resp, err := f.client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-11-28 18:13:08 +00:00
|
|
|
defer fs.CheckClose(resp.Body, &err)
|
2015-11-08 15:29:58 +00:00
|
|
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
2019-01-11 14:14:37 +00:00
|
|
|
body, _ := ioutil.ReadAll(resp.Body)
|
2022-05-16 16:11:45 +00:00
|
|
|
bodyStr := strings.TrimSpace(strings.ReplaceAll(string(body), "\n", " "))
|
2021-11-04 10:12:57 +00:00
|
|
|
return fmt.Errorf("failed to get credentials: %s: %s", resp.Status, bodyStr)
|
2015-11-08 15:29:58 +00:00
|
|
|
}
|
|
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
var result credentials
|
|
|
|
err = decoder.Decode(&result)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-02-09 11:01:20 +00:00
|
|
|
// fs.Debugf(f, "Got credentials %+v", result)
|
2015-11-08 15:29:58 +00:00
|
|
|
if result.Token == "" || result.Endpoint == "" || result.Expires == "" {
|
2016-06-12 14:06:02 +00:00
|
|
|
return errors.New("couldn't read token, result and expired from credentials")
|
2015-11-08 15:29:58 +00:00
|
|
|
}
|
|
|
|
f.credentials = result
|
|
|
|
expires, err := time.Parse(time.RFC3339, result.Expires)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
f.expires = expires
|
2017-02-09 11:01:20 +00:00
|
|
|
fs.Debugf(f, "Got swift credentials (expiry %v in %v)", f.expires, f.expires.Sub(time.Now()))
|
2015-11-08 15:29:58 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewFs constructs an Fs from the path, container:path
|
2020-11-05 15:18:51 +00:00
|
|
|
func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, error) {
|
2020-11-05 18:02:26 +00:00
|
|
|
client, _, err := oauthutil.NewClient(ctx, name, m, oauthConfig)
|
2015-11-08 15:29:58 +00:00
|
|
|
if err != nil {
|
2021-11-04 10:12:57 +00:00
|
|
|
return nil, fmt.Errorf("failed to configure Hubic: %w", err)
|
2015-11-08 15:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
f := &Fs{
|
|
|
|
client: client,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make the swift Connection
|
2020-11-05 11:33:32 +00:00
|
|
|
ci := fs.GetConfig(ctx)
|
2015-11-08 15:29:58 +00:00
|
|
|
c := &swiftLib.Connection{
|
|
|
|
Auth: newAuth(f),
|
2020-11-05 11:33:32 +00:00
|
|
|
ConnectTimeout: 10 * ci.ConnectTimeout, // Use the timeouts in the transport
|
|
|
|
Timeout: 10 * ci.Timeout, // Use the timeouts in the transport
|
2020-11-13 15:24:43 +00:00
|
|
|
Transport: fshttp.NewTransport(ctx),
|
2015-11-08 15:29:58 +00:00
|
|
|
}
|
2021-01-22 17:23:51 +00:00
|
|
|
err = c.Authenticate(ctx)
|
2015-11-08 15:29:58 +00:00
|
|
|
if err != nil {
|
2021-11-04 10:12:57 +00:00
|
|
|
return nil, fmt.Errorf("error authenticating swift connection: %w", err)
|
2015-11-08 15:29:58 +00:00
|
|
|
}
|
|
|
|
|
2018-05-14 17:06:57 +00:00
|
|
|
// Parse config into swift.Options struct
|
|
|
|
opt := new(swift.Options)
|
|
|
|
err = configstruct.Set(m, opt)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-11-08 15:29:58 +00:00
|
|
|
// Make inner swift Fs from the connection
|
2020-11-05 15:18:51 +00:00
|
|
|
swiftFs, err := swift.NewFsWithConnection(ctx, opt, name, root, c, true)
|
2016-06-21 17:01:53 +00:00
|
|
|
if err != nil && err != fs.ErrorIsFile {
|
2015-11-08 15:29:58 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
f.Fs = swiftFs
|
2017-01-13 17:21:47 +00:00
|
|
|
f.features = f.Fs.Features().Wrap(f)
|
2016-06-21 17:01:53 +00:00
|
|
|
return f, err
|
2015-11-08 15:29:58 +00:00
|
|
|
}
|
|
|
|
|
2017-01-13 17:21:47 +00:00
|
|
|
// Features returns the optional features of this Fs
|
|
|
|
func (f *Fs) Features() *fs.Features {
|
|
|
|
return f.features
|
2015-11-08 15:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UnWrap returns the Fs that this Fs is wrapping
|
|
|
|
func (f *Fs) UnWrap() fs.Fs {
|
|
|
|
return f.Fs
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the interfaces are satisfied
|
|
|
|
var (
|
|
|
|
_ fs.Fs = (*Fs)(nil)
|
|
|
|
_ fs.UnWrapper = (*Fs)(nil)
|
|
|
|
)
|