-
Notifications
You must be signed in to change notification settings - Fork 23.7k
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
Fix ssh connection plugin hiding module errors with 255 exit code #41716
Conversation
@@ -159,7 +159,7 @@ def invoke_module(module, modlib_path, json_params): | |||
else: | |||
os.environ['PYTHONPATH'] = modlib_path | |||
|
|||
p = subprocess.Popen([%(interpreter)s, module], env=os.environ, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) | |||
p = subprocess.Popen([%(interpreter)s, '-m', module], env=os.environ, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) |
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.
wouldnt you be able to capture the 255 error here?
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.
Talked about this on slack and decided not too.
(1) Then it only works for ansiballz, nothing else (for instance, can't fix php).
(2) it introduces parsing and scraping into the wrapper which is a bad environment for debugging anything.
(3) the problem lies with the ssh connection plugin deciding that error code 255 is special so we have to teach the ssh connection plugin when error 255 is not special.
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.
its not the ssh connection plugin thinking that, it is ssh itself, the plugin just follows what the cli tool does.
I don't see why you need parsing since we already capture the exit code.
This also potentially has implications for one of the things I was recommending in the plugins redux doc about teaching Ansiballz to use relative imports for internal module_utils inside packaged modules (to allow other plugins to use module_utils as well without hardcoding the namespace). Relative imports and |
bugfixes: | ||
- If a module exited with error 255 When using the ssh connection plugin, the | ||
connection plugin would swallow the error and say we had an ssh error instead. | ||
The new code fixes this for select PHP errors and Python tracebacks. |
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.
PHP? PHP as a PHP language? 0_O
Where does that come from?
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.
Modules can be any language
@@ -347,7 +352,17 @@ def wrapped(self, *args, **kwargs): | |||
if return_tuple[0] != 255: | |||
break | |||
else: | |||
SSH_ERROR = True | |||
for signature in NOT_SSH_ERRORS: |
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 would be more elegant with any()
semantics + as efficient because of generator:
if not any((s in return_tuple[1]) for s in NOT_SSH_ERRORS):
raise AnsibleConnectionFailure("Failed to connect to the host via ssh: %s" % to_native(return_tuple[2]))
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.
or
if all((s not in return_tuple[1]) for s in NOT_SSH_ERRORS):
raise AnsibleConnectionFailure("Failed to connect to the host via ssh: %s" % to_native(return_tuple[2]))
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.
One liners are less readable than conditionals. In general we should prefer to use loops and if (sivel and I commented as much on one of your PRs a few weeks ago IIRC). This is also less efficient in general as it has to process every element in the data instead of breaking early. (Might be a wash here as there's only two elements and we're trading a loop in Python for a loop in C).
list comprehensions and generator expressions are good for list processing tasks. It's when they are used to combine loops and conditions that the code starts to smell (or if the amount of logic shoved into the list comprehension becomes too large... although I haven't figured out what the value of "too alre" should be yet).
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.
FTR since all()
accepts a generator expression it breaks early :)
@@ -347,7 +352,17 @@ def wrapped(self, *args, **kwargs): | |||
if return_tuple[0] != 255: |
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 you drop negation, it will be possible to drop else
part:
if return_tuple[0] == 255 and all((s not in return_tuple[1]) for s in NOT_SSH_ERRORS):
raise AnsibleConnectionFailure("Failed to connect to the host via ssh: %s" % to_native(return_tuple[2]))
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.
Cool, fixed that. I have to be careful... Every time I touch this, I want to use a for-else. But the double break on top of the for-else makes it confusing for people reading the code ;-)
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.
Cool, fixed that. I have to be careful... Every time I touch this, I want to use a for-else. But the double break on top of the for-else makes it confusing for people reading the code ;-)
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.
Honestly I was about to suggest using for-else and then I looked closer :)
@@ -297,6 +297,11 @@ | |||
display = Display() | |||
|
|||
|
|||
NOT_SSH_ERRORS = ('Traceback (most recent call last):', # Python-2.6 when there's an exception | |||
# while invoking a script via -m | |||
'PHP Parse error' # Php always returns error 255 |
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.
Could you please have a trailing comma here for the sake of better future diffs?
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.
Yep, will do
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.
Will do
* __file__ won't work if we want to invoke modules via -m or if we figure out how to keep modules from hitting the disk with pipelining. * module.tmpdir is the new way to place a file where it will be cleaned automatically. Change format string to not depend on __file__: * cloud/amazon/ec2_elb_lb.py * cloud/amazon/elb_classic_lb.py Use module.tempdir: * packaging/os/apt.py * files/unarchive.py
ae0c31d
to
05781d2
Compare
IMPORTANT NOTE: With this change, file referenced in the module changes. It becomes an "offset" into the zipfile (/path/to/ansible_modlib.zip/ansible_module_ping.py for instance). This is an incompatible change but it's not expected that most third party modules will be using that. We'll have to decide whether this is an acceptable backwards incompatible break. |
try: | ||
rsp, info = fetch_url(module, src) | ||
# If download fails, raise a proper exception | ||
if rsp is None: | ||
raise Exception(info['msg']) | ||
|
||
# open in binary mode for python3 | ||
f = open(package, 'wb') | ||
f = open(new_src, 'wb') |
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.
Mind also using a context manager?
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 be a separate PR.
SSH_ERROR = True | ||
for signature in NOT_SSH_ERRORS: | ||
if signature in return_tuple[1]: | ||
SSH_ERROR = False |
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 can raise from here.
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 don't see how. Could you explain?
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.
Oh.. I swapped the logic in my mind. Yeah, no way w/o else:
.
(I still think that semantically all()
reads more obvious)
CI failure in integration tests: https://app.shippable.com/github/ansible/ansible/runs/70845/24/tests |
05781d2
to
8fbc0db
Compare
The ssh connection plugin is overzealous in thinking that error code 255 can only come from ssh. Python can return 255 in some circumstances and error from php does as well.
8fbc0db
to
2c5a373
Compare
ELB changes look good to me |
Merged to devel for the 2.7.0 release |
Since we no longer have to support Python-2.4 on the client, we can nowuse -m to invoke the ansible module. This is possible because of the
changes to the -m switch in PEP-0338:
https://www.python.org/dev/peps/pep-0338/
The change to the ssh connection plugin is because it is overzealous in
thinking that error code 255 can only come from ssh. Python-2.6 can
return 255 in some circumstances and apparently any php error does as
well.
IMPORTANT NOTE: With this change,__file__
referenced in the module changes. It becomes an "offset" into the zipfile (/path/to/ansible_modlib.zip/ansible_module_ping.py for instance). This is an incompatible change but it's not expected that most third party modules will be using that. We'll have to decide whether this is an acceptable backwards incompatible break.We found a way to only invoke python once ( #41749 ) so the -m portion of this PR is no longer necessary.
The removal of file is still a nice cleanup and the ssh connection plugin fix for modules using exit code 255 is still good.
ISSUE TYPE
COMPONENT NAME
ANSIBLE VERSION