config: when using auto confirm make user interaction configurable

* drive: don't run teamdrive config if auto confirm set
* onedrive: don't run extra config if auto confirm set
* make Confirm results customisable by config

Fixes #1010
This commit is contained in:
Nick Craig-Wood 2019-01-17 15:01:13 +00:00
parent 8e107b9657
commit a30e80564d
5 changed files with 74 additions and 39 deletions

View File

@ -718,12 +718,16 @@ func parseExtensions(extensionsIn ...string) (extensions, mimeTypes []string, er
// Figure out if the user wants to use a team drive // Figure out if the user wants to use a team drive
func configTeamDrive(opt *Options, m configmap.Mapper, name string) error { func configTeamDrive(opt *Options, m configmap.Mapper, name string) error {
// Stop if we are running non-interactive config
if fs.Config.AutoConfirm {
return nil
}
if opt.TeamDriveID == "" { if opt.TeamDriveID == "" {
fmt.Printf("Configure this as a team drive?\n") fmt.Printf("Configure this as a team drive?\n")
} else { } else {
fmt.Printf("Change current team drive ID %q?\n", opt.TeamDriveID) fmt.Printf("Change current team drive ID %q?\n", opt.TeamDriveID)
} }
if !config.ConfirmWithDefault(false) { if !config.Confirm() {
return nil return nil
} }
client, err := createOAuthClient(opt, name, m) client, err := createOAuthClient(opt, name, m)

View File

@ -75,9 +75,8 @@ func init() {
return return
} }
// Are we running headless? // Stop if we are running non-interactive config
if automatic, _ := m.Get(config.ConfigAutomatic); automatic != "" { if fs.Config.AutoConfirm {
// Yes, okay we are done
return return
} }
@ -199,7 +198,7 @@ func init() {
fmt.Printf("Found drive '%s' of type '%s', URL: %s\nIs that okay?\n", rootItem.Name, rootItem.ParentReference.DriveType, rootItem.WebURL) 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 :) // This does not work, YET :)
if !config.Confirm() { if !config.ConfirmWithConfig(m, "config_drive_ok", true) {
log.Fatalf("Cancelled by user") log.Fatalf("Cancelled by user")
} }

View File

@ -93,6 +93,15 @@ For example to make a swift remote of name myremote using auto config
you would do: you would do:
rclone config create myremote swift env_auth true rclone config create myremote swift env_auth true
Note that if the config process would normally ask a question the
default is taken. Each time that happens rclone will print a message
saying how to affect the value taken.
So for example if you wanted to configure a Google Drive remote but
using remote authorization you would do this:
rclone config create mydrive drive config_is_local false
`, `,
RunE: func(command *cobra.Command, args []string) error { RunE: func(command *cobra.Command, args []string) error {
cmd.CheckArgs(2, 256, command, args) cmd.CheckArgs(2, 256, command, args)
@ -119,6 +128,11 @@ in pairs of <key> <value>.
For example to update the env_auth field of a remote of name myremote you would do: For example to update the env_auth field of a remote of name myremote you would do:
rclone config update myremote swift env_auth true rclone config update myremote swift env_auth true
If the remote uses oauth the token will be updated, if you don't
require this add an extra parameter thus:
rclone config update myremote swift env_auth true config_refresh_token false
`, `,
RunE: func(command *cobra.Command, args []string) error { RunE: func(command *cobra.Command, args []string) error {
cmd.CheckArgs(3, 256, command, args) cmd.CheckArgs(3, 256, command, args)

View File

@ -27,6 +27,7 @@ import (
"github.com/Unknwon/goconfig" "github.com/Unknwon/goconfig"
"github.com/ncw/rclone/fs" "github.com/ncw/rclone/fs"
"github.com/ncw/rclone/fs/accounting" "github.com/ncw/rclone/fs/accounting"
"github.com/ncw/rclone/fs/config/configmap"
"github.com/ncw/rclone/fs/config/configstruct" "github.com/ncw/rclone/fs/config/configstruct"
"github.com/ncw/rclone/fs/config/obscure" "github.com/ncw/rclone/fs/config/obscure"
"github.com/ncw/rclone/fs/driveletter" "github.com/ncw/rclone/fs/driveletter"
@ -57,8 +58,8 @@ const (
// ConfigTokenURL is the config key used to store the token server endpoint // ConfigTokenURL is the config key used to store the token server endpoint
ConfigTokenURL = "token_url" ConfigTokenURL = "token_url"
// ConfigAutomatic indicates that we want non-interactive configuration // ConfigAuthorize indicates that we just want "rclone authorize"
ConfigAutomatic = "config_automatic" ConfigAuthorize = "config_authorize"
) )
// Global // Global
@ -639,21 +640,38 @@ func Command(commands []string) byte {
} }
} }
// ConfirmWithDefault asks the user for Yes or No and returns true or false.
//
// If AutoConfirm is set, it will return the Default value passed in
func ConfirmWithDefault(Default bool) bool {
if fs.Config.AutoConfirm {
return Default
}
return Command([]string{"yYes", "nNo"}) == 'y'
}
// Confirm asks the user for Yes or No and returns true or false // Confirm asks the user for Yes or No and returns true or false
// //
// If AutoConfirm is set, it will return true // If AutoConfirm is set, it will return true
func Confirm() bool { func Confirm() bool {
return ConfirmWithDefault(true) return Command([]string{"yYes", "nNo"}) == 'y'
}
// ConfirmWithConfig asks the user for Yes or No and returns true or
// false.
//
// If AutoConfirm is set, it will look up the value in m and return
// that, but if it isn't set then it will return the Default value
// passed in
func ConfirmWithConfig(m configmap.Getter, configName string, Default bool) bool {
if fs.Config.AutoConfirm {
configString, ok := m.Get(configName)
if ok {
configValue, err := strconv.ParseBool(configString)
if err != nil {
fs.Errorf(nil, "Failed to parse config parameter %s=%q as boolean - using default %v: %v", configName, configString, Default, err)
} else {
Default = configValue
}
}
answer := "No"
if Default {
answer = "Yes"
}
fmt.Printf("Auto confirm is set: answering %s, override by setting config parameter %s=%v\n", answer, configName, !Default)
return Default
}
return Confirm()
} }
// Choose one of the defaults or type a new string if newOk is set // Choose one of the defaults or type a new string if newOk is set
@ -943,8 +961,6 @@ func CreateRemote(name string, provider string, keyValues rc.Params) error {
getConfigData().DeleteSection(name) getConfigData().DeleteSection(name)
// Set the type // Set the type
getConfigData().SetValue(name, "type", provider) getConfigData().SetValue(name, "type", provider)
// Show this is automatically configured
getConfigData().SetValue(name, ConfigAutomatic, "yes")
// Set the remaining values // Set the remaining values
return UpdateRemote(name, keyValues) return UpdateRemote(name, keyValues)
} }
@ -1227,6 +1243,7 @@ func SetPassword() {
// rclone authorize "fs name" // rclone authorize "fs name"
// rclone authorize "fs name" "client id" "client secret" // rclone authorize "fs name" "client id" "client secret"
func Authorize(args []string) { func Authorize(args []string) {
defer suppressConfirm()()
switch len(args) { switch len(args) {
case 1, 3: case 1, 3:
default: default:
@ -1243,8 +1260,8 @@ func Authorize(args []string) {
// Make sure we delete it // Make sure we delete it
defer DeleteRemote(name) defer DeleteRemote(name)
// Indicate that we want fully automatic configuration. // Indicate that we are running rclone authorize
getConfigData().SetValue(name, ConfigAutomatic, "yes") getConfigData().SetValue(name, ConfigAuthorize, "true")
if len(args) == 3 { if len(args) == 3 {
getConfigData().SetValue(name, ConfigClientID, args[1]) getConfigData().SetValue(name, ConfigClientID, args[1])
getConfigData().SetValue(name, ConfigClientSecret, args[2]) getConfigData().SetValue(name, ConfigClientSecret, args[2])

View File

@ -358,18 +358,26 @@ func ConfigErrorCheck(id, name string, m configmap.Mapper, errorHandler func(*ht
func doConfig(id, name string, m configmap.Mapper, errorHandler func(*http.Request) AuthError, oauthConfig *oauth2.Config, offline bool, opts []oauth2.AuthCodeOption) error { func doConfig(id, name string, m configmap.Mapper, errorHandler func(*http.Request) AuthError, oauthConfig *oauth2.Config, offline bool, opts []oauth2.AuthCodeOption) error {
oauthConfig, changed := overrideCredentials(name, m, oauthConfig) oauthConfig, changed := overrideCredentials(name, m, oauthConfig)
auto, ok := m.Get(config.ConfigAutomatic) authorizeOnlyValue, ok := m.Get(config.ConfigAuthorize)
automatic := ok && auto != "" authorizeOnly := ok && authorizeOnlyValue != "" // set if being run by "rclone authorize"
// See if already have a token // See if already have a token
tokenString, ok := m.Get("token") tokenString, ok := m.Get("token")
if ok && tokenString != "" { if ok && tokenString != "" {
fmt.Printf("Already have a token - refresh?\n") fmt.Printf("Already have a token - refresh?\n")
if !config.Confirm() { if !config.ConfirmWithConfig(m, "config_refresh_token", true) {
return nil return nil
} }
} }
// Ask the user whether they are using a local machine
isLocal := func() bool {
fmt.Printf("Use auto config?\n")
fmt.Printf(" * Say Y if not sure\n")
fmt.Printf(" * Say N if you are working on a remote or headless machine\n")
return config.ConfirmWithConfig(m, "config_is_local", true)
}
// Detect whether we should use internal web server // Detect whether we should use internal web server
useWebServer := false useWebServer := false
switch oauthConfig.RedirectURL { switch oauthConfig.RedirectURL {
@ -378,14 +386,10 @@ func doConfig(id, name string, m configmap.Mapper, errorHandler func(*http.Reque
fmt.Printf("Make sure your Redirect URL is set to %q in your custom config.\n", oauthConfig.RedirectURL) fmt.Printf("Make sure your Redirect URL is set to %q in your custom config.\n", oauthConfig.RedirectURL)
} }
useWebServer = true useWebServer = true
if automatic { if authorizeOnly {
break break
} }
fmt.Printf("Use auto config?\n") if !isLocal() {
fmt.Printf(" * Say Y if not sure\n")
fmt.Printf(" * Say N if you are working on a remote or headless machine\n")
auto := config.Confirm()
if !auto {
fmt.Printf("For this to work, you will need rclone available on a machine that has a web browser available.\n") fmt.Printf("For this to work, you will need rclone available on a machine that has a web browser available.\n")
fmt.Printf("Execute the following on your machine:\n") fmt.Printf("Execute the following on your machine:\n")
if changed { if changed {
@ -407,12 +411,9 @@ func doConfig(id, name string, m configmap.Mapper, errorHandler func(*http.Reque
return PutToken(name, m, token, true) return PutToken(name, m, token, true)
} }
case TitleBarRedirectURL: case TitleBarRedirectURL:
useWebServer = automatic useWebServer = authorizeOnly
if !automatic { if !authorizeOnly {
fmt.Printf("Use auto config?\n") useWebServer = isLocal()
fmt.Printf(" * Say Y if not sure\n")
fmt.Printf(" * Say N if you are working on a remote or headless machine or Y didn't work\n")
useWebServer = config.Confirm()
} }
if useWebServer { if useWebServer {
// copy the config and set to use the internal webserver // copy the config and set to use the internal webserver
@ -479,12 +480,12 @@ func doConfig(id, name string, m configmap.Mapper, errorHandler func(*http.Reque
} }
// Print code if we do automatic retrieval // Print code if we do automatic retrieval
if automatic { if authorizeOnly {
result, err := json.Marshal(token) result, err := json.Marshal(token)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal token") return errors.Wrap(err, "failed to marshal token")
} }
fmt.Printf("Paste the following into your remote machine --->\n%s\n<---End paste", result) fmt.Printf("Paste the following into your remote machine --->\n%s\n<---End paste\n", result)
} }
return PutToken(name, m, token, true) return PutToken(name, m, token, true)
} }