New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow the script handler to wait for the script #1307
Conversation
Script handles can now be configured to wait for the script to execute. The default behaviour is to put the script in the background. Closes #1222
Codecov Report
@@ Coverage Diff @@
## master #1307 +/- ##
=========================================
+ Coverage 95.81% 95.91% +0.1%
=========================================
Files 142 142
Lines 17233 17839 +606
=========================================
+ Hits 16512 17111 +599
- Misses 721 728 +7
Continue to review full report at Codecov.
|
script_name, e)) | ||
log.warning(traceback.format_exc()) | ||
if handler_options.get("raise_error"): | ||
raise e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is mergable, but just wanted to point out potential for code deduplication here: We could get rid of the outer if-branch and instead use
try:
p = subprocess.Popen(proc_args, cwd=self.script_directory)
log.info("Started script {script!r}:"
" {process!r}".format(script=script_name, process=p))
if handler_options.get("background") == SCRIPT_WAIT:
log.info("waiting for script to finish ...")
ret = p.wait()
# handle error case if ret != 0
...
except Exception as e:
log.warning("Failed to execute script {0!r}: {1!r}".format(
script_name, e))
log.warning(traceback.format_exc())
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was not aware of p.wait(). The question is, if it also raises an error accordingly?
Then we would - if raise_error
was configured - have to reraise the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, we would need to re-raise an error. We could, however, just raise a privacyIDEA ServerError
then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is, that different situations would be handled differently. p.wait()
does not raise an exception in case of an exit code <> 0.
The next problem is, that for some reason the tests do not work anymore - since the fail.sh
script somehow does not work :-/
script_name, e)) | ||
log.warning(traceback.format_exc()) | ||
if handler_options.get("raise_error"): | ||
raise e | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add an else
branch like
else:
log.warning("Invalid value: {!r}".format(handler_options.get("background")))
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but I will add an else branch, if raise_error is not set.
Then it would be great to have this merged ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
Closes #1222
"/etc/privacyidea/scripts") | ||
except RuntimeError: | ||
# In case of the tests we are outside of the application context | ||
script_directory = "tests/testdata/scripts" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be self.script_directory
:-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
"/etc/privacyidea/scripts") | ||
except RuntimeError: | ||
# In case of the tests we are outside of the application context | ||
script_directory = "tests/testdata/scripts" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
try: | ||
self.script_directory = get_from_config("PI_SCRIPT_HANDLER_DIRECTORY", | ||
"/etc/privacyidea/scripts") | ||
except RuntimeError: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use except RuntimeError as e:
since Python 3 doesn't allow the old syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in bea2309
def __init__(self, script_directory=None): | ||
if not script_directory: | ||
try: | ||
self.script_directory = get_from_config("PI_SCRIPT_HANDLER_DIRECTORY", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this really be get_from_config
, i.e. to read from the config
database table? From the look of the option, I would think it should rather be set in pi.cfg
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 5fc5322
"required": True, | ||
"description": _("Wait for script to complete or run script in background. This will " | ||
"either return the HTTP request early or could also block the request."), | ||
"value": [SCRIPT_BACKGROUND, SCRIPT_WAIT] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about changing the values of SCRIPT_BACKGROUND
and SCRIPT_WAIT
to "background" and "wait", respectively? We cannot localize the phrases "Run in background" and "Wait for script to finish" anyway, which would look weird in German.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 5fc5322
if rcode: | ||
log.warning("Script {script!r} failed to execute with error code {error!r}".format(script=script_name, | ||
error=rcode)) | ||
if handler_options.get("raise_error"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is missing a is_true
, I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. If raise error is set, then we will raise an error.
If it is not set, we will not raise an error - right? Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the option is disabled, handler_options.get("raise_error")
is actually the string "False"
, so we need to use is_true
. I forgot about that too :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Done in 83f6eaa
" {process!r}".format(script=script_name, process=p)) | ||
if handler_options.get("background") == SCRIPT_WAIT: | ||
rcode = p.wait() | ||
|
||
except Exception as e: | ||
log.warning("Failed to execute script {0!r}: {1!r}".format( | ||
script_name, e)) | ||
log.warning(traceback.format_exc()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we raise an error here if raise_error
is True?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We did not raise an error before. I would like to keep it that way.
Raising an error could result in the request to fail. But imho the script is often just an addon, which should not interfer with the functinoality of e.g. the authentication.
Imagine an event handler, that is supposed to write a backup to a SMB mount on every 100th auth request. Now the SMB is down or someone change a password or deleted the script.
Full denial of service of your authentication ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if I want to avoid that situation, I would probably set raise_error
to False :-) If the script is important enough for me that I set raise_error=True
, I would probably also want the request to fail if the script is deleted and cannot be executed because of that.
Anyway, I'm also OK with keeping this as it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are adding Feature requests in the reviews! ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently "raise_error" can only be set for "background" mode.
Should we change this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in e2b1d60 :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
try: | ||
self.script_directory = get_from_config("PI_SCRIPT_HANDLER_DIRECTORY", | ||
"/etc/privacyidea/scripts") | ||
except RuntimeError: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
rename defines for wait and background
"/etc/privacyidea/scripts") | ||
except RuntimeError: | ||
# In case of the tests we are outside of the application context | ||
script_directory = "tests/testdata/scripts" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"required": True, | ||
"description": _("Wait for script to complete or run script in background. This will " | ||
"either return the HTTP request early or could also block the request."), | ||
"value": [SCRIPT_BACKGROUND, SCRIPT_WAIT] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
script_name, e)) | ||
log.warning(traceback.format_exc()) | ||
if handler_options.get("raise_error"): | ||
raise e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
script_name, e)) | ||
log.warning(traceback.format_exc()) | ||
if handler_options.get("raise_error"): | ||
raise e | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def __init__(self, script_directory=None): | ||
if not script_directory: | ||
try: | ||
self.script_directory = get_from_config("PI_SCRIPT_HANDLER_DIRECTORY", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if rcode: | ||
log.warning("Script {script!r} failed to execute with error code {error!r}".format(script=script_name, | ||
error=rcode)) | ||
if handler_options.get("raise_error"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script handles can now be configured to wait for the
script to execute.
The default behaviour is to put the script in the background.
Closes #1222
94.83% <ø> (+0.02%)
81.57% <81.81%> (-5.31%)
34.24% <0%> (-9.59%)
94.16% <0%> (-1.67%)
99.84% <0%> (+0.43%)
87.7% <0%> (+0.81%)
97.33% <0%> (+0.88%)
97.64% <0%> (+0.99%)
91.91% <0%> (+1.01%)
Continue to review full report at Codecov.
What do you think?
Then we would - if
raise_error
was configured - have to reraise the error.ServerError
then.p.wait()
does not raise an exception in case of an exit code <> 0.The next problem is, that for some reason the tests do not work anymore - since the
fail.sh
script somehow does not work :-/else
branch like?
Then it would be great to have this merged ;-)
self.script_directory
:-)get_from_config
, i.e. to read from theconfig
database table? From the look of the option, I would think it should rather be set inpi.cfg
?except RuntimeError as e:
since Python 3 doesn't allow the old syntax.SCRIPT_BACKGROUND
andSCRIPT_WAIT
to "background" and "wait", respectively? We cannot localize the phrases "Run in background" and "Wait for script to finish" anyway, which would look weird in German.is_true
, I thinkIf it is not set, we will not raise an error - right? Or am I missing something?
handler_options.get("raise_error")
is actually the string"False"
, so we need to useis_true
. I forgot about that too :-)raise_error
is True?Raising an error could result in the request to fail. But imho the script is often just an addon, which should not interfer with the functinoality of e.g. the authentication.
Imagine an event handler, that is supposed to write a backup to a SMB mount on every 100th auth request. Now the SMB is down or someone change a password or deleted the script.
Full denial of service of your authentication ;-)
raise_error
to False :-) If the script is important enough for me that I setraise_error=True
, I would probably also want the request to fail if the script is deleted and cannot be executed because of that.Anyway, I'm also OK with keeping this as it is.
Should we change this?