mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-17 19:03:42 +01:00
Plugin: support extra args to "start".
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Added: Plugins: `start` command can now take plugin-specific parameters.
This commit is contained in:
parent
8a9976c4c1
commit
d971e3de98
@ -1024,14 +1024,15 @@ class LightningRpc(UnixDomainSocketRpc):
|
||||
}
|
||||
return self.call("ping", payload)
|
||||
|
||||
def plugin_start(self, plugin):
|
||||
def plugin_start(self, plugin, **kwargs):
|
||||
"""
|
||||
Adds a plugin to lightningd.
|
||||
"""
|
||||
payload = {
|
||||
"subcommand": "start",
|
||||
"plugin": plugin
|
||||
"plugin": plugin,
|
||||
}
|
||||
payload.update({k: v for k, v in kwargs.items()})
|
||||
return self.call("plugin", payload)
|
||||
|
||||
def plugin_startdir(self, directory):
|
||||
|
9
doc/lightning-plugin.7
generated
9
doc/lightning-plugin.7
generated
@ -14,9 +14,10 @@ optionally one or two parameters which describes the plugin on which the
|
||||
action has to be taken\.
|
||||
|
||||
|
||||
The \fIstart\fR command takes a path as the first parameter and will load the
|
||||
plugin available from this path\. It will wait for the plugin to complete
|
||||
the handshake with \fBlightningd\fR for 20 seconds at the most\.
|
||||
The \fIstart\fR command takes a path as the first parameter and will load
|
||||
the plugin available from this path\. Any additional parameters are
|
||||
passed to the plugin\. It will wait for the plugin to complete the
|
||||
handshake with \fBlightningd\fR for 20 seconds at the most\.
|
||||
|
||||
|
||||
The \fIstop\fR command takes a plugin name as parameter\. It will kill and
|
||||
@ -54,4 +55,4 @@ Antoine Poinsot \fI<darosior@protonmail.com\fR> is mainly responsible\.
|
||||
|
||||
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
|
||||
|
||||
\" SHA256STAMP:a8d7488cb0a731e794aeed1d2d30448cef27204d9ec0374610faf2a84a401762
|
||||
\" SHA256STAMP:f4b405aa5a5bb8dde3b8947b7d6152ba5ac88108cde43c1375121e5c24555d30
|
||||
|
@ -15,9 +15,10 @@ restart lightningd. It takes 1 to 3 parameters: a command
|
||||
optionally one or two parameters which describes the plugin on which the
|
||||
action has to be taken.
|
||||
|
||||
The *start* command takes a path as the first parameter and will load the
|
||||
plugin available from this path. It will wait for the plugin to complete
|
||||
the handshake with `lightningd` for 20 seconds at the most.
|
||||
The *start* command takes a path as the first parameter and will load
|
||||
the plugin available from this path. Any additional parameters are
|
||||
passed to the plugin. It will wait for the plugin to complete the
|
||||
handshake with `lightningd` for 20 seconds at the most.
|
||||
|
||||
The *stop* command takes a plugin name as parameter. It will kill and
|
||||
unload the specified plugin.
|
||||
|
@ -348,7 +348,7 @@ static char *opt_add_plugin(const char *arg, struct lightningd *ld)
|
||||
log_info(ld->log, "%s: disabled via disable-plugin", arg);
|
||||
return NULL;
|
||||
}
|
||||
plugin_register(ld->plugins, arg, NULL, false);
|
||||
plugin_register(ld->plugins, arg, NULL, false, NULL, NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -375,7 +375,7 @@ static char *opt_important_plugin(const char *arg, struct lightningd *ld)
|
||||
log_info(ld->log, "%s: disabled via disable-plugin", arg);
|
||||
return NULL;
|
||||
}
|
||||
plugin_register(ld->plugins, arg, NULL, true);
|
||||
plugin_register(ld->plugins, arg, NULL, true, NULL, NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <ccan/array_size/array_size.h>
|
||||
#include <ccan/list/list.h>
|
||||
#include <ccan/mem/mem.h>
|
||||
#include <ccan/opt/opt.h>
|
||||
#include <ccan/tal/str/str.h>
|
||||
#include <ccan/utf8/utf8.h>
|
||||
@ -174,7 +175,9 @@ static void destroy_plugin(struct plugin *p)
|
||||
}
|
||||
|
||||
struct plugin *plugin_register(struct plugins *plugins, const char* path TAKES,
|
||||
struct command *start_cmd, bool important)
|
||||
struct command *start_cmd, bool important,
|
||||
const char *parambuf STEALS,
|
||||
const jsmntok_t *params STEALS)
|
||||
{
|
||||
struct plugin *p, *p_temp;
|
||||
|
||||
@ -212,6 +215,8 @@ struct plugin *plugin_register(struct plugins *plugins, const char* path TAKES,
|
||||
list_head_init(&p->pending_rpccalls);
|
||||
|
||||
p->important = important;
|
||||
p->parambuf = tal_steal(p, parambuf);
|
||||
p->params = tal_steal(p, params);
|
||||
return p;
|
||||
}
|
||||
|
||||
@ -1154,6 +1159,48 @@ static const char *plugin_hooks_add(struct plugin *plugin, const char *buffer,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct plugin_opt *plugin_opt_find(struct plugin *plugin,
|
||||
const char *name, size_t namelen)
|
||||
{
|
||||
struct plugin_opt *opt;
|
||||
|
||||
list_for_each(&plugin->plugin_opts, opt, list) {
|
||||
/* Trim the `--` that we added before */
|
||||
if (memeqstr(name, namelen, opt->name + 2))
|
||||
return opt;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* start command might have included plugin-specific parameters */
|
||||
static const char *plugin_add_params(struct plugin *plugin)
|
||||
{
|
||||
size_t i;
|
||||
const jsmntok_t *t;
|
||||
|
||||
if (!plugin->params)
|
||||
return NULL;
|
||||
|
||||
json_for_each_obj(i, t, plugin->params) {
|
||||
struct plugin_opt *popt;
|
||||
char *err;
|
||||
|
||||
popt = plugin_opt_find(plugin,
|
||||
plugin->parambuf + t->start,
|
||||
t->end - t->start);
|
||||
if (!popt) {
|
||||
return tal_fmt(plugin, "unknown parameter %.*s",
|
||||
json_tok_full_len(t),
|
||||
json_tok_full(plugin->parambuf, t));
|
||||
}
|
||||
err = plugin_opt_set(json_strdup(tmpctx, plugin->parambuf,
|
||||
t + 1), popt);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void plugin_manifest_timeout(struct plugin *plugin)
|
||||
{
|
||||
bool startup = plugin->plugins->startup;
|
||||
@ -1243,6 +1290,8 @@ static const char *plugin_parse_getmanifest_response(const char *buffer,
|
||||
err = plugin_subscriptions_add(plugin, buffer, resulttok);
|
||||
if (!err)
|
||||
err = plugin_hooks_add(plugin, buffer, resulttok);
|
||||
if (!err)
|
||||
err = plugin_add_params(plugin);
|
||||
|
||||
plugin->plugin_state = NEEDS_INIT;
|
||||
return err;
|
||||
@ -1360,7 +1409,8 @@ char *add_plugin_dir(struct plugins *plugins, const char *dir, bool error_ok)
|
||||
log_info(plugins->log, "%s: disabled via disable-plugin",
|
||||
fullpath);
|
||||
} else {
|
||||
p = plugin_register(plugins, fullpath, NULL, false);
|
||||
p = plugin_register(plugins, fullpath, NULL, false,
|
||||
NULL, NULL);
|
||||
if (!p && !error_ok)
|
||||
return tal_fmt(NULL, "Failed to register %s: %s",
|
||||
fullpath, strerror(errno));
|
||||
@ -1807,7 +1857,8 @@ void plugins_set_builtin_plugins_dir(struct plugins *plugins,
|
||||
take(path_join(NULL, dir,
|
||||
list_of_builtin_plugins[i])),
|
||||
NULL,
|
||||
/* important = */ true);
|
||||
/* important = */ true,
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
struct plugin_destroyed {
|
||||
|
@ -88,6 +88,10 @@ struct plugin {
|
||||
/* If set, the plugin is so important that if it terminates early,
|
||||
* C-lightning should terminate as well. */
|
||||
bool important;
|
||||
|
||||
/* Parameters for dynamically-started plugins. */
|
||||
const char *parambuf;
|
||||
const jsmntok_t *params;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -200,6 +204,8 @@ void plugins_free(struct plugins *plugins);
|
||||
* @param path: The path of the executable for this plugin
|
||||
* @param start_cmd: The optional JSON command which caused this.
|
||||
* @param important: The plugin is important.
|
||||
* @param parambuf: NULL, or the JSON buffer for extra parameters.
|
||||
* @param params: NULL, or the tokens for extra parameters.
|
||||
*
|
||||
* If @start_cmd, then plugin_cmd_killed or plugin_cmd_succeeded will be called
|
||||
* on it eventually.
|
||||
@ -207,7 +213,10 @@ void plugins_free(struct plugins *plugins);
|
||||
struct plugin *plugin_register(struct plugins *plugins,
|
||||
const char* path TAKES,
|
||||
struct command *start_cmd,
|
||||
bool important);
|
||||
bool important,
|
||||
const char *parambuf STEALS,
|
||||
const jsmntok_t *params STEALS);
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the provided name matches a plugin command
|
||||
|
@ -56,9 +56,10 @@ struct command_result *plugin_cmd_all_complete(struct plugins *plugins,
|
||||
* will give a result 60 seconds later at the most (once init completes).
|
||||
*/
|
||||
static struct command_result *
|
||||
plugin_dynamic_start(struct command *cmd, const char *plugin_path)
|
||||
plugin_dynamic_start(struct command *cmd, const char *plugin_path,
|
||||
const char *buffer, const jsmntok_t *params)
|
||||
{
|
||||
struct plugin *p = plugin_register(cmd->ld->plugins, plugin_path, cmd, false);
|
||||
struct plugin *p = plugin_register(cmd->ld->plugins, plugin_path, cmd, false, buffer, params);
|
||||
const char *err;
|
||||
|
||||
if (!p)
|
||||
@ -173,15 +174,35 @@ static struct command_result *json_plugin_control(struct command *cmd,
|
||||
return plugin_dynamic_stop(cmd, plugin_name);
|
||||
} else if (streq(subcmd, "start")) {
|
||||
const char *plugin_path;
|
||||
jsmntok_t *mod_params;
|
||||
|
||||
if (!param(cmd, buffer, params,
|
||||
p_req("subcommand", param_ignore, cmd),
|
||||
p_req("plugin", param_string, &plugin_path),
|
||||
p_opt_any(),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
/* Manually parse any remaining options (only for objects,
|
||||
* since plugin options must be explicitly named!). */
|
||||
if (params->type == JSMN_ARRAY) {
|
||||
if (params->size != 2)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Extra parameters must be in object");
|
||||
mod_params = NULL;
|
||||
} else {
|
||||
mod_params = json_tok_copy(cmd, params);
|
||||
|
||||
json_tok_remove(&mod_params, mod_params,
|
||||
json_get_member(buffer, mod_params,
|
||||
"subcommand") - 1, 1);
|
||||
json_tok_remove(&mod_params, mod_params,
|
||||
json_get_member(buffer, mod_params,
|
||||
"plugin") - 1, 1);
|
||||
}
|
||||
if (access(plugin_path, X_OK) == 0)
|
||||
return plugin_dynamic_start(cmd, plugin_path);
|
||||
return plugin_dynamic_start(cmd, plugin_path,
|
||||
buffer, mod_params);
|
||||
else
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"%s is not executable: %s",
|
||||
|
@ -2182,3 +2182,18 @@ def test_htlc_accepted_hook_failonion(node_factory):
|
||||
inv = l2.rpc.invoice(42, 'failonion000', '')['bolt11']
|
||||
with pytest.raises(RpcError):
|
||||
l1.rpc.pay(inv)
|
||||
|
||||
|
||||
def test_dynamic_args(node_factory):
|
||||
plugin_path = os.path.join(os.getcwd(), 'contrib/plugins/helloworld.py')
|
||||
|
||||
l1 = node_factory.get_node()
|
||||
l1.rpc.plugin_start(plugin_path, greeting='Test arg parsing')
|
||||
|
||||
assert l1.rpc.call("hello") == "Test arg parsing world"
|
||||
plugin = only_one([p for p in l1.rpc.listconfigs()['plugins'] if p['path'] == plugin_path])
|
||||
assert plugin['options']['greeting'] == 'Test arg parsing'
|
||||
|
||||
l1.rpc.plugin_stop(plugin_path)
|
||||
|
||||
assert [p for p in l1.rpc.listconfigs()['plugins'] if p['path'] == plugin_path] == []
|
||||
|
Loading…
Reference in New Issue
Block a user