← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~smoser/cloud-init:feature/usermod-if-no-passwd into cloud-init:master


Scott Moser has proposed merging ~smoser/cloud-init:feature/usermod-if-no-passwd into cloud-init:master.

Commit message:
Support locking user with usermod if passwd is not available.

In some cases, the 'passwd' command might not be available, but
'usermod' might be.  In debian systems both are provided by the
'passwd' package.  In Redhat/Centos passwd comes from 'passwd' package
while 'usermod' comes from `shadow-utils`

This should just support either one with no real cost other than
the check.

Requested reviews:
  cloud-init commiters (cloud-init-dev)

For more details, see:

see commit message
Your team cloud-init commiters is requested to review the proposed merge of ~smoser/cloud-init:feature/usermod-if-no-passwd into cloud-init:master.
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index ef618c2..dc41375 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -577,11 +577,18 @@ class Distro(object):
         Lock the password of a user, i.e., disable password logins
+        lock_tools = (['passwd', '-l'], ['usermod', '--lock'])
+        cmds = [l for l in lock_tools if util.which(l[0])]
+        if not cmds:
+            raise RuntimeError((
+                "Unable to lock user account '%s'. No tools available. "
+                "  Tried: %s.") % (name, [c[0] for c in lock_tools]))
+        cmd = cmds[0]
             # Need to use the short option name '-l' instead of '--lock'
             # (which would be more descriptive) since SLES 11 doesn't know
             # about long names.
-            util.subp(['passwd', '-l', name])
+            util.subp(cmd + [name])
         except Exception as e:
             util.logexc(LOG, 'Failed to disable password for user %s', name)
             raise e
diff --git a/tests/unittests/test_distros/test_create_users.py b/tests/unittests/test_distros/test_create_users.py
index c3f258d..c5082d8 100644
--- a/tests/unittests/test_distros/test_create_users.py
+++ b/tests/unittests/test_distros/test_create_users.py
@@ -240,4 +240,30 @@ class TestCreateUser(CiTestCase):
             [mock.call(set(['auth1']), user),  # not disabled
              mock.call(set(['key1']), 'foouser', options=disable_prefix)])
+    @mock.patch("cloudinit.distros.util.which")
+    def test_lock_with_usermod_if_no_passwd(self, m_which, m_subp,
+                                           m_is_snappy):
+        """Lock with usermod --lock if now 'passwd' cmd available."""
+        m_which.side_effect = lambda m: m in ('usermod',)
+        self.dist.lock_passwd("bob")
+        self.assertEqual(
+            [mock.call(['usermod', '--lock', 'bob'])],
+            m_subp.call_args_list)
+    @mock.patch("cloudinit.distros.util.which")
+    def test_lock_with_passwd_if_available(self, m_which, m_subp,
+                                           m_is_snappy):
+        m_which.side_effect = lambda m: m in ('passwd',)
+        self.dist.lock_passwd("bob")
+        self.assertEqual(
+            [mock.call(['passwd', '-l', 'bob'])],
+            m_subp.call_args_list)
+    @mock.patch("cloudinit.distros.util.which")
+    def test_lock_raises_runtime_if_no_commands(self, m_which, m_subp,
+                                                m_is_snappy):
+        m_which.return_value = None
+        with self.assertRaises(RuntimeError):
+            self.dist.lock_passwd("bob")
 # vi: ts=4 expandtab

Follow ups