diff --git a/bin/config.py b/bin/config.py new file mode 100755 index 000000000..5560fa850 --- /dev/null +++ b/bin/config.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +""" +Test program to demonstrate the remote config interfaces in +rclone. + +This program can simulate + + rclone config create + rclone config update + rclone config password - NOT implemented yet + rclone authorize - NOT implemented yet + +Pass the desired action as the first argument then any parameters. + +This assumes passwords will be passed in the clear. +""" + +import argparse +import subprocess +import json +from pprint import pprint + +sep = "-"*60 + +def rpc(command, params): + """ + Run the command. This could be either over the CLI or the API. + Here we run over the API using rclone rc --loopback which is + useful for making sure state is saved properly. + """ + cmd = ["rclone", "-vv", "rc", "--loopback", command, "--json", json.dumps(params)] + result = subprocess.run(cmd, stdout=subprocess.PIPE, check=True) + return json.loads(result.stdout) + +def parse_parameters(parameters): + """ + Parse the incoming key=value parameters into a dict + """ + d = {} + for param in parameters: + parts = param.split("=", 1) + if len(parts) != 2: + raise ValueError("bad format for parameter need name=value") + d[parts[0]] = parts[1] + return d + +def ask(opt): + """ + Ask the user to enter the option + + This is the user interface for asking a user a question. + + If there are examples they should be presented. + """ + while True: + if opt["IsPassword"]: + print("*** Inputting a password") + print(opt['Help']) + examples = opt.get("Examples", ()) + or_number = "" + if len(examples) > 0: + or_number = " or choice number" + for i, example in enumerate(examples): + print(f"{i:3} value: {example['Value']}") + print(f" help: {example['Help']}") + print(f"Enter a {opt['Type']} value{or_number}. Press Enter for the default ('{opt['DefaultStr']}')") + print(f"{opt['Name']}> ", end='') + s = input() + if s == "": + return opt["DefaultStr"] + try: + i = int(s) + if i >= 0 and i < len(examples): + return examples[i]["Value"] + except ValueError: + pass + if opt["Exclusive"]: + for example in examples: + if s == example["Value"]: + return s + # Exclusive is set but the value isn't one of the accepted + # ones so continue + print("Value isn't one of the acceptable values") + else: + return s + return s + +def create_or_update(what, args): + """ + Run the equivalent of rclone config create + or rclone config update + + what should either be "create" or "update + """ + print(what, args) + params = parse_parameters(args.parameters) + inp = { + "name": args.name, + "parameters": params, + "opt": { + "nonInteractive": True, + "all": args.all, + "noObscure": args.obscured_passwords, + "obscure": not args.obscured_passwords, + }, + } + if what == "create": + inp["type"] = args.type + while True: + print(sep) + print("Input to API") + pprint(inp) + print(sep) + out = rpc("config/"+what, inp) + print(sep) + print("Output from API") + pprint(out) + print(sep) + if out["State"] == "": + return + if out["Error"]: + print("Error", out["Error"]) + result = ask(out["Option"]) + inp["opt"]["state"] = out["State"] + inp["opt"]["result"] = result + inp["opt"]["continue"] = True + +def create(args): + """Run the equivalent of rclone config create""" + create_or_update("create", args) + +def update(args): + """Run the equivalent of rclone config update""" + create_or_update("update", args) + +def password(args): + """Run the equivalent of rclone config password""" + print("password", args) + raise NotImplementedError() + +def authorize(args): + """Run the equivalent of rclone authorize""" + print("authorize", args) + raise NotImplementedError() + +def main(): + """ + Make the command line parser and dispatch + """ + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("-a", "--all", action='store_true', + help="Ask all the config questions if set") + parser.add_argument("-o", "--obscured-passwords", action='store_true', + help="If set assume the passwords are obscured") + + subparsers = parser.add_subparsers(dest='command', required=True) + + subparser = subparsers.add_parser('create') + subparser.add_argument("name", type=str, help="Name of remote to create") + subparser.add_argument("type", type=str, help="Type of remote to create") + subparser.add_argument("parameters", type=str, nargs='*', help="Config parameters name=value name=value") + subparser.set_defaults(func=create) + + subparser = subparsers.add_parser('update') + subparser.add_argument("name", type=str, help="Name of remote to update") + subparser.add_argument("parameters", type=str, nargs='*', help="Config parameters name=value name=value") + subparser.set_defaults(func=update) + + subparser = subparsers.add_parser('password') + subparser.add_argument("name", type=str, help="Name of remote to update") + subparser.add_argument("parameters", type=str, nargs='*', help="Config parameters name=value name=value") + subparser.set_defaults(func=password) + + subparser = subparsers.add_parser('authorize') + subparser.set_defaults(func=authorize) + + args = parser.parse_args() + args.func(args) + +if __name__ == "__main__": + main() diff --git a/fs/backend_config.go b/fs/backend_config.go index 6d8e2fad9..97db87202 100644 --- a/fs/backend_config.go +++ b/fs/backend_config.go @@ -60,6 +60,9 @@ var ConfigOAuth func(ctx context.Context, name string, m configmap.Mapper, ri *R // "config_fs_" are reserved for internal use. // // State names starting with "*" are reserved for internal use. +// +// Note that in the bin directory there is a python program called +// "config.py" which shows how this interface should be used. type ConfigIn struct { State string // State to run Result string // Result from previous Option