mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-09 09:58:20 -05:00
Captures the stdout and stderr of the pre/post scripts into the log
This commit is contained in:
parent
50a211f367
commit
9a47963fd5
@ -2,7 +2,8 @@ import datetime
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from subprocess import Popen
|
from subprocess import CompletedProcess
|
||||||
|
from subprocess import run
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
@ -148,14 +149,21 @@ class Consumer(LoggingMixin):
|
|||||||
script_env["DOCUMENT_SOURCE_PATH"] = filepath_arg
|
script_env["DOCUMENT_SOURCE_PATH"] = filepath_arg
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Popen(
|
completed_proc = run(
|
||||||
(
|
args=[
|
||||||
settings.PRE_CONSUME_SCRIPT,
|
settings.PRE_CONSUME_SCRIPT,
|
||||||
filepath_arg,
|
filepath_arg,
|
||||||
),
|
],
|
||||||
env=script_env,
|
env=script_env,
|
||||||
).wait()
|
capture_output=True,
|
||||||
except Exception as e:
|
)
|
||||||
|
|
||||||
|
self._log_script_outputs(completed_proc)
|
||||||
|
|
||||||
|
# Raises exception on non-zero output
|
||||||
|
completed_proc.check_returncode()
|
||||||
|
|
||||||
|
except Exception as e: # pragma: nocover
|
||||||
self._fail(
|
self._fail(
|
||||||
MESSAGE_PRE_CONSUME_SCRIPT_ERROR,
|
MESSAGE_PRE_CONSUME_SCRIPT_ERROR,
|
||||||
f"Error while executing pre-consume script: {e}",
|
f"Error while executing pre-consume script: {e}",
|
||||||
@ -208,8 +216,8 @@ class Consumer(LoggingMixin):
|
|||||||
script_env["DOCUMENT_ORIGINAL_FILENAME"] = str(document.original_filename)
|
script_env["DOCUMENT_ORIGINAL_FILENAME"] = str(document.original_filename)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Popen(
|
completed_proc = run(
|
||||||
(
|
args=[
|
||||||
settings.POST_CONSUME_SCRIPT,
|
settings.POST_CONSUME_SCRIPT,
|
||||||
str(document.pk),
|
str(document.pk),
|
||||||
document.get_public_filename(),
|
document.get_public_filename(),
|
||||||
@ -219,10 +227,17 @@ class Consumer(LoggingMixin):
|
|||||||
reverse("document-thumb", kwargs={"pk": document.pk}),
|
reverse("document-thumb", kwargs={"pk": document.pk}),
|
||||||
str(document.correspondent),
|
str(document.correspondent),
|
||||||
str(",".join(document.tags.all().values_list("name", flat=True))),
|
str(",".join(document.tags.all().values_list("name", flat=True))),
|
||||||
),
|
],
|
||||||
env=script_env,
|
env=script_env,
|
||||||
).wait()
|
capture_output=True,
|
||||||
except Exception as e:
|
)
|
||||||
|
|
||||||
|
self._log_script_outputs(completed_proc)
|
||||||
|
|
||||||
|
# Raises exception on non-zero output
|
||||||
|
completed_proc.check_returncode()
|
||||||
|
|
||||||
|
except Exception as e: # pragma: nocover
|
||||||
self._fail(
|
self._fail(
|
||||||
MESSAGE_POST_CONSUME_SCRIPT_ERROR,
|
MESSAGE_POST_CONSUME_SCRIPT_ERROR,
|
||||||
f"Error while executing post-consume script: {e}",
|
f"Error while executing post-consume script: {e}",
|
||||||
@ -510,3 +525,31 @@ class Consumer(LoggingMixin):
|
|||||||
with open(source, "rb") as read_file:
|
with open(source, "rb") as read_file:
|
||||||
with open(target, "wb") as write_file:
|
with open(target, "wb") as write_file:
|
||||||
write_file.write(read_file.read())
|
write_file.write(read_file.read())
|
||||||
|
|
||||||
|
def _log_script_outputs(self, completed_process: CompletedProcess):
|
||||||
|
"""
|
||||||
|
Decodes a process stdout and stderr streams and logs them to the main log
|
||||||
|
"""
|
||||||
|
# Log what the script exited as
|
||||||
|
self.log(
|
||||||
|
"info",
|
||||||
|
f"{completed_process.args[0]} exited {completed_process.returncode}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Decode the output (if any)
|
||||||
|
stdout_str = completed_process.stdout.decode("utf8", errors="ignore").split(
|
||||||
|
"\n",
|
||||||
|
)
|
||||||
|
stderr_str = completed_process.stderr.decode("utf8", errors="ignore").split(
|
||||||
|
"\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(stdout_str):
|
||||||
|
self.log("info", "Script stdout:")
|
||||||
|
for line in stdout_str:
|
||||||
|
self.log("info", line)
|
||||||
|
|
||||||
|
if len(stderr_str):
|
||||||
|
self.log("warning", "Script stderr:")
|
||||||
|
for line in stderr_str:
|
||||||
|
self.log("warning", line)
|
||||||
|
@ -2,7 +2,9 @@ import datetime
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import stat
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from subprocess import CalledProcessError
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
@ -801,7 +803,7 @@ class TestConsumerCreatedDate(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class PreConsumeTestCase(TestCase):
|
class PreConsumeTestCase(TestCase):
|
||||||
@mock.patch("documents.consumer.Popen")
|
@mock.patch("documents.consumer.run")
|
||||||
@override_settings(PRE_CONSUME_SCRIPT=None)
|
@override_settings(PRE_CONSUME_SCRIPT=None)
|
||||||
def test_no_pre_consume_script(self, m):
|
def test_no_pre_consume_script(self, m):
|
||||||
c = Consumer()
|
c = Consumer()
|
||||||
@ -809,7 +811,7 @@ class PreConsumeTestCase(TestCase):
|
|||||||
c.run_pre_consume_script()
|
c.run_pre_consume_script()
|
||||||
m.assert_not_called()
|
m.assert_not_called()
|
||||||
|
|
||||||
@mock.patch("documents.consumer.Popen")
|
@mock.patch("documents.consumer.run")
|
||||||
@mock.patch("documents.consumer.Consumer._send_progress")
|
@mock.patch("documents.consumer.Consumer._send_progress")
|
||||||
@override_settings(PRE_CONSUME_SCRIPT="does-not-exist")
|
@override_settings(PRE_CONSUME_SCRIPT="does-not-exist")
|
||||||
def test_pre_consume_script_not_found(self, m, m2):
|
def test_pre_consume_script_not_found(self, m, m2):
|
||||||
@ -818,7 +820,7 @@ class PreConsumeTestCase(TestCase):
|
|||||||
c.path = "path-to-file"
|
c.path = "path-to-file"
|
||||||
self.assertRaises(ConsumerError, c.run_pre_consume_script)
|
self.assertRaises(ConsumerError, c.run_pre_consume_script)
|
||||||
|
|
||||||
@mock.patch("documents.consumer.Popen")
|
@mock.patch("documents.consumer.run")
|
||||||
def test_pre_consume_script(self, m):
|
def test_pre_consume_script(self, m):
|
||||||
with tempfile.NamedTemporaryFile() as script:
|
with tempfile.NamedTemporaryFile() as script:
|
||||||
with override_settings(PRE_CONSUME_SCRIPT=script.name):
|
with override_settings(PRE_CONSUME_SCRIPT=script.name):
|
||||||
@ -830,14 +832,45 @@ class PreConsumeTestCase(TestCase):
|
|||||||
|
|
||||||
args, kwargs = m.call_args
|
args, kwargs = m.call_args
|
||||||
|
|
||||||
command = args[0]
|
command = kwargs["args"]
|
||||||
|
|
||||||
self.assertEqual(command[0], script.name)
|
self.assertEqual(command[0], script.name)
|
||||||
self.assertEqual(command[1], "path-to-file")
|
self.assertEqual(command[1], "path-to-file")
|
||||||
|
|
||||||
|
@mock.patch("documents.consumer.Consumer.log")
|
||||||
|
def test_script_with_output(self, mocked_log):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A script which outputs to stdout and stderr
|
||||||
|
WHEN:
|
||||||
|
- The script is executed as a consume script
|
||||||
|
THEN:
|
||||||
|
- The script's outputs are logged
|
||||||
|
"""
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w") as script:
|
||||||
|
# Write up a little script
|
||||||
|
with script.file as outfile:
|
||||||
|
outfile.write("#!/usr/bin/env bash\n")
|
||||||
|
outfile.write("echo This message goes to stdout\n")
|
||||||
|
outfile.write("echo This message goes to stderr >&2")
|
||||||
|
|
||||||
|
# Make the file executable
|
||||||
|
st = os.stat(script.name)
|
||||||
|
os.chmod(script.name, st.st_mode | stat.S_IEXEC)
|
||||||
|
|
||||||
|
with override_settings(PRE_CONSUME_SCRIPT=script.name):
|
||||||
|
c = Consumer()
|
||||||
|
c.path = "path-to-file"
|
||||||
|
c.run_pre_consume_script()
|
||||||
|
|
||||||
|
mocked_log.assert_called()
|
||||||
|
|
||||||
|
mocked_log.assert_any_call("info", "This message goes to stdout")
|
||||||
|
mocked_log.assert_any_call("warning", "This message goes to stderr")
|
||||||
|
|
||||||
|
|
||||||
class PostConsumeTestCase(TestCase):
|
class PostConsumeTestCase(TestCase):
|
||||||
@mock.patch("documents.consumer.Popen")
|
@mock.patch("documents.consumer.run")
|
||||||
@override_settings(POST_CONSUME_SCRIPT=None)
|
@override_settings(POST_CONSUME_SCRIPT=None)
|
||||||
def test_no_post_consume_script(self, m):
|
def test_no_post_consume_script(self, m):
|
||||||
doc = Document.objects.create(title="Test", mime_type="application/pdf")
|
doc = Document.objects.create(title="Test", mime_type="application/pdf")
|
||||||
@ -858,7 +891,7 @@ class PostConsumeTestCase(TestCase):
|
|||||||
c.filename = "somefile.pdf"
|
c.filename = "somefile.pdf"
|
||||||
self.assertRaises(ConsumerError, c.run_post_consume_script, doc)
|
self.assertRaises(ConsumerError, c.run_post_consume_script, doc)
|
||||||
|
|
||||||
@mock.patch("documents.consumer.Popen")
|
@mock.patch("documents.consumer.run")
|
||||||
def test_post_consume_script_simple(self, m):
|
def test_post_consume_script_simple(self, m):
|
||||||
with tempfile.NamedTemporaryFile() as script:
|
with tempfile.NamedTemporaryFile() as script:
|
||||||
with override_settings(POST_CONSUME_SCRIPT=script.name):
|
with override_settings(POST_CONSUME_SCRIPT=script.name):
|
||||||
@ -868,7 +901,7 @@ class PostConsumeTestCase(TestCase):
|
|||||||
|
|
||||||
m.assert_called_once()
|
m.assert_called_once()
|
||||||
|
|
||||||
@mock.patch("documents.consumer.Popen")
|
@mock.patch("documents.consumer.run")
|
||||||
def test_post_consume_script_with_correspondent(self, m):
|
def test_post_consume_script_with_correspondent(self, m):
|
||||||
with tempfile.NamedTemporaryFile() as script:
|
with tempfile.NamedTemporaryFile() as script:
|
||||||
with override_settings(POST_CONSUME_SCRIPT=script.name):
|
with override_settings(POST_CONSUME_SCRIPT=script.name):
|
||||||
@ -889,7 +922,7 @@ class PostConsumeTestCase(TestCase):
|
|||||||
|
|
||||||
args, kwargs = m.call_args
|
args, kwargs = m.call_args
|
||||||
|
|
||||||
command = args[0]
|
command = kwargs["args"]
|
||||||
|
|
||||||
self.assertEqual(command[0], script.name)
|
self.assertEqual(command[0], script.name)
|
||||||
self.assertEqual(command[1], str(doc.pk))
|
self.assertEqual(command[1], str(doc.pk))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user