diff --git a/cmd/config/config.go b/cmd/config/config.go index f7ad25ed4..c1fffad3d 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -1,65 +1,123 @@ package config import ( - "fmt" - "os" - "github.com/ncw/rclone/cmd" "github.com/ncw/rclone/fs" "github.com/spf13/cobra" ) func init() { - cmd.Root.AddCommand(commandDefintion) + cmd.Root.AddCommand(configCommand) + configCommand.AddCommand(configEditCommand) + configCommand.AddCommand(configFileCommand) + configCommand.AddCommand(configShowCommand) + configCommand.AddCommand(configDumpCommand) + configCommand.AddCommand(configProvidersCommand) + configCommand.AddCommand(configCreateCommand) + configCommand.AddCommand(configUpdateCommand) + configCommand.AddCommand(configDeleteCommand) } -var commandDefintion = &cobra.Command{ - Use: "config [function]", +var configCommand = &cobra.Command{ + Use: "config", Short: `Enter an interactive configuration session.`, - Long: "`rclone config`" + ` - enters an interactive configuration sessions where you can setup -new remotes and manage existing ones. You may also set or remove a password to -protect your configuration. - -Additional functions: - - * ` + "`rclone config edit`" + ` – same as above - * ` + "`rclone config file`" + ` – show path of configuration file in use - * ` + "`rclone config show`" + ` – print (decrypted) config file - * ` + "`rclone config listproviders`" + ` – List, in json format, the protocols supported by sync - * ` + "`rclone config listoptions type`" + ` – Lists all the options needed to connect to a protocol - * ` + "`rclone config jsonconfig`name type jsonoptions" + ` – Created a new remote type X with parameters Y + Long: `Enter an interactive configuration session where you can setup new +remotes and manage existing ones. You may also set or remove a +password to protect your configuration. `, Run: func(command *cobra.Command, args []string) { - cmd.CheckArgs(0, 4, command, args) - if len(args) == 0 { - fs.EditConfig() - } else if (len(args) == 1) { - switch args[0] { - case "edit": - fs.EditConfig() - case "show": - fs.ShowConfig() - case "file": - fs.ShowConfigLocation() - case "listproviders": - fs.ListProviders() - default: - fmt.Fprintf(os.Stderr, "Unknown subcommand %q, %s only supports edit, show and file.\n", args[0], command.Name()) - } - } else if (len(args) == 2) { - if ((args[0] == "listoptions") && (args[1] != "")) { - fs.ListOptions(args[1]) - } else { - fmt.Fprintf(os.Stderr, "Unknown subcommand %q %q, %s only supports optionsprovider .\n", args[0], args[1], command.Name()) - } - } else if (len(args) == 4) { - if ((args[0] == "jsonconfig") && (args[1] != "") && (args[2] != "") && (args[3]!= "")) { - fs.JsonConfig(args[1], args[2], args[3]) - } else { - fmt.Fprintf(os.Stderr, "Unknown subcommand %q %q %q %q, %s only supports jsonconfig .\n", args[0], args[1], args[2], args[3], command.Name()) - } - } - return + cmd.CheckArgs(0, 0, command, args) + fs.EditConfig() + }, +} + +var configEditCommand = &cobra.Command{ + Use: "edit", + Short: configCommand.Short, + Long: configCommand.Long, + Run: configCommand.Run, +} + +var configFileCommand = &cobra.Command{ + Use: "file", + Short: `Show path of configuration file in use.`, + Run: func(command *cobra.Command, args []string) { + cmd.CheckArgs(0, 0, command, args) + fs.ShowConfigLocation() + }, +} + +var configShowCommand = &cobra.Command{ + Use: "show []", + Short: `Print (decrypted) config file, or the config for a single remote.`, + Run: func(command *cobra.Command, args []string) { + cmd.CheckArgs(0, 1, command, args) + if len(args) == 0 { + fs.ShowConfig() + } else { + fs.ShowRemote(args[0]) + } + }, +} + +var configDumpCommand = &cobra.Command{ + Use: "dump", + Short: `Dump the config file as JSON.`, + RunE: func(command *cobra.Command, args []string) error { + cmd.CheckArgs(0, 0, command, args) + return fs.ConfigDump() + }, +} + +var configProvidersCommand = &cobra.Command{ + Use: "providers", + Short: `List in JSON format all the providers and options.`, + RunE: func(command *cobra.Command, args []string) error { + cmd.CheckArgs(0, 0, command, args) + return fs.JSONListProviders() + }, +} + +var configCreateCommand = &cobra.Command{ + Use: "create [ ]*", + Short: `Create a new remote with name, type and options.`, + Long: ` +Create a new remote of with and options. The options +should be passed in in pairs of . + +For example to make a swift remote of name myremote using auto config +you would do: + + rclone config create myremote swift env_auth true +`, + RunE: func(command *cobra.Command, args []string) error { + cmd.CheckArgs(2, 256, command, args) + return fs.CreateRemote(args[0], args[1], args[2:]) + }, +} + +var configUpdateCommand = &cobra.Command{ + Use: "update [ ]+", + Short: `Update options in an existing remote.`, + Long: ` +Update an existing remote's options. The options should be passed in +in pairs of . + +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 +`, + RunE: func(command *cobra.Command, args []string) error { + cmd.CheckArgs(3, 256, command, args) + return fs.UpdateRemote(args[0], args[1:]) + }, +} + +var configDeleteCommand = &cobra.Command{ + Use: "delete ", + Short: `Delete an existing remote .`, + Run: func(command *cobra.Command, args []string) { + cmd.CheckArgs(1, 1, command, args) + fs.DeleteRemote(args[0]) }, } diff --git a/fs/config.go b/fs/config.go index fd525b8e7..e8089132b 100644 --- a/fs/config.go +++ b/fs/config.go @@ -992,56 +992,47 @@ func ChooseOption(o *Option) string { return ReadLine() } -// JsonConfig Created a new remote type X with parameters Y -func JsonConfig(name string, provider string, jsonstr string) { - bytes := []byte(jsonstr) - - // Unmarshal string into structs. - var options []Option - json.Unmarshal(bytes, &options) - - configData.SetValue(name, "type", provider) - // Loop over structs and display them. - for op := range options { - configData.SetValue(name, options[op].Name, options[op].Value) +// UpdateRemote adds the keyValues passed in to the remote of name. +// keyValues should be key, value pairs. +func UpdateRemote(name string, keyValues []string) error { + if len(keyValues)%2 != 0 { + return errors.New("found key without value") + } + // Set the config + for i := 0; i < len(keyValues); i += 2 { + configData.SetValue(name, keyValues[i], keyValues[i+1]) } - configData.SetValue(name, ConfigAutomatic, "yes") RemoteConfig(name) ShowRemote(name) SaveConfig() + return nil } -// ListOptions Lists all the options needed to connect to a protocol -func ListOptions(provider string) { - fs := MustFind(provider) - b, err := json.Marshal(fs.Options) +// CreateRemote creates a new remote with name, provider and a list of +// parameters which are key, value pairs. If update is set then it +// adds the new keys rather than replacing all of them. +func CreateRemote(name string, provider string, keyValues []string) error { + // Delete the old config if it exists + configData.DeleteSection(name) + // Set the type + configData.SetValue(name, "type", provider) + // Show this is automatically configured + configData.SetValue(name, ConfigAutomatic, "yes") + // Set the remaining values + return UpdateRemote(name, keyValues) +} + +// JSONListProviders prints all the providers and options in JSON format +func JSONListProviders() error { + b, err := json.MarshalIndent(fsRegistry, "", " ") if err != nil { - fmt.Println("error:", err) + return errors.Wrap(err, "failed to marshal examples") } - os.Stdout.Write(b) -} - -// ListProviders print all providersList, in json format, the protocols supported by sync -func ListProviders() { - o := &Option{ - Name: "Storage", - Help: "Type of storage to configure.", - } - for _, item := range fsRegistry { - example := OptionExample{ - Value: item.Name, - Help: item.Description, - } - o.Examples = append(o.Examples, example) - } - if len(o.Examples) > 0 { - o.Examples.Sort() - b, err := json.Marshal(o.Examples) - if err != nil { - fmt.Println("error:", err) - } - os.Stdout.Write(b) + _, err = os.Stdout.Write(b) + if err != nil { + return errors.Wrap(err, "failed to write providers list") } + return nil } // fsOption returns an Option describing the possible remotes @@ -1367,3 +1358,24 @@ func ConfigFileSections() []string { } return sections } + +// ConfigDump dumps all the config as a JSON file +func ConfigDump() error { + dump := make(map[string]map[string]string) + for _, name := range configData.GetSectionList() { + params := make(map[string]string) + for _, key := range configData.GetKeyList(name) { + params[key] = ConfigFileGet(name, key) + } + dump[name] = params + } + b, err := json.MarshalIndent(dump, "", " ") + if err != nil { + return errors.Wrap(err, "failed to marshal config dump") + } + _, err = os.Stdout.Write(b) + if err != nil { + return errors.Wrap(err, "failed to write config dump") + } + return nil +} diff --git a/fs/fs.go b/fs/fs.go index b5bb5187d..e339ed222 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -62,9 +62,9 @@ type RegInfo struct { // Create a new file system. If root refers to an existing // object, then it should return a Fs which which points to // the parent of that object and ErrorIsFile. - NewFs func(name string, root string) (Fs, error) + NewFs func(name string, root string) (Fs, error) `json:"-"` // Function to call to help with config - Config func(string) + Config func(string) `json:"-"` // Options for the Fs configuration Options []Option } @@ -75,8 +75,7 @@ type Option struct { Help string Optional bool IsPassword bool - Examples OptionExamples - Value string + Examples OptionExamples `json:",omitempty"` } // OptionExamples is a slice of examples