Skip to content

Commit

Permalink
plugins/clnrest: simple wrapper to handle missing python3.
Browse files Browse the repository at this point in the history
Apparently NixOS didn't have Python (sometimes!) so let's not assume.
By reusing the JSON "parsing" code from cowsay, we can self-disable
to handle this case.

Reported-by: Shahana Farooqui <[email protected]>
Signed-off-by: Rusty Russell <[email protected]>
Changelog-Fixed: Plugins: `clnrest` now correctly self-disables if Python not present at all.
  • Loading branch information
rustyrussell authored and vincenzopalazzo committed Apr 12, 2024
1 parent 7d8c723 commit c4edec8
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 14 deletions.
2 changes: 1 addition & 1 deletion plugins/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ C_PLUGINS := \
plugins/spenderp

PY_PLUGINS := \
plugins/clnrest/clnrest.py
plugins/clnrest/clnrest

ifeq ($(HAVE_SQLITE3),1)
C_PLUGINS += plugins/sql
Expand Down
16 changes: 16 additions & 0 deletions plugins/clnrest/clnrest
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#! /bin/sh
# clrest.py neatly exits if we don't have dependencies, but what if we don't
# have Python itself?

if ! type python3 > /dev/null 2>&1; then
# No python3 binary.
# Fortunately, CLN gives us JSON in a very standard way, so we can assume:
# Eg. {"jsonrpc":"2.0","id":2,"method":"getmanifest","params":{}}\n\n
read -r JSON
read -r _
id=$(echo "$JSON" | sed 's/.*"id" *: *\([^,]*\),.*/\1/')
echo '{"jsonrpc":"2.0","id":'"$id"',"result":{"disable":"No python3 binary found"}}'
exit 1
fi

exec "$0".py
26 changes: 13 additions & 13 deletions tests/test_clnrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def test_clnrest_no_auto_start(node_factory):
l1 = node_factory.get_node()
# This might happen really early!
l1.daemon.logsearch_start = 0
assert [p for p in l1.rpc.plugin('list')['plugins'] if 'clnrest.py' in p['name']] == []
assert l1.daemon.is_in_log(r'plugin-clnrest.py: Killing plugin: disabled itself at init: `clnrest-port` option is not configured')
assert [p for p in l1.rpc.plugin('list')['plugins'] if 'clnrest' in p['name']] == []
assert l1.daemon.is_in_log(r'plugin-clnrest: Killing plugin: disabled itself at init: `clnrest-port` option is not configured')


def test_clnrest_self_signed_certificates(node_factory):
Expand All @@ -43,7 +43,7 @@ def test_clnrest_self_signed_certificates(node_factory):
'clnrest-host': rest_host})
# This might happen really early!
l1.daemon.logsearch_start = 0
l1.daemon.wait_for_log(r'plugin-clnrest.py: REST server running at ' + base_url)
l1.daemon.wait_for_log(r'plugin-clnrest: REST server running at ' + base_url)
ca_cert = Path(l1.daemon.lightning_dir) / TEST_NETWORK / 'ca.pem'

http_session = http_session_with_retry()
Expand All @@ -65,7 +65,7 @@ def test_clnrest_uses_grpc_plugin_certificates(node_factory):
# This might happen really early!
l1.daemon.logsearch_start = 0
l1.daemon.wait_for_logs([r'serving grpc on 0.0.0.0:',
r'plugin-clnrest.py: REST server running at ' + base_url])
r'plugin-clnrest: REST server running at ' + base_url])
ca_cert = Path(l1.daemon.lightning_dir) / TEST_NETWORK / 'ca.pem'
http_session = http_session_with_retry()
response = http_session.get(base_url + '/v1/list-methods', verify=ca_cert)
Expand Down Expand Up @@ -136,7 +136,7 @@ def start_node_with_clnrest(node_factory):
base_url = 'https://127.0.0.1:' + rest_port
# This might happen really early!
l1.daemon.logsearch_start = 0
l1.daemon.wait_for_log(r'plugin-clnrest.py: REST server running at ' + base_url)
l1.daemon.wait_for_log(r'plugin-clnrest: REST server running at ' + base_url)
ca_cert = Path(rest_certs) / 'ca.pem'
return l1, base_url, ca_cert

Expand Down Expand Up @@ -402,21 +402,21 @@ def test_clnrest_options(node_factory):
# with invalid port
rest_port = 1000
l1 = node_factory.get_node(options={'clnrest-port': rest_port})
assert l1.daemon.is_in_log(f'plugin-clnrest.py: Killing plugin: disabled itself at init: `clnrest-port` {rest_port}, should be a valid available port between 1024 and 65535.')
assert l1.daemon.is_in_log(f'plugin-clnrest: Killing plugin: disabled itself at init: `clnrest-port` {rest_port}, should be a valid available port between 1024 and 65535.')

# with invalid protocol
rest_port = str(reserve())
rest_protocol = 'htttps'
l1 = node_factory.get_node(options={'clnrest-port': rest_port,
'clnrest-protocol': rest_protocol})
assert l1.daemon.is_in_log(r'plugin-clnrest.py: Killing plugin: disabled itself at init: `clnrest-protocol` can either be http or https.')
assert l1.daemon.is_in_log(r'plugin-clnrest: Killing plugin: disabled itself at init: `clnrest-protocol` can either be http or https.')

# with invalid host
rest_port = str(reserve())
rest_host = '127.0.0.12.15'
l1 = node_factory.get_node(options={'clnrest-port': rest_port,
'clnrest-host': rest_host})
assert l1.daemon.is_in_log(r'plugin-clnrest.py: Killing plugin: disabled itself at init: `clnrest-host` should be a valid IP.')
assert l1.daemon.is_in_log(r'plugin-clnrest: Killing plugin: disabled itself at init: `clnrest-host` should be a valid IP.')


def test_clnrest_http_headers(node_factory):
Expand All @@ -431,7 +431,7 @@ def test_clnrest_http_headers(node_factory):
assert response.headers['Access-Control-Allow-Origin'] == '*'
# This might happen really early!
l1.daemon.logsearch_start = 0
l1.daemon.wait_for_log(f'plugin-clnrest.py: REST server running at {base_url}')
l1.daemon.wait_for_log(f'plugin-clnrest: REST server running at {base_url}')

# Custom values for `clnrest-csp` and `clnrest-cors-origins` options
rest_port = str(reserve())
Expand All @@ -445,7 +445,7 @@ def test_clnrest_http_headers(node_factory):
base_url = 'https://127.0.0.1:' + rest_port
# This might happen really early!
l2.daemon.logsearch_start = 0
l2.daemon.wait_for_log(f'plugin-clnrest.py: REST server running at {base_url}')
l2.daemon.wait_for_log(f'plugin-clnrest: REST server running at {base_url}')
ca_cert = Path(rest_certs) / 'ca.pem'

response = http_session.get(base_url + '/v1/list-methods',
Expand All @@ -471,7 +471,7 @@ def test_clnrest_old_params(node_factory):
l1.daemon.logsearch_start = 0
l1.daemon.wait_for_logs([r'UNUSUAL lightningd: Option rest-port=.* deprecated in v23\.11, renaming to clnrest-port',
r'UNUSUAL lightningd: Option rest-host=.* deprecated in v23\.11, renaming to clnrest-host'])
l1.daemon.wait_for_log(r'plugin-clnrest.py: REST server running at ' + base_url)
l1.daemon.wait_for_log(r'plugin-clnrest: REST server running at ' + base_url)

# Now try one where a plugin (e.g. clightning-rest) registers the option.
plugin = os.path.join(os.path.dirname(__file__), 'plugins/clnrest-use-options.py')
Expand All @@ -486,6 +486,6 @@ def test_clnrest_old_params(node_factory):

# This one does not get renamed!
assert not l2.daemon.is_in_log(r'UNUSUAL lightningd: Option rest-port=.* deprecated in v23\.11, renaming to clnrest-host')
assert [p for p in l2.rpc.plugin('list')['plugins'] if 'clnrest.py' in p['name']] == []
assert l2.daemon.is_in_log(r'plugin-clnrest.py: Killing plugin: disabled itself at init: `clnrest-port` option is not configured')
assert [p for p in l2.rpc.plugin('list')['plugins'] if p['name'].endswith('clnrest')] == []
assert l2.daemon.is_in_log(r'plugin-clnrest: Killing plugin: disabled itself at init: `clnrest-port` option is not configured')
assert l2.daemon.is_in_log(rf'clnrest-use-options.py: rest-port is {rest_port}')

0 comments on commit c4edec8

Please sign in to comment.