0

I am still learning...

Using python I want to extract the version number from a shell output to determine if an upgrade is required.

I was able to use subprocess.call with shell=true, however i read this is a security issue and would like some advise on a better method. I then hit an AttributeError as it seems StrictVersion doesn't see the output as a integer, I think?

Here is what I am doing currently.

import subprocess
from distutils.version import StrictVersion


def updateAnsible():
    print 'Checking Ansible version'
    version = subprocess.call("ansible --version | grep 'ansible [0-9].[0-9].[0-9]' | awk '{ print $2 }'", shell=True)

    print version
    if StrictVersion(version) < StrictVersion('2.7.0'):
        print "Need to upgrade"
    else:
        print "Do not need to upgrade"

if __name__ == '__main__':
    updateAnsible()

I Expect the output of StrictVersion(version) to be 1.2.3

but what i get is the below

Checking Ansible version
1.2.3
Traceback (most recent call last):
0
  File "test.py", line 32, in <module>
    updateAnsible()
  File "test.py", line 26, in updateAnsible
    if StrictVersion(version) < StrictVersion('2.6.0'):
  File "python2.7/distutils/version.py", line 140, in __cmp__
    compare = cmp(self.version, other.version)
AttributeError: StrictVersion instance has no attribute 'version'

Process finished with exit code 1
5
  • No security issues here because you aren't using string concatenation to pass variables into the command run with shell=True. Well, no big ones; if the shell has startup-time vulnerabilities like shellshock was you'd still be in trouble, but it's not nearly as bad as most shell=True uses are. Commented Feb 8, 2019 at 22:12
  • ...anyhow, I'd argue that there are two separate concerns: The capture, and the version comparison. Ideally, they should be distinct questions. Commented Feb 8, 2019 at 22:14
  • The immediate problem is that your capture process isn't getting the output of your shell pipeline; instead, it's returning the exit status, and that exit status isn't a string that StrictVersion can parse. The output is just being written by grep straight to the stdout it inherited when your script was started up. Commented Feb 8, 2019 at 22:17
  • Use subprocess.check_output instead of subprocess.call to fix the AttributeError. Commented Feb 8, 2019 at 22:18
  • BTW, in [0-9].[0-9].[0-9], the .s aren't matched only against the period character -- they're wildcards in regex and match any character. And since it's common for a version number to be something like 2.14.3, assuming each segment will only be one digit isn't necessarily well-founded. (For that matter, 2.1.3rc5 is possible too, so you can't even assume digits-only if you want to be robust). Commented Feb 8, 2019 at 22:35

1 Answer 1

1

The immediate and narrow issue is that subprocess.call() returns exit status (which will be 0 if the grep didn't fail, or 1 if it did), not output. This can be worked around by using check_output() instead:

version = subprocess.check_output(
    "ansible --version | awk '/ansible [0-9].[0-9].[0-9]/ { print $2; exit }'", shell=True
).strip().decode('utf-8')

If you want to avoid shell=True (commendable, but not actually an immediate security issue in your current use case), that might look like:

import re

av = subprocess.check_output(['ansible', '--version'])
match = re.match('^ansible (\d+[.]\d+[.]\d+)$', av.split(b'\n')[0].decode('utf-8'))
if match is None:
  raise Exception("Unable to get version number from ansible")
version = match.group(1)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.