Browse Source

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.
ppa
Rusty Russell 4 years ago
parent
commit
d971e3de98
  1. 5
      contrib/pyln-client/pyln/client/lightning.py
  2. 9
      doc/lightning-plugin.7
  3. 7
      doc/lightning-plugin.7.md
  4. 4
      lightningd/options.c
  5. 57
      lightningd/plugin.c
  6. 11
      lightningd/plugin.h
  7. 27
      lightningd/plugin_control.c
  8. 15
      tests/test_plugin.py

5
contrib/pyln-client/pyln/client/lightning.py

@ -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

@ -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

7
doc/lightning-plugin.7.md

@ -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.

4
lightningd/options.c

@ -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;
}

57
lightningd/plugin.c

@ -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 {

11
lightningd/plugin.h

@ -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

27
lightningd/plugin_control.c

@ -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",

15
tests/test_plugin.py

@ -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…
Cancel
Save