[master 2/5] Allow the kickstart --groups list to specify GIDs.

dashea installerbot-noreply at redhat.com
Wed Sep 30 20:20:11 UTC 2015


From: David Shea <dshea at redhat.com>

This accomplishes several things:

 - All of the information that can be set in the user scope can now be
   encoded in the kickstart.

 - We can tell the difference between groups created by the kickstart
   group command, where existing groups should be rejected, and groups
   requested by the user --groups list, where existing groups should be
   accepted.

 - This removes the need for a lot of more terrible code in the user
   spoke, since the spoke can communicate with the advanced dialog with
   strings instead of specially crafted user objects. This effectively
   fixes #1226564.
---
 pyanaconda/ui/gui/spokes/user.glade |  1 +
 pyanaconda/ui/gui/spokes/user.py    | 86 +++++++------------------------------
 pyanaconda/users.py                 | 41 +++++++++++++-----
 3 files changed, 47 insertions(+), 81 deletions(-)

diff --git a/pyanaconda/ui/gui/spokes/user.glade b/pyanaconda/ui/gui/spokes/user.glade
index 335c3c7..e15c966 100644
--- a/pyanaconda/ui/gui/spokes/user.glade
+++ b/pyanaconda/ui/gui/spokes/user.glade
@@ -274,6 +274,7 @@
                         <property name="use_underline">True</property>
                         <property name="xalign">0</property>
                         <property name="draw_indicator">True</property>
+                        <signal name="toggled" handler="on_admin_toggled" swapped="no"/>
                       </object>
                       <packing>
                         <property name="left_attach">1</property>
diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py
index 4777da9..e21cc81 100644
--- a/pyanaconda/ui/gui/spokes/user.py
+++ b/pyanaconda/ui/gui/spokes/user.py
@@ -66,14 +66,13 @@ def _validateGroups(self, inputcheck):
 
         return InputCheck.CHECK_OK
 
-    def __init__(self, user, groupDict, data):
+    def __init__(self, user, data):
         GUIObject.__init__(self, data)
 
         self._saveButton = self.builder.get_object("save_button")
         GUIDialogInputCheckHandler.__init__(self, self._saveButton)
 
         self._user = user
-        self._groupDict = groupDict
 
         # Track whether the user has requested a home directory other
         # than the default.
@@ -110,23 +109,6 @@ def _apply_checkboxes(self, _editable=None, data=None):
         self._spinUid.set_sensitive(c_uid)
         self._spinGid.set_sensitive(c_gid)
 
-    def _parse_groups(self):
-        group_strings = self._tGroups.get_text().split(",")
-        group_objects = []
-
-        for group in group_strings:
-            # Skip empty strings
-            if not group:
-                continue
-
-            (group_name, group_id) = GROUPLIST_FANCY_PARSE.match(group).groups()
-            if group_id:
-                group_id = int(group_id)
-
-            group_objects.append(self.data.GroupData(name=group_name, gid=group_id))
-
-        return group_objects
-
     def refresh(self):
         if self._user.homedir:
             homedir = self._user.homedir
@@ -143,18 +125,7 @@ def refresh(self):
         self._spinUid.update()
         self._spinGid.update()
 
-        groups = []
-        for group_name in self._user.groups:
-            group = self._groupDict[group_name]
-
-            if group.name and group.gid is not None:
-                groups.append("%s (%d)" % (group.name, group.gid))
-            elif group.name:
-                groups.append(group.name)
-            elif group.gid is not None:
-                groups.append("(%d)" % (group.gid,))
-
-        self._tGroups.set_text(", ".join(groups))
+        self._tGroups.set_text(", ".join(self._user.groups))
 
     def run(self):
         self.window.show()
@@ -185,12 +156,7 @@ def run(self):
                     else:
                         self._user.gid = None
 
-                    groups = self._parse_groups()
-                    self._user.groups = []
-                    self._groupDict.clear()
-                    for group in groups:
-                        self._groupDict[group.name] = group
-                        self._user.groups.append(group.name)
+                    self._user.groups = [g.strip() for g in self._tGroups.get_text().split(",")]
                     break
                 # Input checks fail, try again
                 else:
@@ -268,8 +234,6 @@ def initialize(self):
             self._user = self.data.user.userList[0]
         else:
             self._user = self.data.UserData()
-        self._wheel = self.data.GroupData(name="wheel")
-        self._groupDict = {"wheel": self._wheel}
 
         # placeholders for the text boxes
         self.fullname = self.builder.get_object("t_fullname")
@@ -351,8 +315,7 @@ def initialize(self):
 
         self.add_re_check(self.fullname, GECOS_VALID, _("Full name cannot contain colon characters"))
 
-        self._advanced = AdvancedUserDialog(self._user, self._groupDict,
-                                            self.data)
+        self._advanced = AdvancedUserDialog(self._user, self.data)
         self._advanced.initialize()
 
     def refresh(self):
@@ -362,7 +325,7 @@ def refresh(self):
 
         self.username.set_text(self._user.name)
         self.fullname.set_text(self._user.gecos)
-        self.admin.set_active(self._wheel.name in self._user.groups)
+        self.admin.set_active("wheel" in self._user.groups)
 
         self.pw.emit("changed")
         self.confirm.emit("changed")
@@ -381,7 +344,7 @@ def refresh(self):
     def status(self):
         if len(self.data.user.userList) == 0:
             return _("No user will be created")
-        elif self._wheel.name in self.data.user.userList[0].groups:
+        elif "wheel" in self.data.user.userList[0].groups:
             return _("Administrator %s will be created") % self.data.user.userList[0].name
         else:
             return _("User %s will be created") % self.data.user.userList[0].name
@@ -415,28 +378,8 @@ def apply(self):
         self._user.name = self.username.get_text()
         self._user.gecos = self.fullname.get_text()
 
-        # Remove any groups that were created in a previous visit to this spoke
-        self.data.group.groupList = [g for g in self.data.group.groupList \
-                if not hasattr(g, 'anaconda_group')]
-
         # the user will be created only if the username is set
         if self._user.name:
-            if self.admin.get_active() and \
-               self._wheel.name not in self._user.groups:
-                self._user.groups.append(self._wheel.name)
-            elif not self.admin.get_active() and \
-                 self._wheel.name in self._user.groups:
-                self._user.groups.remove(self._wheel.name)
-
-            anaconda_groups = [self._groupDict[g] for g in self._user.groups
-                                if g != self._wheel.name]
-
-            self.data.group.groupList += anaconda_groups
-
-            # Flag the groups as being created in this spoke
-            for g in anaconda_groups:
-                g.anaconda_group = True
-
             if self._user not in self.data.user.userList:
                 self.data.user.userList.append(self._user)
 
@@ -530,6 +473,14 @@ def full_name_changed(self, editable=None, data=None):
             self.username.set_text(username)
             self.guesser[self.username] = True
 
+    def on_admin_toggled(self, togglebutton, data=None):
+        # Add or remove "wheel" from the grouplist on changes to the admin checkbox
+        if togglebutton.get_active():
+            if "wheel" not in self._user.groups:
+                self._user.groups.append("wheel")
+        elif "wheel" in self._user.groups:
+            self._user.groups.delete("wheel")
+
     def _checkPasswordEmpty(self, inputcheck):
         """Check whether a password has been specified at all.
 
@@ -637,18 +588,11 @@ def on_advanced_clicked(self, _button, data=None):
 
         self._user.name = self.username.get_text()
 
-        if self.admin.get_active() and \
-           self._wheel.name not in self._user.groups:
-            self._user.groups.append(self._wheel.name)
-        elif not self.admin.get_active() and \
-             self._wheel.name in self._user.groups:
-            self._user.groups.remove(self._wheel.name)
-
         self._advanced.refresh()
         with self.main_window.enlightbox(self._advanced.window):
             self._advanced.run()
 
-        self.admin.set_active(self._wheel.name in self._user.groups)
+        self.admin.set_active("wheel" in self._user.groups)
 
     def on_back_clicked(self, button):
         # If the failed check is for non-ASCII characters,
diff --git a/pyanaconda/users.py b/pyanaconda/users.py
index b7e9273..49820e20 100644
--- a/pyanaconda/users.py
+++ b/pyanaconda/users.py
@@ -33,6 +33,7 @@
 from pyanaconda.iutil import open   # pylint: disable=redefined-builtin
 from pyanaconda.constants import PASSWORD_MIN_LEN
 from pyanaconda.errors import errorHandler, PasswordCryptError, ERROR_RAISE
+from pyanaconda.regexes import GROUPLIST_FANCY_PARSE
 
 import logging
 log = logging.getLogger("anaconda")
@@ -167,14 +168,18 @@ def _getpwnam(self, user_name, root):
 
         return None
 
-    def _groupExists(self, group_name, root):
-        """Returns whether a group with the given name already exists."""
+    def _getgrnam(self, group_name, root):
+        """Like grp.getgrnam, but able to use a different root.
+
+            Just returns the grp structure as a list, same reason as above.
+        """
         with open(root + "/etc/group", "r") as f:
             for line in f:
-                if line.split(":")[0] == group_name:
-                    return True
+                fields = line.split(":")
+                if fields[0] == group_name:
+                    return fields
 
-        return False
+        return None
 
     @contextmanager
     def _ensureLoginDefs(self, root):
@@ -207,7 +212,7 @@ def createGroup(self, group_name, **kwargs):
         """
         root = kwargs.get("root", iutil.getSysroot())
 
-        if self._groupExists(group_name, root):
+        if self._getgrnam(group_name, root):
             raise ValueError("Group %s already exists" % group_name)
 
         args = ["-R", root]
@@ -232,8 +237,9 @@ def createUser(self, user_name, *args, **kwargs):
                               If none is given, the cryptPassword default is used.
            :keyword str gecos: The GECOS information (full name, office, phone, etc.).
                                Defaults to "".
-           :keyword groups: A list of existing group names the user should be
-                            added to.  Defaults to [].
+           :keyword groups: A list of group names the user should be added to.
+                            Each group name can contain an optional GID in parenthesis,
+                            such as "groupName(5000)". Defaults to [].
            :type groups: list of str
            :keyword str homedir: The home directory for the new user.  Defaults to
                                  /home/<name>.
@@ -271,8 +277,23 @@ def createUser(self, user_name, *args, **kwargs):
         else:
             args.append('-U')
 
-        if kwargs.get("groups"):
-            args.extend(['-G', ",".join(kwargs["groups"])])
+        # If any requested groups do not exist, create them.
+        group_list = []
+        for group in kwargs.get("groups", []):
+            group_name, gid = GROUPLIST_FANCY_PARSE.match(group).groups()
+            existing_group = self._getgrnam(group_name, root)
+
+            # Check for a bad GID request
+            if gid and existing_group and gid != existing_group[2]:
+                raise ValueError("Group %s already exists with GID %s" % (group_name, gid))
+
+            # Otherwise, create the group if it does not already exist
+            if not existing_group:
+                self.createGroup(group_name, gid=gid, root=root)
+            group_list.append(group_name)
+
+        if group_list:
+            args.extend(['-G', ",".join(group_list)])
 
         if kwargs.get("homedir"):
             homedir = kwargs["homedir"]


-- 
To view this commit on github, visit https://github.com/rhinstaller/anaconda/commit/31770d7dd369a4c553024d2c6f7b7747b219e047


More information about the anaconda-patches mailing list