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

include userInGroup check in Lua modules when installation is group-restricted #2274

Merged
merged 20 commits into from
Aug 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a892709
Added robust check in lua
damianam Feb 28, 2017
ac31087
Added group check to the module generation
damianam Jul 12, 2017
90bac1f
Merge remote-tracking branch 'upstream/develop' into robust_group_check
damianam Jul 12, 2017
048da08
Added support for optional load error message if user is not in a giv…
damianam Jul 20, 2017
05ca470
Merge branch 'develop' of https://github.com/hpcugent/easybuild-frame…
damianam Jul 20, 2017
c0f9c36
Initial test
damianam Jul 20, 2017
981dcbe
Added missing toy eb file and check for correct lmod version
damianam Aug 8, 2017
95e4b96
Fixed check for minimal lmod version
damianam Aug 8, 2017
73188a2
Fixed check for minimal lmod version (now for real)
damianam Aug 9, 2017
1e767d9
clean up implementation for including user-in-group check in Lua modu…
boegel Aug 18, 2017
b142351
fix logic w.r.t. grabbing group spec
boegel Aug 18, 2017
27fe54b
fix check for known Lmod version
boegel Aug 18, 2017
00ebb39
fix hardcoded group name in test for group check
boegel Aug 18, 2017
bd45b00
fix grabbing group that we're a member of in test_toy_group_check
boegel Aug 18, 2017
25f6f32
fix typo in test
boegel Aug 18, 2017
6ed016f
Merge pull request #9 from boegel/robust_group_check
damianam Aug 18, 2017
258a3f9
correctly fix broken test_toy_group_check
boegel Aug 18, 2017
549c1d2
Merge pull request #10 from boegel/robust_group_check
damianam Aug 18, 2017
3a88a56
fix error for invalid group spec & enhance test to catch it
boegel Aug 21, 2017
0184479
Merge pull request #11 from boegel/robust_group_check
damianam Aug 21, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,15 @@ def __init__(self, ec):

# try and use the specified group (if any)
group_name = build_option('group')
if self.cfg['group'] is not None:
self.log.warning("Group spec '%s' is overriding config group '%s'." % (self.cfg['group'], group_name))
group_name = self.cfg['group']
group_spec = self.cfg['group']
if group_spec is not None:
if isinstance(group_spec, tuple):
if len(group_spec) == 2:
group_spec = group_spec[0]
else:
raise EasyBuildError("Found group spec in tuple format that is not a 2-tuple: %s", str(group_spec))
self.log.warning("Group spec '%s' is overriding config group '%s'." % (group_spec, group_name))
group_name = group_spec

self.group = None
if group_name is not None:
Expand Down Expand Up @@ -1163,6 +1169,22 @@ def make_module_extend_modpath(self):
self.log.debug("Not including module path extensions, as specified.")
return txt

def make_module_group_check(self):
"""
Create the necessary group check.
"""
group_error_msg = None
ec_group = self.cfg['group']
if ec_group is not None and isinstance(ec_group, tuple):
group_error_msg = ec_group[1]

if self.group is not None:
txt = self.module_generator.check_group(self.group[0], error_msg=group_error_msg)
else:
txt = ''

return txt

def make_module_req(self):
"""
Generate the environment-variables to run the module.
Expand Down Expand Up @@ -2189,6 +2211,7 @@ def make_module_step(self, fake=False):
txt += self.modules_header + '\n'

txt += self.make_module_description()
txt += self.make_module_group_check()
txt += self.make_module_dep()
txt += self.make_module_extend_modpath()
txt += self.make_module_req()
Expand Down
3 changes: 2 additions & 1 deletion easybuild/framework/easyconfig/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@
'osdependencies': [[], "OS dependencies that should be present on the system", DEPENDENCIES],

# LICENSE easyconfig parameters
'group': [None, "Name of the user group for which the software should be available", LICENSE],
'group': [None, "Name of the user group for which the software should be available; "
"format: string or 2-tuple with group name + custom error for users outside group", LICENSE],
'key': [None, 'Key for installing software', LICENSE],
'license_file': [None, 'License file for software', LICENSE],
'license_server': [None, 'License server for software', LICENSE],
Expand Down
49 changes: 49 additions & 0 deletions easybuild/tools/module_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@
:author: Pieter De Baets (Ghent University)
:author: Jens Timmerman (Ghent University)
:author: Fotis Georgatos (Uni.Lu, NTUA)
:author: Damian Alvarez (Forschungszentrum Juelich GmbH)
"""
import os
import re
import sys
import tempfile
from distutils.version import LooseVersion
from textwrap import wrap
from vsc.utils import fancylogger
from vsc.utils.missing import get_subclasses
Expand Down Expand Up @@ -193,6 +195,16 @@ def prepare(self, fake=False):

# From this point on just not implemented methods

def check_group(self, group, error_msg=None):
"""
Generate a check of the software group and the current user, and refuse to load the module if the user don't
belong to the group

:param group: string with the group name
:param error_msg: error message to print for users outside that group
"""
raise NotImplementedError

def comment(self, msg):
"""Return given string formatted as a comment."""
raise NotImplementedError
Expand Down Expand Up @@ -389,6 +401,17 @@ class ModuleGeneratorTcl(ModuleGenerator):
LOAD_REGEX = r"^\s*module\s+load\s+(\S+)"
LOAD_TEMPLATE = "module load %(mod_name)s"

def check_group(self, group, error_msg=None):
"""
Generate a check of the software group and the current user, and refuse to load the module if the user don't
belong to the group

:param group: string with the group name
:param error_msg: error message to print for users outside that group
"""
self.log.warning("Can't generate robust check in TCL modules for users belonging to group %s.", group)
return ''

def comment(self, msg):
"""Return string containing given message as a comment."""
return "# %s\n" % msg
Expand Down Expand Up @@ -639,6 +662,32 @@ class ModuleGeneratorLua(ModuleGenerator):
PATH_JOIN_TEMPLATE = 'pathJoin(root, "%s")'
PREPEND_PATH_TEMPLATE = 'prepend_path("%s", %s)'

def check_group(self, group, error_msg=None):
"""
Generate a check of the software group and the current user, and refuse to load the module if the user don't
belong to the group

:param group: string with the group name
:param error_msg: error message to print for users outside that group
"""
lmod_version = os.environ.get('LMOD_VERSION', 'NOT_FOUND')
min_lmod_version = '6.0.8'

if lmod_version != 'NOT_FOUND' and LooseVersion(lmod_version) >= LooseVersion(min_lmod_version):
if error_msg is None:
error_msg = "You are not part of '%s' group of users that have access to this software; " % group
error_msg += "Please consult with user support how to become a member of this group"

error_msg = 'LmodError("' + error_msg + '")'
res = self.conditional_statement('userInGroup("%s")' % group, error_msg, negative=True)
else:
warn_msg = "Can't generate robust check in Lua modules for users belonging to group %s. "
warn_msg += "Lmod version not recent enough (%s), should be >= %s"
self.log.warning(warn_msg, group, lmod_version, min_lmod_version)
res = ''

return res

def comment(self, msg):
"""Return string containing given message as a comment."""
return "-- %s\n" % msg
Expand Down
1 change: 0 additions & 1 deletion test/framework/module_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import os
import sys
import tempfile
from distutils.version import StrictVersion
from unittest import TextTestRunner, TestSuite
from vsc.utils.fancylogger import setLogLevelDebug, logToScreen
from vsc.utils.missing import nub
Expand Down
67 changes: 67 additions & 0 deletions test/framework/toy_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import stat
import sys
import tempfile
from distutils.version import LooseVersion
from test.framework.utilities import EnhancedTestCase, TestLoaderFiltered
from test.framework.package import mock_fpm
from unittest import TextTestRunner
Expand All @@ -49,6 +50,7 @@
from easybuild.tools.config import get_module_syntax, get_repositorypath
from easybuild.tools.filetools import adjust_permissions, mkdir, read_file, remove_file, which, write_file
from easybuild.tools.modules import Lmod
from easybuild.tools.run import run_cmd
from easybuild.tools.version import VERSION as EASYBUILD_VERSION


Expand Down Expand Up @@ -562,6 +564,71 @@ def test_toy_gid_sticky_bits(self):
self.assertTrue(perms & stat.S_ISGID, "gid bit set on %s" % fullpath)
self.assertTrue(perms & stat.S_ISVTX, "sticky bit set on %s" % fullpath)

def test_toy_group_check(self):
"""Test presence of group check in generated (Lua) modules"""
fd, dummylogfn = tempfile.mkstemp(prefix='easybuild-dummy', suffix='.log')
os.close(fd)

# figure out a group that we're a member of to use in the test
out, ec = run_cmd('groups', simple=False)
self.assertEqual(ec, 0, "Failed to select group to use in test")
group_name = out.split(' ')[0].strip()

toy_ec = os.path.join(os.path.dirname(__file__), 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0.eb')
test_ec = os.path.join(self.test_prefix, 'test.eb')
args = [
test_ec,
'--force',
'--module-only',
]

for group in [group_name, (group_name, "Hey, you're not in the '%s' group!" % group_name)]:

if isinstance(group, basestring):
write_file(test_ec, read_file(toy_ec) + "\ngroup = '%s'\n" % group)
else:
write_file(test_ec, read_file(toy_ec) + "\ngroup = %s\n" % str(group))
outtxt = self.eb_main(args, logfile=dummylogfn, do_build=True, raise_error=True, raise_systemexit=True)

if get_module_syntax() == 'Tcl':
pattern = "Can't generate robust check in TCL modules for users belonging to group %s." % group_name
regex = re.compile(pattern, re.M)
self.assertTrue(regex.search(outtxt), "Pattern '%s' found in: %s" % (regex.pattern, outtxt))

elif get_module_syntax() == 'Lua':
lmod_version = os.getenv('LMOD_VERSION', 'NOT_FOUND')
if LooseVersion(lmod_version) >= LooseVersion('6.0.8'):
toy_mod = os.path.join(self.test_installpath, 'modules', 'all', 'toy', '0.0.lua')
toy_mod_txt = read_file(toy_mod)

if isinstance(group, tuple):
group_name = group[0]
error_msg_pattern = "Hey, you're not in the '%s' group!" % group_name
else:
group_name = group
error_msg_pattern = "You are not part of '%s' group of users" % group_name

pattern = '\n'.join([
'^if not userInGroup\("%s"\) then' % group_name,
' LmodError\("%s[^"]*"\)' % error_msg_pattern,
'end$',
])
regex = re.compile(pattern, re.M)
self.assertTrue(regex.search(outtxt), "Pattern '%s' found in: %s" % (regex.pattern, toy_mod_txt))
else:
pattern = "Can't generate robust check in Lua modules for users belonging to group %s. "
pattern += "Lmod version not recent enough \(%s\), should be >= 6.0.8" % lmod_version
regex = re.compile(pattern % group_name, re.M)
self.assertTrue(regex.search(outtxt), "Pattern '%s' found in: %s" % (regex.pattern, outtxt))
else:
self.assertTrue(False, "Unknown module syntax: %s" % get_module_syntax())

write_file(test_ec, read_file(toy_ec) + "\ngroup = ('%s', 'custom message', 'extra item')\n" % group_name)
error_pattern = "Failed to get application instance.*: Found group spec in tuple format that is not a 2-tuple:"
self.assertErrorRegex(SystemExit, '.*', self.eb_main, args, do_build=True,
raise_error=True, raise_systemexit=True)


def test_allow_system_deps(self):
"""Test allow_system_deps easyconfig parameter."""
tmpdir = tempfile.mkdtemp()
Expand Down
4 changes: 2 additions & 2 deletions test/framework/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,10 +267,10 @@ def eb_main(self, args, do_build=False, return_error=False, logfile=None, verbos

try:
main(args=args, logfile=logfile, do_build=do_build, testing=testing, modtool=self.modtool)
except SystemExit:
except SystemExit as err:
if raise_systemexit:
raise err
except Exception, err:
except Exception as err:
myerr = err
if verbose:
print "err: %s" % err
Expand Down