Skip to content

Fix fpm description by wrapping in shell quote#2124

Merged
boegel merged 13 commits intoeasybuilders:developfrom
rjeschmi:fpm-shellquote
Mar 3, 2017
Merged

Fix fpm description by wrapping in shell quote#2124
boegel merged 13 commits intoeasybuilders:developfrom
rjeschmi:fpm-shellquote

Conversation

@rjeschmi
Copy link
Copy Markdown
Contributor

Still need to write the test.

@rjeschmi
Copy link
Copy Markdown
Contributor Author

This will resolve #2014

Comment thread easybuild/tools/package/utilities.py Outdated
'--version', pkgver,
'--iteration', pkgrel,
'--description', quote_str(easyblock.cfg["description"]),
'--description', quote_str(eb_shell_quote(easyblock.cfg["description"])),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@rjeschmi do we need both quote_str and eb_shell_quote?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll check. I thought the quote_str would handle the outside wrapping, but it probably doesn't work the way I think it does.

The tests should help me figure it out I'd think.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You can see now that I've removed both quotes. If you pass the commands as a list it will handle doing the escaping properly.

It is necessary to pass each separate part of the command line as its own list entry though (so I had to also change the way I was handling the excludes and such).

I think I'm missing a test case...

@boegel boegel added this to the 3.2.0 milestone Feb 16, 2017
@rjeschmi
Copy link
Copy Markdown
Contributor Author

well the test is failing nicely, but it seems like both escapes are not enough :)

    raise EasyBuildError('cmd "%s" exited with exitcode %s and output:\n%s', cmd, ec, stdouterr)
EasyBuildError: 'cmd "fpm --workdir /tmp/eb-fveqiq/eb-OWKZv5/eb-fh53Jr/eb-CTbwdf/eb-pkgs-WRzb40 --name "toy-0.0-gompi-1.3.12-test" --provides "toy-0.0-gompi-1.3.12-test" -t rpm -s dir --version eb-3.1.0 --iteration 1 --description "\'Toy C program. Now with `backticks\\\'\'" --url "http://hpcugent.github.com/easybuild" --exclude "tmp/eb-fveqiq/eb-OWKZv5/tmpQlV2Qs/software/toy/0.0-gompi-1.3.12-test/easybuild/*.log" --exclude "tmp/eb-fveqiq/eb-OWKZv5/tmpQlV2Qs/software/toy/0.0-gompi-1.3.12-test/easybuild/*.md"  --depends "gompi-1.3.12" /tmp/eb-fveqiq/eb-OWKZv5/tmpQlV2Qs/software/toy/0.0-gompi-1.3.12-test /tmp/eb-fveqiq/eb-OWKZv5/tmpQlV2Qs/modules/all/toy/0.0-gompi-1.3.12-test.lua" exited with exitcode 1 and output:\n/bin/bash: -c: line 0: unexpected EOF while looking for matching ``\'\n/bin/bash: -c: line 1: syntax error: unexpected end of file\n'

Comment thread easybuild/tools/options.py Outdated
"""
# escape any non-escaped single quotes, and wrap entire token in single quotes
return "'%s'" % re.sub(r"(?<!\\)'", r"\'", str(token))
return "'%s'" % re.sub(r"(?<!\\)'", r"'\''", str(token))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think this is "more correct" but I'm no longer using this command so I could remove this from this PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if this is not relevant to this PR, please roll back (and maybe open a new PR for it)

@boegel boegel modified the milestones: 3.1.1, 3.2.0 Feb 28, 2017
@boegel
Copy link
Copy Markdown
Member

boegel commented Feb 28, 2017

@rjeschmi I plan to do a v3.1.1 bugfix release this week, any idea how close this is to being good to go?

@rjeschmi
Copy link
Copy Markdown
Contributor Author

I don't think it is too far. I'll maybe remove the changes to eb_shell_quote?

@rjeschmi
Copy link
Copy Markdown
Contributor Author

and you wanted me to write command lists to tuples right?

Comment thread easybuild/tools/options.py Outdated
"""
# escape any non-escaped single quotes, and wrap entire token in single quotes
return "'%s'" % re.sub(r"(?<!\\)'", r"\'", str(token))
return "'%s'" % re.sub(r"(?<!\\)'", r"'\''", str(token))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if this is not relevant to this PR, please roll back (and maybe open a new PR for it)

Comment thread easybuild/tools/package/utilities.py Outdated
This function will build a package using fpm and return the directory where the packages are
"""
# avoid circular import, should move eb_shell_quote
from easybuild.tools.options import eb_shell_quote
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

no longer used, so please remove?

Comment thread easybuild/tools/package/utilities.py Outdated
_log.debug("The list of excluded files passed to fpm: %s", exclude_files_glob)
# use env to find FPM https://stackoverflow.com/questions/5658622/python-subprocess-popen-environment-path
cmdlist = [
'/usr/bin/env',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this should be done by run_cmd in case shell=False is used?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

possibly, but do you think shell=false will rely on path in all cases?

Comment thread easybuild/tools/package/utilities.py Outdated
if build_option('debug'):
cmdlist.append('--debug')

cmdlist.extend(deplist)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should we move down the definition of deplist here, to keep things together?

Comment thread easybuild/tools/package/utilities.py Outdated
easyblock.module_generator.get_module_filepath(),
])
cmd = ' '.join(cmdlist)
print "cmd: %s" % cmdlist
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please remove, or add to debug log statement below

Comment thread easybuild/tools/package/utilities.py Outdated
print "cmd: %s" % cmdlist
_log.debug("The flattened cmdlist looks like: %s", cmd)
run_cmd(cmd, log_all=True, simple=True)
run_cmd(cmdlist, log_all=True, simple=True, cache=False, shell=False)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it's not immediately clear to me why you are disabling caching here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the cache doesn't work (in that it tries to hash a list, I will try to fix that, but this command is also not cacheable).

versionsuffix = '-test'

homepage = 'http://hpcugent.github.com/easybuild'
description = "Toy C program. Now with `backticks'"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

rather than adding a new easyconfig, can we just use toy-0.0.eb, read it, do:

toytxt = re.sub('description = .*', 'description = "Toy C program. Now with `backticks'", toytxt)

and then write it to a temporary file to use for the test?

Comment thread test/framework/package.py Outdated

ec_desc = EasyConfig(
os.path.join(test_easyconfigs, 't', 'toy', 'toy-0.0-gompi-1.3.12-test-description.eb'),
validate=False)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do a hard check on the description with the backtick in it, to make it clear why this is an important test case

Comment thread test/framework/package.py
validate=False)
easyblock_desc = EB_toy(ec_desc)
easyblock_desc.run_all_steps(False)
pkgdir = package(easyblock_desc)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

also check that the package is really there?

@rjeschmi
Copy link
Copy Markdown
Contributor Author

rjeschmi commented Mar 1, 2017

I think this resolves the review and it passed package test locally.

Comment thread easybuild/tools/package/utilities.py Outdated

_log.debug("Got the PNS values name: %s version: %s release: %s", pkgname, pkgver, pkgrel)
cmdlist = [
'/usr/bin/env',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@rjeschmi I really think this belongs in run_cmd...

Comment thread easybuild/tools/package/utilities.py Outdated
_log.debug("The dep added looks like %s ", dep)
dep_pkgname = package_naming_scheme.name(dep)
depstring += " --depends %s" % quote_str(dep_pkgname)
deplist.extend(["--depends", dep_pkgname])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why not just extend cmdlist?

also, the extend(deplist) below should be right below this for loop, having something unrelated in between is confusing (although it doesn't matter anymore if you just extend to cmdlist straight away)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It just kept it more like it was, but sure I can do it the other way now too since I have the cmdlist moved up.

Comment thread easybuild/tools/package/utilities.py Outdated
if build_option('debug'):
cmdlist.append('--debug')
_log.debug("The list of excluded files passed to fpm: %s", exclude_files_glob)
# use env to find FPM https://stackoverflow.com/questions/5658622/python-subprocess-popen-environment-path
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

comment is out of place?

Comment thread easybuild/tools/package/utilities.py Outdated
cmdlist.extend(exclude_files_glob)
exclude_files_cmd = []
for x in exclude_files_glob:
exclude_files_cmd.extend(['--exclude', os.path.join(easyblock.installdir.lstrip(os.sep), x)])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same here, just use cmdlist.extend?

Comment thread easybuild/tools/run.py
if cmd in cache:
_log.debug("Using cached value for command '%s': %s", cmd, cache[cmd])
return cache[cmd]
# fetch from cache if available, cache it if it's not, but only on cmd strings
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why only on strings?

if they're a list, you can just use tuple(cmd) as cache key?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You only cache based on strings. It seems safer to harden the code for what is expected rather than trying to handle all sorts of things that aren't necessary (including lists since none of the cached commands are lists at the moment and it doesn't work).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This one not done. Rest are done.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since we're only using strings in CACHED_COMMANDS, why do we need to check the type of cmd at all?

Since we never cache a non-string cmd right now, cmd in cache will always be False, and so we'll always call out to the actual func?

This looks like a case of premature optimisation here, especially for a command that's executed very rarely...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OK, ignore my rant, the cmd in cache will fail if cmd is of type list:

>>> [] in {}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Comment thread easybuild/tools/run.py
_log.info('running cmd: %s ' % cmd)
try:
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
p = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

prefix cmd with /usr/bin/env if shell is False (and take into account that cmd may be either a string or a list/tuple)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok, I worry that it will have side effects, but it should be backwards compatible anyway.

Comment thread test/framework/package.py Outdated
self.assertTrue(os.path.isfile(pkgfile))
pkgtxt = read_file(pkgfile)
regex_pkg_regex = re.compile(r"""DESCRIPTION:.*`backticks'.*""")
self.assertTrue(regex_pkg_regex.search(pkgtxt))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

self.assertTrue(regex_pkg_regex.search(pkgtxt), "Pattern '%s' not found in: %s" % (regex.pattern, pkgtxt))

@rjeschmi
Copy link
Copy Markdown
Contributor Author

rjeschmi commented Mar 2, 2017

I've handled the rest of the comments save the one about checking for string in the cache code. All of the commands in CACHED_COMMANDS are strings.

If it is really a useful feature to allow it I think you should rethink how cached commands are handled. Right now it works and is better performing since it doesn't uselessly convert the command list to a tuple to check against a constant list of strings.

Comment thread easybuild/tools/package/utilities.py Outdated
from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME
from easybuild.tools.utilities import import_available_modules, quote_str

from subprocess import list2cmdline
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@rjeschmi unused import?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah sorry I was using it to debug, should have checked pylint.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I actually removed quote_str as well since I no longer use it with the cmd list version.

Comment thread easybuild/tools/run.py
if cmd in cache:
_log.debug("Using cached value for command '%s': %s", cmd, cache[cmd])
return cache[cmd]
# fetch from cache if available, cache it if it's not, but only on cmd strings
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since we're only using strings in CACHED_COMMANDS, why do we need to check the type of cmd at all?

Since we never cache a non-string cmd right now, cmd in cache will always be False, and so we'll always call out to the actual func?

This looks like a case of premature optimisation here, especially for a command that's executed very rarely...

Comment thread easybuild/tools/run.py Outdated
if not shell and isinstance(cmd, list):
cmd.insert(0, '/usr/bin/env')
elif not shell and isinstance(cmd, str):
cmd = '/usr/bin/env %s' % cmd
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

# run command via 'env' if it is not run in a shell to trigger $PATH lookup of used command;
# cfr. <include-missing-reference>
if not shell:
    if isinstance(cmd, list):
        cmd.insert(0, '/usr/bin/env')
    elif isinstance(cmd, basestring):
        cmd = '/usr/bin/env ' + cmd
    else:
        raise EasyBuildError("Don't know how to prefix with /usr/bin/env for commands of type %s", type(cmd))

Comment thread easybuild/tools/run.py Outdated
return cache[cmd]
# fetch from cache if available, cache it if it's not, but only on cmd strings
if isinstance(cmd, str):
if cmd in cache:
Copy link
Copy Markdown
Member

@boegel boegel Mar 3, 2017

Choose a reason for hiding this comment

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

let's chain these together:

if isinstance(cmd, basestring) and cmd in cache:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

you want me to prefix the next conditional with isinstance as well? Just seems harder to read...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it'll avoid the indent and the extra else at the end which is just a trimmed-down version of the inner else

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok, thanks for the explanation on chat. I have it fixed now.

@rjeschmi
Copy link
Copy Markdown
Contributor Author

rjeschmi commented Mar 3, 2017

I think I got them all this time.

Comment thread test/framework/package.py Outdated
"debug": ('', 'on')[DEBUG],
"debug_fpm_file": os.path.join(tmpdir, DEBUG_FPM_FILE)}
)
"debug_fpm_file": os.path.join(tmpdir, DEBUG_FPM_FILE)})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

put }) on new line instead, indenting like ) was before

Comment thread test/framework/package.py Outdated
print read_file(pkgfile)

toy_txt = read_file(os.path.join(test_easyconfigs, 't', 'toy', 'toy-0.0-gompi-1.3.12-test.eb'))
replace_str = '''description = """Toy C program. Now with `backticks' ''' "\n" '''and newlines"""'''
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

since you're using triple quotes, you can just insert a hard newline, but then it looks icky due to left indent

you can also:

replace_str = '''description = """Toy C program. Now with `backticks'\n''''
replace_str += '''and newlines"""'''

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is a syntax error. I assume you mean one less '

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yes, first line has an extra trailing '... I didn't say I tested this ;)

Comment thread test/framework/package.py Outdated
"Pattern '%s' found in: %s" % (desc_backticks_regex.pattern, toy_txt))
ec_desc = EasyConfig(
toy_file,
validate=False)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

looks a bit funky here, not the usual code style

how about:

regex = re.compile(r"""`backticks'""")
self.assertTrue(regex.search(toy_txt), "Pattern '%s' found in: %s" % (regex.pattern, toy_txt))
ec_desc = EasyConfig(toy_file, validate=False)

@boegel
Copy link
Copy Markdown
Member

boegel commented Mar 3, 2017

Going in, thanks for the effort @rjeschmi!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants