Skip to content
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

Merged
merged 2 commits into from
Jul 9, 2018

Conversation

abadger
Copy link
Contributor

@abadger abadger commented Jun 20, 2018

Since we no longer have to support Python-2.4 on the client, we can now
use -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
  • Bugfix Pull Request
COMPONENT NAME
  • executor/module_common.py
  • plugins/connection/ssh.py
ANSIBLE VERSION
devel

@ansibot ansibot added affects_2.7 This issue/PR affects Ansible v2.7 bug This issue/PR relates to a bug. needs_triage Needs a first human triage before being processed. support:core This issue/PR relates to code supported by the Ansible Engineering Team. labels Jun 20, 2018
@@ -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)
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

@nitzmahone
Copy link
Member

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 -m don't play nice together under many circumstances... Though since we're hacking up the path at execution time, we might be able to make it work. Would need to do a little experiementation.

@ansibot ansibot added the needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. label Jun 20, 2018
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.
Copy link
Member

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?

Copy link
Contributor Author

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:
Copy link
Member

@webknjaz webknjaz Jun 20, 2018

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]))

Copy link
Member

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]))

Copy link
Contributor Author

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).

Copy link
Member

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:
Copy link
Member

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]))

Copy link
Contributor Author

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 ;-)

Copy link
Contributor Author

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 ;-)

Copy link
Member

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
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, will do

Copy link
Contributor Author

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
@ansibot
Copy link
Contributor

ansibot commented Jun 20, 2018

@ansibot ansibot added aws cloud module This issue/PR relates to a module. support:certified This issue/PR relates to certified code. m:unarchive This issue/PR relates to the unarchive module. and removed needs_triage Needs a first human triage before being processed. labels Jun 20, 2018
@abadger
Copy link
Contributor Author

abadger commented Jun 20, 2018

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')
Copy link
Member

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?

Copy link
Contributor Author

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
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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)

@mattclay
Copy link
Member

CI failure in integration tests: https://app.shippable.com/github/ansible/ansible/runs/70845/24/tests

@mattclay mattclay added the ci_verified Changes made in this PR are causing tests to fail. label Jun 27, 2018
@abadger abadger changed the title Use -m to invoke the ansible module Fix ssh connection plugin hiding module errors with 255 exit code Jul 3, 2018
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.
@ansibot ansibot removed the ci_verified Changes made in this PR are causing tests to fail. label Jul 3, 2018
@ansibot ansibot added core_review In order to be merged, this PR must follow the core review workflow. and removed needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. labels Jul 3, 2018
@ryansb
Copy link
Contributor

ryansb commented Jul 9, 2018

ELB changes look good to me

@abadger abadger merged commit 673c55f into ansible:devel Jul 9, 2018
@abadger abadger deleted the ansiballz-less-disk branch July 9, 2018 22:51
@abadger
Copy link
Contributor Author

abadger commented Jul 9, 2018

Merged to devel for the 2.7.0 release

@ansible ansible locked and limited conversation to collaborators Jul 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
affects_2.7 This issue/PR affects Ansible v2.7 aws bug This issue/PR relates to a bug. cloud core_review In order to be merged, this PR must follow the core review workflow. m:unarchive This issue/PR relates to the unarchive module. module This issue/PR relates to a module. support:certified This issue/PR relates to certified code. support:core This issue/PR relates to code supported by the Ansible Engineering Team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants