-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
[3006.x] module.run: fix result detection from returned dict #67115
base: 3006.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fixed result detection of module.run from returned dict |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -452,7 +452,7 @@ def _run(**kwargs): | |
func_ret = _call_function( | ||
_func, returner=kwargs.get("returner"), func_args=kwargs.get(func) | ||
) | ||
if not _get_result(func_ret, ret["changes"].get("ret", {})): | ||
if not _get_result(func_ret): | ||
if isinstance(func_ret, dict): | ||
failures.append( | ||
"'{}' failed: {}".format( | ||
|
@@ -658,31 +658,29 @@ def _legacy_run(name, **kwargs): | |
if kwargs["returner"] in returners: | ||
returners[kwargs["returner"]](ret_ret) | ||
ret["comment"] = f"Module function {name} executed" | ||
ret["result"] = _get_result(mret, ret["changes"]) | ||
ret["result"] = _get_result(mret) | ||
|
||
return ret | ||
|
||
|
||
def _get_result(func_ret, changes): | ||
def _get_result(func_ret): | ||
res = True | ||
# if mret is a dict and there is retcode and its non-zero | ||
if isinstance(func_ret, dict) and func_ret.get("retcode", 0) != 0: | ||
res = False | ||
# if its a boolean, return that as the result | ||
elif isinstance(func_ret, bool): | ||
# if mret a boolean, return that as the result | ||
if isinstance(func_ret, bool): | ||
res = func_ret | ||
else: | ||
changes_ret = changes.get("ret", {}) | ||
if isinstance(changes_ret, dict): | ||
if isinstance(changes_ret.get("result", {}), bool): | ||
res = changes_ret.get("result", {}) | ||
elif changes_ret.get("retcode", 0) != 0: | ||
res = False | ||
# Explore dict in depth to determine if there is a | ||
# 'result' key set to False which sets the global | ||
# state result. | ||
else: | ||
res = _get_dict_result(changes_ret) | ||
# if mret is a dict, check if certain keys exist | ||
elif isinstance(func_ret, dict): | ||
# if return key exists and is boolean, return that as the result | ||
if isinstance(func_ret.get("result", {}), bool): | ||
res = func_ret.get("result", {}) | ||
Comment on lines
+674
to
+675
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we determined the result is already in the dict, I recommend using if isinstance(func_ret.get("result"), bool):
res = func_ret["result"] alternatively we can use the newer python if isinstance(bool_res := func_ret.get("result"), bool):
res = bool_res There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I actually thought about that, but I didn't want to start refactoring the whole module.
While I think the assignment operator is pretty neat, I think it is better to not use it here. |
||
# if retcode exists and it is non-zero, return False | ||
elif func_ret.get("retcode", 0) != 0: | ||
hurzhurz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
res = False | ||
# Explore dict in depth to determine if there is a | ||
# 'result' key set to False which sets the global | ||
# state result. | ||
else: | ||
res = _get_dict_result(func_ret) | ||
|
||
return res | ||
|
||
|
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.
There is no longer an else case for when
func_ret
is not abool
nor adict
, the return will beTrue
. Thefunc_ret
not being abool
nordict
is unexpected so should throw an exception or at least returnFalse
. Also we are checkingisinstance(func_ret, dict)
multiple times (also on line 669). We should just check once and have thefunc_ret.get("retcode", 0) != 0
check be inside it.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.
There was not really a plain else case before, as the
else
had theif isinstance(changes_ret, dict)
inside which itself had no else case.And I think it is ok or even wanted to have it return
True
in such cases: Not every execution module function does give a clear successful/unsuccessful indication.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.
In that case I would expect
func_ret
to beNone
which I recommend we handle in the way you deem correct, but an unexpected data type should either return the truth/bool
value of it self (i.e.bool(func_ret)
so empty list[]
is False and list with value is True) or should throw an exception because we are getting an unexpected value. We should not do the worst case though of returningTrue
that function was successful it wasn't and returned a bad value.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.
Well, there seems to be no hard standard for the return of execution modules. Unlike state modules.
Returning
None
or[]
could simply mean that it had nothing to do.For example
saltutil.sync_modules
does return a list of synced modules. But if there was nothing to sync, the list is empty, which is totally fine and actually a success.However, there are some typical ways how some module functions indicate the succession: By a returning a bool or by dict keys
result
orretcode
. Everything else would be a guess.Using module.run is just some kind of a workaround anyway. If a proper determination of the result is needed, one should look for a state module or make one.
Also this PR is just meant to fix the limited (and often sufficient) way the function already had to determine the succession. Not to redesign it.