From 126588196b9c93e2f4d787afa106aaf68141f404 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna <43019056+aMahanna@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:54:40 -0500 Subject: [PATCH] new: `post /_admin/execute` (#309) * new: `Database.execute()` * fix lint * fix end of file * fix: lint --- arango/database.py | 31 +++++++++++++++++++++++++++++++ arango/exceptions.py | 4 ++++ arango/replication.py | 16 +++++++++------- tests/static/cluster-3.10.conf | 1 + tests/static/cluster.conf | 1 + tests/static/single-3.10.conf | 1 + tests/static/single.conf | 1 + tests/test_database.py | 6 ++++++ 8 files changed, 54 insertions(+), 7 deletions(-) diff --git a/arango/database.py b/arango/database.py index aedeaba6..734fd7ca 100644 --- a/arango/database.py +++ b/arango/database.py @@ -45,6 +45,7 @@ ServerEchoError, ServerEncryptionError, ServerEngineError, + ServerExecuteError, ServerLicenseGetError, ServerLicenseSetError, ServerLogLevelError, @@ -229,6 +230,36 @@ def response_handler(resp: Response) -> Json: return self._execute(request, response_handler) + def execute(self, command: str) -> Result[Any]: + """Execute raw Javascript command on the server. + + Executes the JavaScript code in the body on the server as + the body of a function with no arguments. If you have a + return statement then the return value you produce will be returned + as 'application/json'. + + NOTE: this method endpoint will only be usable if the server + was started with the option `--javascript.allow-admin-execute true`. + The default value of this option is false, which disables the execution + of user-defined code and disables this API endpoint entirely. + This is also the recommended setting for production. + + :param command: Javascript command to execute. + :type command: str + :return: Return value of **command**, if any. + :rtype: Any + :raise arango.exceptions.ServerExecuteError: If execution fails. + """ + request = Request(method="post", endpoint="/_admin/execute", data=command) + + def response_handler(resp: Response) -> Any: + if not resp.is_success: + raise ServerExecuteError(resp, request) + + return resp.body + + return self._execute(request, response_handler) + def execute_transaction( self, command: str, diff --git a/arango/exceptions.py b/arango/exceptions.py index 0c0aca26..8e7b807d 100644 --- a/arango/exceptions.py +++ b/arango/exceptions.py @@ -718,6 +718,10 @@ class ServerEncryptionError(ArangoServerError): """Failed to reload user-defined encryption keys.""" +class ServerExecuteError(ArangoServerError): + """Failed to execute raw JavaScript command.""" + + ##################### # Task Exceptions # ##################### diff --git a/arango/replication.py b/arango/replication.py index 0ecfc20e..d5fae457 100644 --- a/arango/replication.py +++ b/arango/replication.py @@ -180,13 +180,15 @@ def response_handler(resp: Response) -> Json: if resp.is_success: result = format_replication_header(resp.headers) result["content"] = [ - [ - self._conn.deserialize(line) - for line in resp.body.split("\n") - if line - ] - if deserialize - else resp.body + ( + [ + self._conn.deserialize(line) + for line in resp.body.split("\n") + if line + ] + if deserialize + else resp.body + ) ] return result diff --git a/tests/static/cluster-3.10.conf b/tests/static/cluster-3.10.conf index 573c030a..d7732c90 100644 --- a/tests/static/cluster-3.10.conf +++ b/tests/static/cluster-3.10.conf @@ -10,3 +10,4 @@ jwt-secret = /tests/static/keyfile [args] all.database.password = passwd all.log.api-enabled = true +all.javascript.allow-admin-execute = true diff --git a/tests/static/cluster.conf b/tests/static/cluster.conf index 182f3d17..86f78556 100644 --- a/tests/static/cluster.conf +++ b/tests/static/cluster.conf @@ -11,3 +11,4 @@ jwt-secret = /tests/static/keyfile all.database.password = passwd all.database.extended-names = true all.log.api-enabled = true +all.javascript.allow-admin-execute = true diff --git a/tests/static/single-3.10.conf b/tests/static/single-3.10.conf index c982303b..09d1d9f3 100644 --- a/tests/static/single-3.10.conf +++ b/tests/static/single-3.10.conf @@ -8,3 +8,4 @@ jwt-secret = /tests/static/keyfile [args] all.database.password = passwd +all.javascript.allow-admin-execute = true diff --git a/tests/static/single.conf b/tests/static/single.conf index e880f9d5..df45cb76 100644 --- a/tests/static/single.conf +++ b/tests/static/single.conf @@ -9,3 +9,4 @@ jwt-secret = /tests/static/keyfile [args] all.database.password = passwd all.database.extended-names = true +all.javascript.allow-admin-execute = true diff --git a/tests/test_database.py b/tests/test_database.py index 8645b909..cb43cbf2 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -305,6 +305,12 @@ def test_database_misc_methods(client, sys_db, db, bad_db, cluster, secret): bad_db.engine() assert err.value.error_code in {11, 1228} + # Test execute JavaScript code + assert db.execute(1) is None + assert db.execute(None) == {"error": False, "code": 200} + assert db.execute("") == {"error": False, "code": 200} + assert db.execute("return 1") == 1 + # Test database compact with assert_raises(DatabaseCompactError) as err: db.compact()