From 07c15ded771489ca18d819d9e0e1fa51ead7d285 Mon Sep 17 00:00:00 2001 From: Shekhar Date: Tue, 26 Mar 2013 12:11:40 +0530 Subject: [PATCH] user_check is useful function but it also needs sudo access. sudo access is primarily required to parse /etc/shadow. There are situations when - passwd is not required information - sudo access is not desired/possible So added need_passwd parameter to user_check(). - default is True to make sure existing use cases do not break - added tests with need_passwd=False user_check runs much faster :) --- src/cuisine.py | 26 +++++++++++++++----------- tests/all.py | 47 +++++++++++++++++++++++++++++------------------ 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/cuisine.py b/src/cuisine.py index 39fa522..3af8564 100644 --- a/src/cuisine.py +++ b/src/cuisine.py @@ -896,7 +896,7 @@ def python_package_ensure_pip(package=None, r=None, pip=None): path to a virtualenv. If provided, it will be added to the pip call. ''' #FIXME: At the moment, I do not know how to check for the existence of a pip package and - # I am not sure if this really makes sense, based on the pip built in functionality. + # I am not sure if this really makes sense, based on the pip built in functionality. # So I just call the install functions pip=pip or fabric.api.env.get('pip','pip') python_package_install_pip(package,r,pip) @@ -908,7 +908,7 @@ def python_package_remove_pip(package, E=None, pip=None): is equivalent to the "-r" parameter of pip. Either "package" or "r" needs to be provided The optional argument "E" is equivalent to the "-E" parameter of pip. E is the - path to a virtualenv. If provided, it will be added to the pip call. + path to a virtualenv. If provided, it will be added to the pip call. ''' pip=pip or fabric.api.env.get('pip','pip') return run('%s uninstall %s' %(pip,package)) @@ -934,7 +934,7 @@ def python_package_ensure_easy_install(package): The "package" argument, defines the name of the package that will be ensured. ''' #FIXME: At the moment, I do not know how to check for the existence of a py package and - # I am not sure if this really makes sense, based on the easy_install built in functionality. + # I am not sure if this really makes sense, based on the easy_install built in functionality. # So I just call the install functions python_package_install_easy_install(package) @@ -943,7 +943,7 @@ def python_package_remove_easy_install(package): The "package" argument, defines the name of the package that will be removed. ''' #FIXME: this will not remove egg file etc. - run('easy_install -m %s' %package) + run('easy_install -m %s' %package) # ============================================================================= # @@ -1007,25 +1007,29 @@ def user_create(name, passwd=None, home=None, uid=None, gid=None, shell=None, if passwd: user_passwd(name=name,passwd=passwd,encrypted_passwd=encrypted_passwd) -def user_check(name=None, uid=None): +def user_check(name=None, uid=None, need_passwd=True): """Checks if there is a user defined with the given name, returning its information as a '{"name":,"uid":,"gid":,"home":,"shell":}' - or 'None' if the user does not exists.""" + or 'None' if the user does not exists. + need_passwd (Boolean) indicates if password to be included in result or not. + If set to True it parses /etc/shadow and needs sudo access + """ assert name!=None or uid!=None, "user_check: either `uid` or `name` should be given" assert name is None or uid is None,"user_check: `uid` and `name` both given, only one should be provided" if name != None: - d = sudo("cat /etc/passwd | egrep '^%s:' ; true" % (name)) + d = run("cat /etc/passwd | egrep '^%s:' ; true" % (name)) elif uid != None: - d = sudo("cat /etc/passwd | egrep '^.*:.*:%s:' ; true" % (uid)) + d = run("cat /etc/passwd | egrep '^.*:.*:%s:' ; true" % (uid)) results = {} s = None if d: d = d.split(":") assert len(d) >= 7, "/etc/passwd entry is expected to have at least 7 fields, got %s in: %s" % (len(d), ":".join(d)) results = dict(name=d[0], uid=d[2], gid=d[3], fullname=d[4], home=d[5], shell=d[6]) - s = sudo("cat /etc/shadow | egrep '^%s:' | awk -F':' '{print $2}'" % (results['name'])) - if s: results['passwd'] = s + if need_passwd: + s = sudo("cat /etc/shadow | egrep '^%s:' | awk -F':' '{print $2}'" % (results['name'])) + if s: results['passwd'] = s if results: return results else: @@ -1152,7 +1156,7 @@ def ssh_keygen(user, keytype="dsa"): def ssh_authorize(user, key): """Adds the given key to the '.ssh/authorized_keys' for the given user.""" - d = user_check(user) + d = user_check(user, need_passwd=False) group = d["gid"] keyf = d["home"] + "/.ssh/authorized_keys" if key[-1] != "\n": diff --git a/tests/all.py b/tests/all.py index 7667476..b53c272 100644 --- a/tests/all.py +++ b/tests/all.py @@ -24,6 +24,17 @@ def testUserCheck( self ): assert cuisine.user_check(uid=user_data["uid"]) assert cuisine.user_check(uid=user_data["uid"])["name"] == user_data["name"] + def testUserCheckNeedPasswd( self ): + user_data = cuisine.user_check(USER, need_passwd=False) + user_data_with_passwd = cuisine.user_check(name=USER) + assert user_data + assert user_data["name"] == USER + assert 'passwd' in user_data_with_passwd + assert 'passwd' not in user_data + # We ensure that user_check works with uid and name + assert cuisine.user_check(uid=user_data["uid"], need_passwd=False) + assert cuisine.user_check(uid=user_data["uid"], need_passwd=False)["name"] == user_data["name"] + class Modes(unittest.TestCase): def testModeLocal( self ): @@ -63,7 +74,7 @@ def testModeSudo( self ): # with cd(tmpdir), cuisine.mode_sudo(): # cuisine.run('echo "test" > test.txt') # cuisine.run('chmod 0600 test.txt') -# +# # with cd(tmpdir), cuisine.mode_user(), settings(warn_only=True): # listing = cuisine.run('ls -la test.txt').split() # self.assertEqual('root', listing[2]) # user @@ -76,7 +87,7 @@ def testModeSudo( self ): # NOTE: Test disabled for now # class LocalExecution(unittest.TestCase): -# +# # def testFabricLocalCommands( self ): # ''' # Make sure local and lcd still work properly and that run and cd @@ -87,31 +98,31 @@ def testModeSudo( self ): # dir1 = os.path.join(tmpdir, 'test1') # dir2 = os.path.join(tmpdir, 'test2') # [os.mkdir(d) for d in [dir1, dir2]] -# +# # with cd(dir1), fabric.api.lcd(dir2): # file1 = os.path.join(dir1, 'test1.txt') # cuisine.run('touch %s' % file1) -# +# # file2 = os.path.join(dir2, 'test2.txt') # fabric.api.local('touch %s' % file2) -# +# # self.assertTrue(cuisine.file_exists(file2)) # finally: # shutil.rmtree(tmpdir) -# -# +# +# # def testResultAttributes( self ): # failing_command = 'cat /etc/shadow' # insufficient permissions # succeeding_command = 'uname -a' # erroneous_command = 'this-command-does-not-exist -a' -# +# # # A successful command should have the appropriate status # # attributes set # result = cuisine.run(succeeding_command) # self.assertTrue(result.succeeded) # self.assertFalse(result.failed) # self.assertEqual(result.return_code, 0) -# +# # # With warn_only set, we should be able to examine the result # # even if it fails # with settings(warn_only=True): @@ -122,29 +133,29 @@ def testModeSudo( self ): # self.assertEqual(result.return_code, 1) # self.assertIsNotNone(result.stderr) # self.assertIn('Permission denied', result.stderr) -# +# # # With warn_only off, failure should cause execution to abort # with settings(warn_only=False): # with self.assertRaises(SystemExit): # cuisine.run(failing_command) -# +# # # An erroneoneous command should fail similarly to fabric # with settings(warn_only=True): # result = cuisine.run(erroneous_command) # self.assertTrue(result.failed) # self.assertEqual(result.return_code, 127) -# +# # def testCd( self ): # with cd('/tmp'): # self.assertEqual(cuisine.run('pwd'), '/tmp') -# +# # def testShell( self ): -# # Ensure that env.shell is respected by setting it to the +# # Ensure that env.shell is respected by setting it to the # # 'exit' command and testing that it aborts. # with settings(use_shell=True, shell='exit'): # with self.assertRaises(SystemExit): # cuisine.run('ls') -# +# # def testSudoPrefix( self ): # # Ensure that env.sudo_prefix is respected by setting it to # # echo the command to stdout rather than executing it @@ -155,12 +166,12 @@ def testModeSudo( self ): # self.assertNotEqual(run_result.stdout, sudo_result.stdout) # self.assertIn(env.shell, sudo_result) # self.assertIn(cmd, sudo_result) -# +# # def testPath( self ): -# # Make sure the path is applied properly by setting it empty +# # Make sure the path is applied properly by setting it empty # # and making sure that stops a simple command from running # self.assertTrue(cuisine.run('ls').succeeded) -# +# # with fabric.api.path(' ', behavior='replace'), settings(warn_only=True): # result = cuisine.run('ls', combine_stderr=False) # self.assertTrue(result.failed)