2015-11-08 16:29:58 +01: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
|
|
|
|
// to be revisted after some actual experience.
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
2018-01-11 17:05:41 +01:00
|
|
|
"github.com/ncw/rclone/backend/swift"
|
2015-11-08 16:29:58 +01:00
|
|
|
"github.com/ncw/rclone/fs"
|
2018-01-11 17:29:20 +01:00
|
|
|
"github.com/ncw/rclone/lib/oauthutil"
|
2015-11-08 16:29:58 +01:00
|
|
|
swiftLib "github.com/ncw/swift"
|
2016-06-12 16:06:02 +02:00
|
|
|
"github.com/pkg/errors"
|
2015-11-08 16:29:58 +01:00
|
|
|
"golang.org/x/oauth2"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2016-02-28 20:57:19 +01:00
|
|
|
rcloneClientID = "api_hubic_svWP970PvSWbw5G3PzrAqZ6X2uHeZBPI"
|
2016-08-14 13:04:43 +02:00
|
|
|
rcloneEncryptedClientSecret = "leZKCcqy9movLhDWLVXX8cSLp_FzoiAPeEJOIOMRw1A5RuC4iLEPDYPWVF46adC_MVonnLdVEOTHVstfBOZ_lY4WNp8CK_YWlpRZ9diT5YI"
|
2015-11-08 16:29:58 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// Globals
|
|
|
|
var (
|
|
|
|
// Description of how to auth for this app
|
|
|
|
oauthConfig = &oauth2.Config{
|
|
|
|
Scopes: []string{
|
|
|
|
"credentials.r", // Read Openstack credentials
|
|
|
|
},
|
|
|
|
Endpoint: oauth2.Endpoint{
|
|
|
|
AuthURL: "https://api.hubic.com/oauth/auth/",
|
|
|
|
TokenURL: "https://api.hubic.com/oauth/token/",
|
|
|
|
},
|
|
|
|
ClientID: rcloneClientID,
|
2016-08-14 13:04:43 +02:00
|
|
|
ClientSecret: fs.MustReveal(rcloneEncryptedClientSecret),
|
2015-11-08 16:29:58 +01:00
|
|
|
RedirectURL: oauthutil.RedirectLocalhostURL,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Register with Fs
|
|
|
|
func init() {
|
2016-02-18 12:35:25 +01:00
|
|
|
fs.Register(&fs.RegInfo{
|
2016-02-15 19:11:53 +01:00
|
|
|
Name: "hubic",
|
|
|
|
Description: "Hubic",
|
|
|
|
NewFs: NewFs,
|
2015-11-08 16:29:58 +01:00
|
|
|
Config: func(name string) {
|
2016-01-04 16:13:36 +01:00
|
|
|
err := oauthutil.Config("hubic", name, oauthConfig)
|
2015-11-08 16:29:58 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to configure token: %v", err)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Options: []fs.Option{{
|
2016-01-07 16:20:32 +01:00
|
|
|
Name: fs.ConfigClientID,
|
2015-11-08 16:29:58 +01:00
|
|
|
Help: "Hubic Client Id - leave blank normally.",
|
|
|
|
}, {
|
2016-01-07 16:20:32 +01:00
|
|
|
Name: fs.ConfigClientSecret,
|
2015-11-08 16:29:58 +01:00
|
|
|
Help: "Hubic Client Secret - leave blank normally.",
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// credentials is the JSON returned from the Hubic API to read the
|
|
|
|
// OpenStack credentials
|
|
|
|
type credentials struct {
|
|
|
|
Token string `json:"token"` // Openstack token
|
|
|
|
Endpoint string `json:"endpoint"` // Openstack endpoint
|
|
|
|
Expires string `json:"expires"` // Expires date - eg "2015-11-09T14:24:56+01:00"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fs represents a remote hubic
|
|
|
|
type Fs struct {
|
|
|
|
fs.Fs // wrapped Fs
|
2017-01-13 18:21:47 +01:00
|
|
|
features *fs.Features // optional features
|
2015-11-08 16:29:58 +01: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
|
|
|
|
func (f *Fs) getCredentials() (err error) {
|
|
|
|
req, err := http.NewRequest("GET", "https://api.hubic.com/1.0/account/credentials", nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
resp, err := f.client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-11-28 19:13:08 +01:00
|
|
|
defer fs.CheckClose(resp.Body, &err)
|
2015-11-08 16:29:58 +01:00
|
|
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
2016-06-12 16:06:02 +02:00
|
|
|
return errors.Errorf("failed to get credentials: %s", resp.Status)
|
2015-11-08 16:29:58 +01:00
|
|
|
}
|
|
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
var result credentials
|
|
|
|
err = decoder.Decode(&result)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-02-09 12:01:20 +01:00
|
|
|
// fs.Debugf(f, "Got credentials %+v", result)
|
2015-11-08 16:29:58 +01:00
|
|
|
if result.Token == "" || result.Endpoint == "" || result.Expires == "" {
|
2016-06-12 16:06:02 +02:00
|
|
|
return errors.New("couldn't read token, result and expired from credentials")
|
2015-11-08 16:29:58 +01:00
|
|
|
}
|
|
|
|
f.credentials = result
|
|
|
|
expires, err := time.Parse(time.RFC3339, result.Expires)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
f.expires = expires
|
2017-02-09 12:01:20 +01:00
|
|
|
fs.Debugf(f, "Got swift credentials (expiry %v in %v)", f.expires, f.expires.Sub(time.Now()))
|
2015-11-08 16:29:58 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewFs constructs an Fs from the path, container:path
|
|
|
|
func NewFs(name, root string) (fs.Fs, error) {
|
2016-05-23 19:03:22 +02:00
|
|
|
client, _, err := oauthutil.NewClient(name, oauthConfig)
|
2015-11-08 16:29:58 +01:00
|
|
|
if err != nil {
|
2016-06-12 16:06:02 +02:00
|
|
|
return nil, errors.Wrap(err, "failed to configure Hubic")
|
2015-11-08 16:29:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
f := &Fs{
|
|
|
|
client: client,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make the swift Connection
|
|
|
|
c := &swiftLib.Connection{
|
|
|
|
Auth: newAuth(f),
|
|
|
|
ConnectTimeout: 10 * fs.Config.ConnectTimeout, // Use the timeouts in the transport
|
|
|
|
Timeout: 10 * fs.Config.Timeout, // Use the timeouts in the transport
|
|
|
|
Transport: fs.Config.Transport(),
|
|
|
|
}
|
|
|
|
err = c.Authenticate()
|
|
|
|
if err != nil {
|
2016-06-12 16:06:02 +02:00
|
|
|
return nil, errors.Wrap(err, "error authenticating swift connection")
|
2015-11-08 16:29:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make inner swift Fs from the connection
|
2017-08-30 16:54:49 +02:00
|
|
|
swiftFs, err := swift.NewFsWithConnection(name, root, c, true)
|
2016-06-21 19:01:53 +02:00
|
|
|
if err != nil && err != fs.ErrorIsFile {
|
2015-11-08 16:29:58 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
f.Fs = swiftFs
|
2017-01-13 18:21:47 +01:00
|
|
|
f.features = f.Fs.Features().Wrap(f)
|
2016-06-21 19:01:53 +02:00
|
|
|
return f, err
|
2015-11-08 16:29:58 +01:00
|
|
|
}
|
|
|
|
|
2017-01-13 18:21:47 +01:00
|
|
|
// Features returns the optional features of this Fs
|
|
|
|
func (f *Fs) Features() *fs.Features {
|
|
|
|
return f.features
|
2015-11-08 16:29:58 +01: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)
|
|
|
|
)
|