From b78c9a65fa73ec3bb65487287bd7ce8b3249262a Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 6 Apr 2021 21:27:34 +0100 Subject: [PATCH] backends: remove log.Fatal and replace with error returns #5234 This changes the Config interface so that it returns an error. --- backend/amazonclouddrive/amazonclouddrive.go | 6 +-- backend/box/box.go | 8 +-- backend/cache/cache_internal_test.go | 2 +- backend/drive/drive.go | 13 +++-- backend/dropbox/dropbox.go | 6 +-- backend/fichier/fichier.go | 4 +- .../googlecloudstorage/googlecloudstorage.go | 8 +-- backend/googlephotos/googlephotos.go | 9 ++-- backend/hubic/hubic.go | 6 +-- backend/jottacloud/jottacloud.go | 49 ++++++++++--------- backend/mailru/api/helpers.go | 10 ++-- backend/onedrive/onedrive.go | 49 +++++++++---------- backend/pcloud/pcloud.go | 6 +-- backend/premiumizeme/premiumizeme.go | 6 +-- backend/putio/putio.go | 7 +-- backend/seafile/seafile.go | 17 +++---- backend/sharefile/sharefile.go | 6 +-- backend/sugarsync/sugarsync.go | 10 ++-- backend/tardigrade/fs.go | 12 ++--- backend/uptobox/uptobox.go | 4 +- backend/yandex/yandex.go | 14 +++--- backend/zoho/zoho.go | 31 +++++++----- cmd/config/config.go | 7 ++- fs/config/authorize.go | 5 +- fs/config/config.go | 5 +- fs/config/ui.go | 35 ++++++++----- fs/config/ui_test.go | 8 +-- fs/fs.go | 2 +- 28 files changed, 179 insertions(+), 166 deletions(-) diff --git a/backend/amazonclouddrive/amazonclouddrive.go b/backend/amazonclouddrive/amazonclouddrive.go index 0f89c7eae..003b5f607 100644 --- a/backend/amazonclouddrive/amazonclouddrive.go +++ b/backend/amazonclouddrive/amazonclouddrive.go @@ -16,7 +16,6 @@ import ( "encoding/json" "fmt" "io" - "log" "net/http" "path" "strings" @@ -70,11 +69,12 @@ func init() { Prefix: "acd", Description: "Amazon Drive", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { err := oauthutil.Config(ctx, "amazon cloud drive", name, m, acdConfig, nil) if err != nil { - log.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } + return nil }, Options: append(oauthutil.SharedOptions, []fs.Option{{ Name: "checkpoint", diff --git a/backend/box/box.go b/backend/box/box.go index 3bea1bc67..34b627b17 100644 --- a/backend/box/box.go +++ b/backend/box/box.go @@ -17,7 +17,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "net/http" "net/url" "path" @@ -84,7 +83,7 @@ func init() { Name: "box", Description: "Box", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { jsonFile, ok := m.Get("box_config_file") boxSubType, boxSubTypeOk := m.Get("box_sub_type") boxAccessToken, boxAccessTokenOk := m.Get("access_token") @@ -93,15 +92,16 @@ func init() { if ok && boxSubTypeOk && jsonFile != "" && boxSubType != "" { err = refreshJWTToken(ctx, jsonFile, boxSubType, name, m) if err != nil { - log.Fatalf("Failed to configure token with jwt authentication: %v", err) + return errors.Wrap(err, "failed to configure token with jwt authentication") } // Else, if not using an access token, use oauth2 } else if boxAccessToken == "" || !boxAccessTokenOk { err = oauthutil.Config(ctx, "box", name, m, oauthConfig, nil) if err != nil { - log.Fatalf("Failed to configure token with oauth authentication: %v", err) + return errors.Wrap(err, "failed to configure token with oauth authentication") } } + return nil }, Options: append(oauthutil.SharedOptions, []fs.Option{{ Name: "root_folder_id", diff --git a/backend/cache/cache_internal_test.go b/backend/cache/cache_internal_test.go index d15a39866..4c54340d9 100644 --- a/backend/cache/cache_internal_test.go +++ b/backend/cache/cache_internal_test.go @@ -836,7 +836,7 @@ func newRun() *run { if uploadDir == "" { r.tmpUploadDir, err = ioutil.TempDir("", "rclonecache-tmp") if err != nil { - log.Fatalf("Failed to create temp dir: %v", err) + panic(fmt.Sprintf("Failed to create temp dir: %v", err)) } } else { r.tmpUploadDir = uploadDir diff --git a/backend/drive/drive.go b/backend/drive/drive.go index a396268f7..c4f1ea1c5 100755 --- a/backend/drive/drive.go +++ b/backend/drive/drive.go @@ -14,7 +14,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "mime" "net/http" "path" @@ -183,13 +182,12 @@ func init() { Description: "Google Drive", NewFs: NewFs, CommandHelp: commandHelp, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { // Parse config into Options struct opt := new(Options) err := configstruct.Set(m, opt) if err != nil { - fs.Errorf(nil, "Couldn't parse config into struct: %v", err) - return + return errors.Wrap(err, "couldn't parse config into struct") } // Fill in the scopes @@ -202,13 +200,14 @@ func init() { if opt.ServiceAccountFile == "" && opt.ServiceAccountCredentials == "" { err = oauthutil.Config(ctx, "drive", name, m, driveConfig, nil) if err != nil { - log.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } } err = configTeamDrive(ctx, opt, m, name) if err != nil { - log.Fatalf("Failed to configure Shared Drive: %v", err) + return errors.Wrap(err, "failed to configure Shared Drive") } + return nil }, Options: append(driveOAuthOptions(), []fs.Option{{ Name: "scope", @@ -522,7 +521,7 @@ If this flag is set then rclone will ignore shortcut files completely. } { for mimeType, extension := range m { if err := mime.AddExtensionType(extension, mimeType); err != nil { - log.Fatalf("Failed to register MIME type %q: %v", mimeType, err) + fs.Errorf("Failed to register MIME type %q: %v", mimeType, err) } } } diff --git a/backend/dropbox/dropbox.go b/backend/dropbox/dropbox.go index 3de65d26c..9e2dbd4a7 100755 --- a/backend/dropbox/dropbox.go +++ b/backend/dropbox/dropbox.go @@ -25,7 +25,6 @@ import ( "context" "fmt" "io" - "log" "path" "regexp" "strings" @@ -144,7 +143,7 @@ func init() { Name: "dropbox", Description: "Dropbox", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { opt := oauthutil.Options{ NoOffline: true, OAuth2Opts: []oauth2.AuthCodeOption{ @@ -153,8 +152,9 @@ func init() { } err := oauthutil.Config(ctx, "dropbox", name, m, getOauthConfig(m), &opt) if err != nil { - log.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } + return nil }, Options: append(oauthutil.SharedOptions, []fs.Option{{ Name: "chunk_size", diff --git a/backend/fichier/fichier.go b/backend/fichier/fichier.go index 5a1c2aea2..5a5800b90 100644 --- a/backend/fichier/fichier.go +++ b/backend/fichier/fichier.go @@ -35,9 +35,7 @@ func init() { fs.Register(&fs.RegInfo{ Name: "fichier", Description: "1Fichier", - Config: func(ctx context.Context, name string, config configmap.Mapper) { - }, - NewFs: NewFs, + NewFs: NewFs, Options: []fs.Option{{ Help: "Your API Key, get it from https://1fichier.com/console/params.pl", Name: "api_key", diff --git a/backend/googlecloudstorage/googlecloudstorage.go b/backend/googlecloudstorage/googlecloudstorage.go index 3566e3b42..3d312a02f 100644 --- a/backend/googlecloudstorage/googlecloudstorage.go +++ b/backend/googlecloudstorage/googlecloudstorage.go @@ -19,7 +19,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "net/http" "path" "strings" @@ -76,17 +75,18 @@ func init() { Prefix: "gcs", Description: "Google Cloud Storage (this is not Google Drive)", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { saFile, _ := m.Get("service_account_file") saCreds, _ := m.Get("service_account_credentials") anonymous, _ := m.Get("anonymous") if saFile != "" || saCreds != "" || anonymous == "true" { - return + return nil } err := oauthutil.Config(ctx, "google cloud storage", name, m, storageConfig, nil) if err != nil { - log.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } + return nil }, Options: append(oauthutil.SharedOptions, []fs.Option{{ Name: "project_number", diff --git a/backend/googlephotos/googlephotos.go b/backend/googlephotos/googlephotos.go index 35f58d09e..6d1aab403 100644 --- a/backend/googlephotos/googlephotos.go +++ b/backend/googlephotos/googlephotos.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" "io" - golog "log" "net/http" "net/url" "path" @@ -78,13 +77,12 @@ func init() { Prefix: "gphotos", Description: "Google Photos", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { // Parse config into Options struct opt := new(Options) err := configstruct.Set(m, opt) if err != nil { - fs.Errorf(nil, "Couldn't parse config into struct: %v", err) - return + return errors.Wrap(err, "couldn't parse config into struct") } // Fill in the scopes @@ -97,7 +95,7 @@ func init() { // Do the oauth err = oauthutil.Config(ctx, "google photos", name, m, oauthConfig, nil) if err != nil { - golog.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } // Warn the user @@ -108,6 +106,7 @@ func init() { `) + return nil }, Options: append(oauthutil.SharedOptions, []fs.Option{{ Name: "read_only", diff --git a/backend/hubic/hubic.go b/backend/hubic/hubic.go index 7c85fd778..7728905d8 100644 --- a/backend/hubic/hubic.go +++ b/backend/hubic/hubic.go @@ -11,7 +11,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "net/http" "strings" "time" @@ -56,11 +55,12 @@ func init() { Name: "hubic", Description: "Hubic", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { err := oauthutil.Config(ctx, "hubic", name, m, oauthConfig, nil) if err != nil { - log.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } + return nil }, Options: append(oauthutil.SharedOptions, swift.SharedOptions...), }) diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go index d970fdf04..527fd335f 100644 --- a/backend/jottacloud/jottacloud.go +++ b/backend/jottacloud/jottacloud.go @@ -10,7 +10,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "math/rand" "net/http" "net/url" @@ -87,12 +86,12 @@ func init() { Name: "jottacloud", Description: "Jottacloud", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { refresh := false if version, ok := m.Get("configVersion"); ok { ver, err := strconv.Atoi(version) if err != nil { - log.Fatalf("Failed to parse config version - corrupted config") + return errors.Wrap(err, "failed to parse config version - corrupted config") } refresh = (ver != configVersion) && (ver != v1configVersion) } @@ -104,7 +103,7 @@ func init() { if ok && tokenString != "" { fmt.Printf("Already have a token - refresh?\n") if !config.Confirm(false) { - return + return nil } } } @@ -116,11 +115,13 @@ func init() { switch config.ChooseNumber("Your choice", 1, 3) { case 1: - v2config(ctx, name, m) + return v2config(ctx, name, m) case 2: - v1config(ctx, name, m) + return v1config(ctx, name, m) case 3: - teliaCloudConfig(ctx, name, m) + return teliaCloudConfig(ctx, name, m) + default: + return errors.New("unknown config choice") } }, Options: []fs.Option{{ @@ -242,7 +243,7 @@ func shouldRetry(ctx context.Context, resp *http.Response, err error) (bool, err return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err } -func teliaCloudConfig(ctx context.Context, name string, m configmap.Mapper) { +func teliaCloudConfig(ctx context.Context, name string, m configmap.Mapper) error { teliaCloudOauthConfig := &oauth2.Config{ Endpoint: oauth2.Endpoint{ AuthURL: teliaCloudAuthURL, @@ -255,15 +256,14 @@ func teliaCloudConfig(ctx context.Context, name string, m configmap.Mapper) { err := oauthutil.Config(ctx, "jottacloud", name, m, teliaCloudOauthConfig, nil) if err != nil { - log.Fatalf("Failed to configure token: %v", err) - return + return errors.Wrap(err, "failed to configure token") } fmt.Printf("\nDo you want to use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?\n\n") if config.Confirm(false) { oAuthClient, _, err := oauthutil.NewClient(ctx, name, m, teliaCloudOauthConfig) if err != nil { - log.Fatalf("Failed to load oAuthClient: %s", err) + return errors.Wrap(err, "failed to load oAuthClient") } srv := rest.NewClient(oAuthClient).SetRoot(rootURL) @@ -271,7 +271,7 @@ func teliaCloudConfig(ctx context.Context, name string, m configmap.Mapper) { device, mountpoint, err := setupMountpoint(ctx, srv, apiSrv) if err != nil { - log.Fatalf("Failed to setup mountpoint: %s", err) + return errors.Wrap(err, "failed to setup mountpoint") } m.Set(configDevice, device) m.Set(configMountpoint, mountpoint) @@ -280,17 +280,18 @@ func teliaCloudConfig(ctx context.Context, name string, m configmap.Mapper) { m.Set("configVersion", strconv.Itoa(configVersion)) m.Set(configClientID, teliaCloudClientID) m.Set(configTokenURL, teliaCloudTokenURL) + return nil } // v1config configure a jottacloud backend using legacy authentication -func v1config(ctx context.Context, name string, m configmap.Mapper) { +func v1config(ctx context.Context, name string, m configmap.Mapper) error { srv := rest.NewClient(fshttp.NewClient(ctx)) fmt.Printf("\nDo you want to create a machine specific API key?\n\nRclone has it's own Jottacloud API KEY which works fine as long as one only uses rclone on a single machine. When you want to use rclone with this account on more than one machine it's recommended to create a machine specific API key. These keys can NOT be shared between machines.\n\n") if config.Confirm(false) { deviceRegistration, err := registerDevice(ctx, srv) if err != nil { - log.Fatalf("Failed to register device: %v", err) + return errors.Wrap(err, "failed to register device") } m.Set(configClientID, deviceRegistration.ClientID) @@ -318,18 +319,18 @@ func v1config(ctx context.Context, name string, m configmap.Mapper) { token, err := doAuthV1(ctx, srv, username, password) if err != nil { - log.Fatalf("Failed to get oauth token: %s", err) + return errors.Wrap(err, "failed to get oauth token") } err = oauthutil.PutToken(name, m, &token, true) if err != nil { - log.Fatalf("Error while saving token: %s", err) + return errors.Wrap(err, "error while saving token") } fmt.Printf("\nDo you want to use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?\n\n") if config.Confirm(false) { oAuthClient, _, err := oauthutil.NewClient(ctx, name, m, oauthConfig) if err != nil { - log.Fatalf("Failed to load oAuthClient: %s", err) + return errors.Wrap(err, "failed to load oAuthClient") } srv = rest.NewClient(oAuthClient).SetRoot(rootURL) @@ -337,13 +338,14 @@ func v1config(ctx context.Context, name string, m configmap.Mapper) { device, mountpoint, err := setupMountpoint(ctx, srv, apiSrv) if err != nil { - log.Fatalf("Failed to setup mountpoint: %s", err) + return errors.Wrap(err, "failed to setup mountpoint") } m.Set(configDevice, device) m.Set(configMountpoint, mountpoint) } m.Set("configVersion", strconv.Itoa(v1configVersion)) + return nil } // registerDevice register a new device for use with the jottacloud API @@ -418,7 +420,7 @@ func doAuthV1(ctx context.Context, srv *rest.Client, username, password string) } // v2config configure a jottacloud backend using the modern JottaCli token based authentication -func v2config(ctx context.Context, name string, m configmap.Mapper) { +func v2config(ctx context.Context, name string, m configmap.Mapper) error { srv := rest.NewClient(fshttp.NewClient(ctx)) fmt.Printf("Generate a personal login token here: https://www.jottacloud.com/web/secure\n") @@ -430,31 +432,32 @@ func v2config(ctx context.Context, name string, m configmap.Mapper) { token, err := doAuthV2(ctx, srv, loginToken, m) if err != nil { - log.Fatalf("Failed to get oauth token: %s", err) + return errors.Wrap(err, "failed to get oauth token") } err = oauthutil.PutToken(name, m, &token, true) if err != nil { - log.Fatalf("Error while saving token: %s", err) + return errors.Wrap(err, "error while saving token") } fmt.Printf("\nDo you want to use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?\n\n") if config.Confirm(false) { oAuthClient, _, err := oauthutil.NewClient(ctx, name, m, oauthConfig) if err != nil { - log.Fatalf("Failed to load oAuthClient: %s", err) + return errors.Wrap(err, "failed to load oAuthClient") } srv = rest.NewClient(oAuthClient).SetRoot(rootURL) apiSrv := rest.NewClient(oAuthClient).SetRoot(apiURL) device, mountpoint, err := setupMountpoint(ctx, srv, apiSrv) if err != nil { - log.Fatalf("Failed to setup mountpoint: %s", err) + return errors.Wrap(err, "failed to setup mountpoint") } m.Set(configDevice, device) m.Set(configMountpoint, mountpoint) } m.Set("configVersion", strconv.Itoa(configVersion)) + return nil } // doAuthV2 runs the actual token request for V2 authentication diff --git a/backend/mailru/api/helpers.go b/backend/mailru/api/helpers.go index ac8574411..2b4ebb3b5 100644 --- a/backend/mailru/api/helpers.go +++ b/backend/mailru/api/helpers.go @@ -6,8 +6,8 @@ import ( "bufio" "bytes" "encoding/binary" + "fmt" "io" - "log" "time" "github.com/pkg/errors" @@ -48,7 +48,7 @@ func (w *BinWriter) Reader() io.Reader { // WritePu16 writes a short as unsigned varint func (w *BinWriter) WritePu16(val int) { if val < 0 || val > 65535 { - log.Fatalf("Invalid UInt16 %v", val) + panic(fmt.Sprintf("Invalid UInt16 %v", val)) } w.WritePu64(int64(val)) } @@ -56,7 +56,7 @@ func (w *BinWriter) WritePu16(val int) { // WritePu32 writes a signed long as unsigned varint func (w *BinWriter) WritePu32(val int64) { if val < 0 || val > 4294967295 { - log.Fatalf("Invalid UInt32 %v", val) + panic(fmt.Sprintf("Invalid UInt32 %v", val)) } w.WritePu64(val) } @@ -64,7 +64,7 @@ func (w *BinWriter) WritePu32(val int64) { // WritePu64 writes an unsigned (actually, signed) long as unsigned varint func (w *BinWriter) WritePu64(val int64) { if val < 0 { - log.Fatalf("Invalid UInt64 %v", val) + panic(fmt.Sprintf("Invalid UInt64 %v", val)) } w.b.Write(w.a[:binary.PutUvarint(w.a, uint64(val))]) } @@ -123,7 +123,7 @@ func (r *BinReader) check(err error) bool { r.err = err } if err != io.EOF { - log.Fatalf("Error parsing response: %v", err) + panic(fmt.Sprintf("Error parsing response: %v", err)) } return false } diff --git a/backend/onedrive/onedrive.go b/backend/onedrive/onedrive.go index 5c5845ba4..325075f30 100755 --- a/backend/onedrive/onedrive.go +++ b/backend/onedrive/onedrive.go @@ -9,7 +9,6 @@ import ( "encoding/json" "fmt" "io" - "log" "net/http" "net/url" "path" @@ -99,7 +98,7 @@ func init() { Name: "onedrive", Description: "Microsoft OneDrive", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { region, _ := m.Get("region") graphURL := graphAPIEndpoint[region] + "/v1.0" oauthConfig.Endpoint = oauth2.Endpoint{ @@ -109,13 +108,12 @@ func init() { ci := fs.GetConfig(ctx) err := oauthutil.Config(ctx, "onedrive", name, m, oauthConfig, nil) if err != nil { - log.Fatalf("Failed to configure token: %v", err) - return + return errors.Wrap(err, "failed to configure token") } // Stop if we are running non-interactive config if ci.AutoConfirm { - return + return nil } type driveResource struct { @@ -138,7 +136,7 @@ func init() { oAuthClient, _, err := oauthutil.NewClient(ctx, name, m, oauthConfig) if err != nil { - log.Fatalf("Failed to configure OneDrive: %v", err) + return errors.Wrap(err, "failed to configure OneDrive") } srv := rest.NewClient(oAuthClient) @@ -203,18 +201,17 @@ func init() { sites := siteResponse{} _, err := srv.CallJSON(ctx, &opts, nil, &sites) if err != nil { - log.Fatalf("Failed to query available sites: %v", err) + return errors.Wrap(err, "failed to query available sites") } if len(sites.Sites) == 0 { - log.Fatalf("Search for '%s' returned no results", searchTerm) - } else { - fmt.Printf("Found %d sites, please select the one you want to use:\n", len(sites.Sites)) - for index, site := range sites.Sites { - fmt.Printf("%d: %s (%s) id=%s\n", index, site.SiteName, site.SiteURL, site.SiteID) - } - siteID = sites.Sites[config.ChooseNumber("Chose drive to use:", 0, len(sites.Sites)-1)].SiteID + return errors.Errorf("search for %q returned no results", searchTerm) } + fmt.Printf("Found %d sites, please select the one you want to use:\n", len(sites.Sites)) + for index, site := range sites.Sites { + fmt.Printf("%d: %s (%s) id=%s\n", index, site.SiteName, site.SiteURL, site.SiteID) + } + siteID = sites.Sites[config.ChooseNumber("Chose drive to use:", 0, len(sites.Sites)-1)].SiteID } // if we use server-relative URL for finding the drive @@ -227,7 +224,7 @@ func init() { site := siteResource{} _, err := srv.CallJSON(ctx, &opts, nil, &site) if err != nil { - log.Fatalf("Failed to query available site by relative path: %v", err) + return errors.Wrap(err, "failed to query available site by relative path") } siteID = site.SiteID } @@ -247,7 +244,7 @@ func init() { drives := drivesResponse{} _, err := srv.CallJSON(ctx, &opts, nil, &drives) if err != nil { - log.Fatalf("Failed to query available drives: %v", err) + return errors.Wrap(err, "failed to query available drives") } // Also call /me/drive as sometimes /me/drives doesn't return it #4068 @@ -256,7 +253,7 @@ func init() { meDrive := driveResource{} _, err := srv.CallJSON(ctx, &opts, nil, &meDrive) if err != nil { - log.Fatalf("Failed to query available drives: %v", err) + return errors.Wrap(err, "failed to query available drives") } found := false for _, drive := range drives.Drives { @@ -273,14 +270,13 @@ func init() { } if len(drives.Drives) == 0 { - log.Fatalf("No drives found") - } else { - fmt.Printf("Found %d drives, please select the one you want to use:\n", len(drives.Drives)) - for index, drive := range drives.Drives { - fmt.Printf("%d: %s (%s) id=%s\n", index, drive.DriveName, drive.DriveType, drive.DriveID) - } - finalDriveID = drives.Drives[config.ChooseNumber("Chose drive to use:", 0, len(drives.Drives)-1)].DriveID + return errors.New("no drives found") } + fmt.Printf("Found %d drives, please select the one you want to use:\n", len(drives.Drives)) + for index, drive := range drives.Drives { + fmt.Printf("%d: %s (%s) id=%s\n", index, drive.DriveName, drive.DriveType, drive.DriveID) + } + finalDriveID = drives.Drives[config.ChooseNumber("Chose drive to use:", 0, len(drives.Drives)-1)].DriveID } // Test the driveID and get drive type @@ -291,17 +287,18 @@ func init() { var rootItem api.Item _, err = srv.CallJSON(ctx, &opts, nil, &rootItem) if err != nil { - log.Fatalf("Failed to query root for drive %s: %v", finalDriveID, err) + return errors.Wrapf(err, "failed to query root for drive %s", finalDriveID) } fmt.Printf("Found drive '%s' of type '%s', URL: %s\nIs that okay?\n", rootItem.Name, rootItem.ParentReference.DriveType, rootItem.WebURL) // This does not work, YET :) if !config.ConfirmWithConfig(ctx, m, "config_drive_ok", true) { - log.Fatalf("Cancelled by user") + return errors.New("cancelled by user") } m.Set(configDriveID, finalDriveID) m.Set(configDriveType, rootItem.ParentReference.DriveType) + return nil }, Options: append(oauthutil.SharedOptions, []fs.Option{{ Name: "region", diff --git a/backend/pcloud/pcloud.go b/backend/pcloud/pcloud.go index 3cb0d3aed..45a4e56c8 100644 --- a/backend/pcloud/pcloud.go +++ b/backend/pcloud/pcloud.go @@ -12,7 +12,6 @@ import ( "context" "fmt" "io" - "log" "net/http" "net/url" "path" @@ -72,7 +71,7 @@ func init() { Name: "pcloud", Description: "Pcloud", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { optc := new(Options) err := configstruct.Set(m, optc) if err != nil { @@ -100,8 +99,9 @@ func init() { } err = oauthutil.Config(ctx, "pcloud", name, m, oauthConfig, &opt) if err != nil { - log.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } + return nil }, Options: append(oauthutil.SharedOptions, []fs.Option{{ Name: config.ConfigEncoding, diff --git a/backend/premiumizeme/premiumizeme.go b/backend/premiumizeme/premiumizeme.go index 76c512cec..895049e5b 100644 --- a/backend/premiumizeme/premiumizeme.go +++ b/backend/premiumizeme/premiumizeme.go @@ -20,7 +20,6 @@ import ( "encoding/json" "fmt" "io" - "log" "net" "net/http" "net/url" @@ -78,11 +77,12 @@ func init() { Name: "premiumizeme", Description: "premiumize.me", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { err := oauthutil.Config(ctx, "premiumizeme", name, m, oauthConfig, nil) if err != nil { - log.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } + return nil }, Options: []fs.Option{{ Name: "api_key", diff --git a/backend/putio/putio.go b/backend/putio/putio.go index 07a6f0236..20a64f224 100644 --- a/backend/putio/putio.go +++ b/backend/putio/putio.go @@ -2,10 +2,10 @@ package putio import ( "context" - "log" "regexp" "time" + "github.com/pkg/errors" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/config" "github.com/rclone/rclone/fs/config/configmap" @@ -60,14 +60,15 @@ func init() { Name: "putio", Description: "Put.io", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { opt := oauthutil.Options{ NoOffline: true, } err := oauthutil.Config(ctx, "putio", name, m, putioConfig, &opt) if err != nil { - log.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } + return nil }, Options: []fs.Option{{ Name: config.ConfigEncoding, diff --git a/backend/seafile/seafile.go b/backend/seafile/seafile.go index 784beb4d8..30a38a142 100644 --- a/backend/seafile/seafile.go +++ b/backend/seafile/seafile.go @@ -296,36 +296,32 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e } // Config callback for 2FA -func Config(ctx context.Context, name string, m configmap.Mapper) { +func Config(ctx context.Context, name string, m configmap.Mapper) error { ci := fs.GetConfig(ctx) serverURL, ok := m.Get(configURL) if !ok || serverURL == "" { // If there's no server URL, it means we're trying an operation at the backend level, like a "rclone authorize seafile" - fmt.Print("\nOperation not supported on this remote.\nIf you need a 2FA code on your account, use the command:\n\nrclone config reconnect :\n\n") - return + return errors.New("operation not supported on this remote. If you need a 2FA code on your account, use the command: nrclone config reconnect : ") } // Stop if we are running non-interactive config if ci.AutoConfirm { - return + return nil } u, err := url.Parse(serverURL) if err != nil { - fs.Errorf(nil, "Invalid server URL %s", serverURL) - return + return errors.Errorf("invalid server URL %s", serverURL) } is2faEnabled, _ := m.Get(config2FA) if is2faEnabled != "true" { - fmt.Println("Two-factor authentication is not enabled on this account.") - return + return errors.New("two-factor authentication is not enabled on this account") } username, _ := m.Get(configUser) if username == "" { - fs.Errorf(nil, "A username is required") - return + return errors.New("a username is required") } password, _ := m.Get(configPassword) @@ -376,6 +372,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper) { break } } + return nil } // sets the AuthorizationToken up diff --git a/backend/sharefile/sharefile.go b/backend/sharefile/sharefile.go index fb33c4c95..265d5e6d2 100644 --- a/backend/sharefile/sharefile.go +++ b/backend/sharefile/sharefile.go @@ -77,7 +77,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "net/http" "net/url" "path" @@ -136,7 +135,7 @@ func init() { Name: "sharefile", Description: "Citrix Sharefile", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { oauthConfig := newOauthConfig("") checkAuth := func(oauthConfig *oauth2.Config, auth *oauthutil.AuthResult) error { if auth == nil || auth.Form == nil { @@ -157,8 +156,9 @@ func init() { } err := oauthutil.Config(ctx, "sharefile", name, m, oauthConfig, &opt) if err != nil { - log.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } + return nil }, Options: []fs.Option{{ Name: "upload_cutoff", diff --git a/backend/sugarsync/sugarsync.go b/backend/sugarsync/sugarsync.go index aba130eb9..82f0c6a9e 100644 --- a/backend/sugarsync/sugarsync.go +++ b/backend/sugarsync/sugarsync.go @@ -16,7 +16,6 @@ import ( "context" "fmt" "io" - "log" "net/http" "net/url" "path" @@ -76,17 +75,17 @@ func init() { Name: "sugarsync", Description: "Sugarsync", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { opt := new(Options) err := configstruct.Set(m, opt) if err != nil { - log.Fatalf("Failed to read options: %v", err) + return errors.Wrap(err, "failed to read options") } if opt.RefreshToken != "" { fmt.Printf("Already have a token - refresh?\n") if !config.ConfirmWithConfig(ctx, m, "config_refresh_token", true) { - return + return nil } } fmt.Printf("Username (email address)> ") @@ -114,10 +113,11 @@ func init() { // return shouldRetry(ctx, resp, err) //}) if err != nil { - log.Fatalf("Failed to get token: %v", err) + return errors.Wrap(err, "failed to get token") } opt.RefreshToken = resp.Header.Get("Location") m.Set("refresh_token", opt.RefreshToken) + return nil }, Options: []fs.Option{{ Name: "app_id", diff --git a/backend/tardigrade/fs.go b/backend/tardigrade/fs.go index 490302866..3d6ead1e2 100644 --- a/backend/tardigrade/fs.go +++ b/backend/tardigrade/fs.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "io" - "log" "path" "strings" "time" @@ -42,7 +41,7 @@ func init() { Name: "tardigrade", Description: "Tardigrade Decentralized Cloud Storage", NewFs: NewFs, - Config: func(ctx context.Context, name string, configMapper configmap.Mapper) { + Config: func(ctx context.Context, name string, configMapper configmap.Mapper) error { provider, _ := configMapper.Get(fs.ConfigProvider) config.FileDeleteKey(name, fs.ConfigProvider) @@ -54,7 +53,7 @@ func init() { // satelliteString contains always default and passphrase can be empty if apiKey == "" { - return + return nil } satellite, found := satMap[satelliteString] @@ -64,12 +63,12 @@ func init() { access, err := uplink.RequestAccessWithPassphrase(context.TODO(), satellite, apiKey, passphrase) if err != nil { - log.Fatalf("Couldn't create access grant: %v", err) + return errors.Wrap(err, "couldn't create access grant") } serializedAccess, err := access.Serialize() if err != nil { - log.Fatalf("Couldn't serialize access grant: %v", err) + return errors.Wrap(err, "couldn't serialize access grant") } configMapper.Set("satellite_address", satellite) configMapper.Set("access_grant", serializedAccess) @@ -78,8 +77,9 @@ func init() { config.FileDeleteKey(name, "api_key") config.FileDeleteKey(name, "passphrase") } else { - log.Fatalf("Invalid provider type: %s", provider) + return errors.Errorf("invalid provider type: %s", provider) } + return nil }, Options: []fs.Option{ { diff --git a/backend/uptobox/uptobox.go b/backend/uptobox/uptobox.go index 8bf6ae520..9006cf039 100644 --- a/backend/uptobox/uptobox.go +++ b/backend/uptobox/uptobox.go @@ -41,9 +41,7 @@ func init() { fs.Register(&fs.RegInfo{ Name: "uptobox", Description: "Uptobox", - Config: func(ctx context.Context, name string, config configmap.Mapper) { - }, - NewFs: NewFs, + NewFs: NewFs, Options: []fs.Option{{ Help: "Your access Token, get it from https://uptobox.com/my_account", Name: "access_token", diff --git a/backend/yandex/yandex.go b/backend/yandex/yandex.go index 3c850932e..da85b38f6 100644 --- a/backend/yandex/yandex.go +++ b/backend/yandex/yandex.go @@ -60,12 +60,12 @@ func init() { Name: "yandex", Description: "Yandex Disk", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { err := oauthutil.Config(ctx, "yandex", name, m, oauthConfig, nil) if err != nil { - log.Fatalf("Failed to configure token: %v", err) - return + return errors.Wrap(err, "failed to configure token") } + return nil }, Options: append(oauthutil.SharedOptions, []fs.Option{{ Name: config.ConfigEncoding, @@ -251,22 +251,22 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e token, err := oauthutil.GetToken(name, m) if err != nil { - log.Fatalf("Couldn't read OAuth token (this should never happen).") + return nil, errors.Wrap(err, "couldn't read OAuth token") } if token.RefreshToken == "" { - log.Fatalf("Unable to get RefreshToken. If you are upgrading from older versions of rclone, please run `rclone config` and re-configure this backend.") + return nil, errors.New("unable to get RefreshToken. If you are upgrading from older versions of rclone, please run `rclone config` and re-configure this backend") } if token.TokenType != "OAuth" { token.TokenType = "OAuth" err = oauthutil.PutToken(name, m, token, false) if err != nil { - log.Fatalf("Couldn't save OAuth token (this should never happen).") + return nil, errors.Wrap(err, "couldn't save OAuth token") } log.Printf("Automatically upgraded OAuth config.") } oAuthClient, _, err := oauthutil.NewClient(ctx, name, m, oauthConfig) if err != nil { - log.Fatalf("Failed to configure Yandex: %v", err) + return nil, errors.Wrap(err, "failed to configure Yandex") } ci := fs.GetConfig(ctx) diff --git a/backend/zoho/zoho.go b/backend/zoho/zoho.go index b585e9ae6..172f0eb4f 100644 --- a/backend/zoho/zoho.go +++ b/backend/zoho/zoho.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "net/http" "net/url" "path" @@ -73,37 +72,41 @@ func init() { Name: "zoho", Description: "Zoho", NewFs: NewFs, - Config: func(ctx context.Context, name string, m configmap.Mapper) { + Config: func(ctx context.Context, name string, m configmap.Mapper) error { // Need to setup region before configuring oauth - setupRegion(m) + err := setupRegion(m) + if err != nil { + return err + } opt := oauthutil.Options{ // No refresh token unless ApprovalForce is set OAuth2Opts: []oauth2.AuthCodeOption{oauth2.ApprovalForce}, } if err := oauthutil.Config(ctx, "zoho", name, m, oauthConfig, &opt); err != nil { - log.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } // We need to rewrite the token type to "Zoho-oauthtoken" because Zoho wants // it's own custom type token, err := oauthutil.GetToken(name, m) if err != nil { - log.Fatalf("Failed to read token: %v", err) + return errors.Wrap(err, "failed to read token") } if token.TokenType != "Zoho-oauthtoken" { token.TokenType = "Zoho-oauthtoken" err = oauthutil.PutToken(name, m, token, false) if err != nil { - log.Fatalf("Failed to configure token: %v", err) + return errors.Wrap(err, "failed to configure token") } } if fs.GetConfig(ctx).AutoConfirm { - return + return nil } if err = setupRoot(ctx, name, m); err != nil { - log.Fatalf("Failed to configure root directory: %v", err) + return errors.Wrap(err, "failed to configure root directory") } + return nil }, Options: append(oauthutil.SharedOptions, []fs.Option{{ Name: "region", @@ -164,15 +167,16 @@ type Object struct { // ------------------------------------------------------------ -func setupRegion(m configmap.Mapper) { +func setupRegion(m configmap.Mapper) error { region, ok := m.Get("region") if !ok || region == "" { - log.Fatalf("No region set\n") + return errors.New("no region set") } rootURL = fmt.Sprintf("https://workdrive.zoho.%s/api/v1", region) accountsURL = fmt.Sprintf("https://accounts.zoho.%s", region) oauthConfig.Endpoint.AuthURL = fmt.Sprintf("https://accounts.zoho.%s/oauth/v2/auth", region) oauthConfig.Endpoint.TokenURL = fmt.Sprintf("https://accounts.zoho.%s/oauth/v2/token", region) + return nil } // ------------------------------------------------------------ @@ -208,7 +212,7 @@ func listWorkspaces(ctx context.Context, teamID string, srv *rest.Client) ([]api func setupRoot(ctx context.Context, name string, m configmap.Mapper) error { oAuthClient, _, err := oauthutil.NewClient(ctx, name, m, oauthConfig) if err != nil { - log.Fatalf("Failed to load oAuthClient: %s", err) + return errors.Wrap(err, "failed to load oAuthClient") } authSrv := rest.NewClient(oAuthClient).SetRoot(accountsURL) opts := rest.Opts{ @@ -377,7 +381,10 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e if err := configstruct.Set(m, opt); err != nil { return nil, err } - setupRegion(m) + err := setupRegion(m) + if err != nil { + return nil, err + } root = parsePath(root) oAuthClient, _, err := oauthutil.NewClient(ctx, name, m, oauthConfig) diff --git a/cmd/config/config.go b/cmd/config/config.go index 7e6f3fc23..4d61efd6e 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -42,9 +42,9 @@ var configCommand = &cobra.Command{ remotes and manage existing ones. You may also set or remove a password to protect your configuration. `, - Run: func(command *cobra.Command, args []string) { + RunE: func(command *cobra.Command, args []string) error { cmd.CheckArgs(0, 0, command, args) - config.EditConfig(context.Background()) + return config.EditConfig(context.Background()) }, } @@ -272,8 +272,7 @@ This normally means going through the interactive oauth flow again. if fsInfo.Config == nil { return errors.Errorf("%s: doesn't support Reconnect", configName) } - fsInfo.Config(ctx, configName, config) - return nil + return fsInfo.Config(ctx, configName, config) }, } diff --git a/fs/config/authorize.go b/fs/config/authorize.go index fc1a291bb..f37b4b7d8 100644 --- a/fs/config/authorize.go +++ b/fs/config/authorize.go @@ -60,7 +60,10 @@ func Authorize(ctx context.Context, args []string, noAutoBrowser bool) error { m.AddSetter(outM) m.AddGetter(outM, configmap.PriorityNormal) - ri.Config(ctx, name, m) + err = ri.Config(ctx, name, m) + if err != nil { + return err + } // Print the code for the user to paste out := outM["token"] diff --git a/fs/config/config.go b/fs/config/config.go index e0ac1aba0..b7c1cce53 100644 --- a/fs/config/config.go +++ b/fs/config/config.go @@ -455,7 +455,10 @@ func UpdateRemote(ctx context.Context, name string, keyValues rc.Params, doObscu } LoadedData().SetValue(name, k, vStr) } - RemoteConfig(ctx, name) + err = RemoteConfig(ctx, name) + if err != nil { + return err + } SaveConfig() cache.ClearConfig(name) // remove any remotes based on this config from the cache return nil diff --git a/fs/config/ui.go b/fs/config/ui.go index e3874852e..6d660cdc1 100644 --- a/fs/config/ui.go +++ b/fs/config/ui.go @@ -270,13 +270,14 @@ func OkRemote(name string) bool { } // RemoteConfig runs the config helper for the remote if needed -func RemoteConfig(ctx context.Context, name string) { +func RemoteConfig(ctx context.Context, name string) error { fmt.Printf("Remote config\n") f := mustFindByName(name) if f.Config != nil { m := fs.ConfigMap(f, name, nil) - f.Config(ctx, name, m) + return f.Config(ctx, name, m) } + return nil } // matchProvider returns true if provider matches the providerConfig string. @@ -456,7 +457,7 @@ func editOptions(ri *fs.RegInfo, name string, isNew bool) { } // NewRemote make a new remote from its name -func NewRemote(ctx context.Context, name string) { +func NewRemote(ctx context.Context, name string) error { var ( newType string ri *fs.RegInfo @@ -476,16 +477,19 @@ func NewRemote(ctx context.Context, name string) { LoadedData().SetValue(name, "type", newType) editOptions(ri, name, true) - RemoteConfig(ctx, name) + err = RemoteConfig(ctx, name) + if err != nil { + return err + } if OkRemote(name) { SaveConfig() - return + return nil } - EditRemote(ctx, ri, name) + return EditRemote(ctx, ri, name) } // EditRemote gets the user to edit a remote -func EditRemote(ctx context.Context, ri *fs.RegInfo, name string) { +func EditRemote(ctx context.Context, ri *fs.RegInfo, name string) error { ShowRemote(name) fmt.Printf("Edit remote\n") for { @@ -495,7 +499,7 @@ func EditRemote(ctx context.Context, ri *fs.RegInfo, name string) { } } SaveConfig() - RemoteConfig(ctx, name) + return RemoteConfig(ctx, name) } // DeleteRemote gets the user to delete a remote @@ -560,7 +564,7 @@ func ShowConfig() { } // EditConfig edits the config file interactively -func EditConfig(ctx context.Context) { +func EditConfig(ctx context.Context) (err error) { for { haveRemotes := len(LoadedData().GetSectionList()) != 0 what := []string{"eEdit existing remote", "nNew remote", "dDelete remote", "rRename remote", "cCopy remote", "sSet configuration password", "qQuit config"} @@ -577,9 +581,15 @@ func EditConfig(ctx context.Context) { case 'e': name := ChooseRemote() fs := mustFindByName(name) - EditRemote(ctx, fs, name) + err = EditRemote(ctx, fs, name) + if err != nil { + return err + } case 'n': - NewRemote(ctx, NewRemoteName()) + err = NewRemote(ctx, NewRemoteName()) + if err != nil { + return err + } case 'd': name := ChooseRemote() DeleteRemote(name) @@ -590,8 +600,7 @@ func EditConfig(ctx context.Context) { case 's': SetPassword() case 'q': - return - + return nil } } } diff --git a/fs/config/ui_test.go b/fs/config/ui_test.go index a01761c07..69e66e666 100644 --- a/fs/config/ui_test.go +++ b/fs/config/ui_test.go @@ -103,7 +103,7 @@ func TestCRUD(t *testing.T) { "secret", // repeat "y", // looks good, save }) - config.NewRemote(ctx, "test") + require.NoError(t, config.NewRemote(ctx, "test")) assert.Equal(t, []string{"test"}, config.Data().GetSectionList()) assert.Equal(t, "config_test_remote", config.FileGet("test", "type")) @@ -146,7 +146,7 @@ func TestChooseOption(t *testing.T) { assert.Equal(t, 1024, bits) return "not very random password", nil } - config.NewRemote(ctx, "test") + require.NoError(t, config.NewRemote(ctx, "test")) assert.Equal(t, "false", config.FileGet("test", "bool")) assert.Equal(t, "not very random password", obscure.MustReveal(config.FileGet("test", "pass"))) @@ -158,7 +158,7 @@ func TestChooseOption(t *testing.T) { "n", // not required "y", // looks good, save }) - config.NewRemote(ctx, "test") + require.NoError(t, config.NewRemote(ctx, "test")) assert.Equal(t, "true", config.FileGet("test", "bool")) assert.Equal(t, "", config.FileGet("test", "pass")) @@ -175,7 +175,7 @@ func TestNewRemoteName(t *testing.T) { "n", // not required "y", // looks good, save }) - config.NewRemote(ctx, "test") + require.NoError(t, config.NewRemote(ctx, "test")) config.ReadLine = makeReadLine([]string{ "test", // already exists diff --git a/fs/fs.go b/fs/fs.go index a114cf9e2..e4c6dc17d 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -89,7 +89,7 @@ type RegInfo struct { // the parent of that object and ErrorIsFile. NewFs func(ctx context.Context, name string, root string, config configmap.Mapper) (Fs, error) `json:"-"` // Function to call to help with config - Config func(ctx context.Context, name string, config configmap.Mapper) `json:"-"` + Config func(ctx context.Context, name string, config configmap.Mapper) error `json:"-"` // Options for the Fs configuration Options Options // The command help, if any