Rework the rest of the locations that used

the previous 'user' and make those locations
go through the new distros functions to select
the default user or the user list (depending on usage).

Adjust the tests to check the new 'default' field
that signifies the default user + test the new method
to extract just the default user from a normalized
user dictionary.
This commit is contained in:
Joshua Harlow 2012-09-28 13:53:56 -07:00
parent f599e720c7
commit bd8c4d84c2
9 changed files with 202 additions and 87 deletions

View File

@ -18,12 +18,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from cloudinit import distros as ds
from cloudinit import util from cloudinit import util
distros = ['ubuntu', 'debian'] distros = ['ubuntu', 'debian']
def handle(name, cfg, _cloud, log, args): def handle(name, cfg, cloud, log, args):
if len(args) != 0: if len(args) != 0:
value = args[0] value = args[0]
else: else:
@ -56,7 +57,8 @@ def handle(name, cfg, _cloud, log, args):
shcmd = "" shcmd = ""
if mod_user: if mod_user:
user = util.get_cfg_option_str(cfg, "user", "ubuntu") (users, _groups) = ds.normalize_users_groups(cfg, cloud.distro)
(user, _user_config) = ds.extract_default(users, 'ubuntu')
shcmd += " sudo -Hu \"%s\" byobu-launcher-%s" % (user, bl_inst) shcmd += " sudo -Hu \"%s\" byobu-launcher-%s" % (user, bl_inst)
shcmd += " || X=$(($X+1)); " shcmd += " || X=$(($X+1)); "
if mod_sys: if mod_sys:

View File

@ -20,6 +20,7 @@
import sys import sys
from cloudinit import distros as ds
from cloudinit import ssh_util from cloudinit import ssh_util
from cloudinit import util from cloudinit import util
@ -50,18 +51,10 @@ def handle(_name, cfg, cloud, log, args):
expire = util.get_cfg_option_bool(chfg, 'expire', expire) expire = util.get_cfg_option_bool(chfg, 'expire', expire)
if not plist and password: if not plist and password:
user = cloud.distro.get_default_user() (users, _groups) = ds.normalize_users_groups(cfg, cloud.distro)
(user, _user_config) = ds.extract_default(users)
if 'users' in cfg:
user_zero = cfg['users'][0]
if isinstance(user_zero, dict) and 'name' in user_zero:
user = user_zero['name']
if user: if user:
plist = "%s:%s" % (user, password) plist = "%s:%s" % (user, password)
else: else:
log.warn("No default or defined user to change password for.") log.warn("No default or defined user to change password for.")

View File

@ -21,6 +21,7 @@
import glob import glob
import os import os
from cloudinit import distros as ds
from cloudinit import ssh_util from cloudinit import ssh_util
from cloudinit import util from cloudinit import util
@ -102,16 +103,8 @@ def handle(_name, cfg, cloud, log, _args):
" %s to file %s"), keytype, keyfile) " %s to file %s"), keytype, keyfile)
try: try:
# TODO(utlemming): consolidate this stanza that occurs in: (users, _groups) = ds.normalize_users_groups(cfg, cloud.distro)
# cc_ssh_import_id, cc_set_passwords, maybe cc_users_groups.py (user, _user_config) = ds.extract_default(users)
user = cloud.distro.get_default_user()
if 'users' in cfg:
user_zero = cfg['users'][0]
if user_zero != "default":
user = user_zero
disable_root = util.get_cfg_option_bool(cfg, "disable_root", True) disable_root = util.get_cfg_option_bool(cfg, "disable_root", True)
disable_root_opts = util.get_cfg_option_str(cfg, "disable_root_opts", disable_root_opts = util.get_cfg_option_str(cfg, "disable_root_opts",
DISABLE_ROOT_OPTS) DISABLE_ROOT_OPTS)

View File

@ -41,8 +41,10 @@ def _gen_fingerprint(b64_text, hash_meth='md5'):
hasher = hashlib.new(hash_meth) hasher = hashlib.new(hash_meth)
hasher.update(base64.b64decode(b64_text)) hasher.update(base64.b64decode(b64_text))
return ":".join(_split_hash(hasher.hexdigest())) return ":".join(_split_hash(hasher.hexdigest()))
except TypeError: except (TypeError, ValueError):
# Raised when b64 not really b64... # Raised when b64 not really b64...
# or when the hash type is not really
# a known/supported hash type...
return '?' return '?'
@ -95,4 +97,5 @@ def handle(name, cfg, cloud, log, _args):
(users, _groups) = distros.normalize_users_groups(cfg, cloud.distro) (users, _groups) = distros.normalize_users_groups(cfg, cloud.distro)
for (user_name, _cfg) in users.items(): for (user_name, _cfg) in users.items():
(auth_key_fn, auth_key_entries) = extract_func(user_name, cloud.paths) (auth_key_fn, auth_key_entries) = extract_func(user_name, cloud.paths)
_pprint_key_entries(user_name, auth_key_fn, auth_key_entries, hash_meth) _pprint_key_entries(user_name, auth_key_fn,
auth_key_entries, hash_meth)

View File

@ -18,6 +18,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from cloudinit import distros as ds
from cloudinit import util from cloudinit import util
import pwd import pwd
@ -39,33 +40,27 @@ def handle(_name, cfg, cloud, log, args):
return return
# import for cloudinit created users # import for cloudinit created users
(users, _groups) = ds.normalize_users_groups(cfg, cloud.distro)
elist = [] elist = []
for user_cfg in cfg['users']: for (user, user_cfg) in users.items():
user = None
import_ids = [] import_ids = []
if user_cfg['default']:
if isinstance(user_cfg, str) and user_cfg == "default":
user = cloud.distro.get_default_user()
if not user:
continue
import_ids = util.get_cfg_option_list(cfg, "ssh_import_id", []) import_ids = util.get_cfg_option_list(cfg, "ssh_import_id", [])
else:
elif isinstance(user_cfg, dict):
user = None
import_ids = []
try: try:
user = user_cfg['name']
import_ids = user_cfg['ssh_import_id'] import_ids = user_cfg['ssh_import_id']
if import_ids and isinstance(import_ids, str):
import_ids = str(import_ids).split(',')
except: except:
log.debug("user %s is not configured for ssh_import" % user) log.debug("User %s is not configured for ssh_import_id", user)
continue continue
try:
import_ids = util.uniq_merge(import_ids)
import_ids = [str(i) for i in import_ids]
except:
log.debug("User %s is not correctly configured for ssh_import_id",
user)
continue
if not len(import_ids): if not len(import_ids):
continue continue

View File

@ -16,16 +16,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from cloudinit import distros from cloudinit import distros as ds
from cloudinit import util
from cloudinit.settings import PER_INSTANCE from cloudinit.settings import PER_INSTANCE
frequency = PER_INSTANCE frequency = PER_INSTANCE
def handle(name, cfg, cloud, log, _args): def handle(name, cfg, cloud, _log, _args):
(users, groups) = distros.normalize_users_groups(cfg, cloud.distro) (users, groups) = ds.normalize_users_groups(cfg, cloud.distro)
for (name, members) in groups.items(): for (name, members) in groups.items():
cloud.distro.create_group(name, members) cloud.distro.create_group(name, members)
for (user, config) in users.items(): for (user, config) in users.items():

View File

@ -24,6 +24,7 @@
from StringIO import StringIO from StringIO import StringIO
import abc import abc
import itertools
import os import os
import re import re
@ -187,8 +188,10 @@ class Distro(object):
'gecos': "%s" % (self.default_user.title()), 'gecos': "%s" % (self.default_user.title()),
'sudo': "ALL=(ALL) NOPASSWD:ALL", 'sudo': "ALL=(ALL) NOPASSWD:ALL",
} }
if self.default_user_groups: def_groups = self.default_user_groups
user_cfg['groups'] = _uniq_merge_sorted(self.default_user_groups) if not def_groups:
def_groups = []
user_cfg['groups'] = util.uniq_merge_sorted(def_groups)
return user_cfg return user_cfg
def create_user(self, name, **kwargs): def create_user(self, name, **kwargs):
@ -397,39 +400,27 @@ def _get_arch_package_mirror_info(package_mirrors, arch):
return default return default
def _uniq_merge_sorted(*lists): # Normalizes a input group configuration
return sorted(_uniq_merge(*lists)) # which can be a comma seperated list of
# group names, or a list of group names
# or a python dictionary of group names
def _uniq_merge(*lists): # to a list of members of that group.
combined_list = [] #
for a_list in lists: # The output is a dictionary of group
if isinstance(a_list, (str, basestring)): # names => members of that group which
a_list = a_list.strip().split(",") # is the standard form used in the rest
else: # of cloud-init
a_list = [str(a) for a in a_list]
a_list = [a.strip() for a in a_list if a.strip()]
combined_list.extend(a_list)
uniq_list = []
for a in combined_list:
if a in uniq_list:
continue
else:
uniq_list.append(a)
return uniq_list
def _normalize_groups(grp_cfg): def _normalize_groups(grp_cfg):
if isinstance(grp_cfg, (str, basestring, list)): if isinstance(grp_cfg, (str, basestring, list)):
c_grp_cfg = {} c_grp_cfg = {}
for i in _uniq_merge(grp_cfg): for i in util.uniq_merge(grp_cfg):
c_grp_cfg[i] = [] c_grp_cfg[i] = []
grp_cfg = c_grp_cfg grp_cfg = c_grp_cfg
groups = {} groups = {}
if isinstance(grp_cfg, (dict)): if isinstance(grp_cfg, (dict)):
for (grp_name, grp_members) in grp_cfg.items(): for (grp_name, grp_members) in grp_cfg.items():
groups[grp_name] = _uniq_merge_sorted(grp_members) groups[grp_name] = util.uniq_merge_sorted(grp_members)
else: else:
raise TypeError(("Group config must be list, dict " raise TypeError(("Group config must be list, dict "
" or string types only and not %s") % " or string types only and not %s") %
@ -437,6 +428,21 @@ def _normalize_groups(grp_cfg):
return groups return groups
# Normalizes a input group configuration
# which can be a comma seperated list of
# user names, or a list of string user names
# or a list of dictionaries with components
# that define the user config + 'name' (if
# a 'name' field does not exist then the
# default user is assumed to 'own' that
# configuration.
#
# The output is a dictionary of user
# names => user config which is the standard
# form used in the rest of cloud-init. Note
# the default user will have a special config
# entry 'default' which will be marked as true
# all other users will be marked as false.
def _normalize_users(u_cfg, def_user_cfg=None): def _normalize_users(u_cfg, def_user_cfg=None):
if isinstance(u_cfg, (dict)): if isinstance(u_cfg, (dict)):
ad_ucfg = [] ad_ucfg = []
@ -452,12 +458,12 @@ def _normalize_users(u_cfg, def_user_cfg=None):
" for key %s") % (util.obj_name(v), k)) " for key %s") % (util.obj_name(v), k))
u_cfg = ad_ucfg u_cfg = ad_ucfg
elif isinstance(u_cfg, (str, basestring)): elif isinstance(u_cfg, (str, basestring)):
u_cfg = _uniq_merge_sorted(u_cfg) u_cfg = util.uniq_merge_sorted(u_cfg)
users = {} users = {}
for user_config in u_cfg: for user_config in u_cfg:
if isinstance(user_config, (str, basestring, list)): if isinstance(user_config, (str, basestring, list)):
for u in _uniq_merge(user_config): for u in util.uniq_merge(user_config):
if u and u not in users: if u and u not in users:
users[u] = {} users[u] = {}
elif isinstance(user_config, (dict)): elif isinstance(user_config, (dict)):
@ -490,22 +496,59 @@ def _normalize_users(u_cfg, def_user_cfg=None):
# Fixup the default user into the real # Fixup the default user into the real
# default user name and replace it... # default user name and replace it...
def_user = None
if users and 'default' in users: if users and 'default' in users:
def_config = users.pop('default') def_config = users.pop('default')
if def_user_cfg: if def_user_cfg:
# Pickup what the default 'real name' is
# and any groups that are provided by the
# default config
def_user = def_user_cfg.pop('name') def_user = def_user_cfg.pop('name')
def_groups = def_user_cfg.pop('groups', []) def_groups = def_user_cfg.pop('groups', [])
# Pickup any config + groups for that user name
# that we may have previously extracted
parsed_config = users.pop(def_user, {}) parsed_config = users.pop(def_user, {})
users_groups = _uniq_merge_sorted(parsed_config.get('groups', []), parsed_groups = parsed_config.get('groups', [])
def_groups) # Now merge our extracted groups with
# anything the default config provided
users_groups = util.uniq_merge_sorted(parsed_groups, def_groups)
parsed_config['groups'] = ",".join(users_groups) parsed_config['groups'] = ",".join(users_groups)
# The real config for the default user is the
# combination of the default user config provided
# by the distro, the default user config provided
# by the above merging for the user 'default' and
# then the parsed config from the user's 'real name'
# which does not have to be 'default' (but could be)
users[def_user] = util.mergemanydict([def_user_cfg, users[def_user] = util.mergemanydict([def_user_cfg,
def_config, def_config,
parsed_config]) parsed_config])
# Ensure that only the default user that we
# found (if any) is actually marked as being
# the default user
if users:
for (uname, uconfig) in users.items():
if def_user and uname == def_user:
uconfig['default'] = True
else:
uconfig['default'] = False
return users return users
# Normalizes a set of user/users and group
# dictionary configuration into a useable
# format that the rest of cloud-init can
# understand using the default user
# provided by the input distrobution (if any)
# to allow for mapping of the 'default' user.
#
# Output is a dictionary of group names -> [member] (list)
# and a dictionary of user names -> user configuration (dict)
#
# If 'user' exists it will override
# the 'users'[0] entry (if a list) otherwise it will
# just become an entry in the returned dictionary (no override)
def normalize_users_groups(cfg, distro): def normalize_users_groups(cfg, distro):
if not cfg: if not cfg:
cfg = {} cfg = {}
@ -547,6 +590,33 @@ def normalize_users_groups(cfg, distro):
return (users, groups) return (users, groups)
# Given a user dictionary config it will
# extract the default user name and user config
# from that list and return that tuple or
# return (None, None) if no default user is
# found in the given input
def extract_default(users, default_name=None, default_config=None):
if not users:
users = {}
def safe_find(entry):
config = entry[1]
if not config or 'default' not in config:
return False
else:
return config['default']
tmp_users = users.items()
tmp_users = dict(itertools.ifilter(safe_find, tmp_users))
if not tmp_users:
return (default_name, default_config)
else:
name = tmp_users.keys()[0]
config = tmp_users[name]
config.pop('default', None)
return (name, config)
def fetch(name): def fetch(name):
locs = importer.find_module(name, locs = importer.find_module(name,
['', __name__], ['', __name__],

View File

@ -248,6 +248,36 @@ def read_conf(fname):
raise raise
# Merges X lists, and then keeps the
# unique ones, but orders by sort order
# instead of by the original order
def uniq_merge_sorted(*lists):
return sorted(uniq_merge(*lists))
# Merges X lists and then iterates over those
# and only keeps the unique items (order preserving)
# and returns that merged and uniqued list as the
# final result.
#
# Note: if any entry is a string it will be
# split on commas and empty entries will be
# evicted and merged in accordingly.
def uniq_merge(*lists):
combined_list = []
for a_list in lists:
if isinstance(a_list, (str, basestring)):
a_list = a_list.strip().split(",")
# Kickout the empty ones
a_list = [a for a in a_list if len(a)]
combined_list.extend(a_list)
uniq_list = []
for i in combined_list:
if i not in uniq_list:
uniq_list.append(i)
return uniq_list
def clean_filename(fn): def clean_filename(fn):
for (k, v) in FN_REPLACEMENTS.iteritems(): for (k, v) in FN_REPLACEMENTS.iteritems():
fn = fn.replace(k, v) fn = fn.replace(k, v)

View File

@ -119,8 +119,8 @@ class TestUGNormalize(MockerTestCase):
(users, _groups) = self._norm(ug_cfg, distro) (users, _groups) = self._norm(ug_cfg, distro)
self.assertIn('joe', users) self.assertIn('joe', users)
self.assertIn('bob', users) self.assertIn('bob', users)
self.assertEquals({}, users['joe']) self.assertEquals({'default': False}, users['joe'])
self.assertEquals({}, users['bob']) self.assertEquals({'default': False}, users['bob'])
def test_users_simple(self): def test_users_simple(self):
distro = self._make_distro('ubuntu') distro = self._make_distro('ubuntu')
@ -133,8 +133,8 @@ class TestUGNormalize(MockerTestCase):
(users, _groups) = self._norm(ug_cfg, distro) (users, _groups) = self._norm(ug_cfg, distro)
self.assertIn('joe', users) self.assertIn('joe', users)
self.assertIn('bob', users) self.assertIn('bob', users)
self.assertEquals({}, users['joe']) self.assertEquals({'default': False}, users['joe'])
self.assertEquals({}, users['bob']) self.assertEquals({'default': False}, users['bob'])
def test_users_old_user(self): def test_users_old_user(self):
distro = self._make_distro('ubuntu', 'bob') distro = self._make_distro('ubuntu', 'bob')
@ -179,8 +179,7 @@ class TestUGNormalize(MockerTestCase):
} }
(users, _groups) = self._norm(ug_cfg, distro) (users, _groups) = self._norm(ug_cfg, distro)
self.assertIn('zetta', users) self.assertIn('zetta', users)
ug_cfg = { ug_cfg = {}
}
(users, groups) = self._norm(ug_cfg, distro) (users, groups) = self._norm(ug_cfg, distro)
self.assertEquals({}, users) self.assertEquals({}, users)
self.assertEquals({}, groups) self.assertEquals({}, groups)
@ -198,6 +197,35 @@ class TestUGNormalize(MockerTestCase):
users['bob']['groups']) users['bob']['groups'])
self.assertEquals(True, self.assertEquals(True,
users['bob']['blah']) users['bob']['blah'])
self.assertEquals(True,
users['bob']['default'])
def test_users_dict_extract(self):
distro = self._make_distro('ubuntu', 'bob')
ug_cfg = {
'users': [
'default',
],
}
(users, _groups) = self._norm(ug_cfg, distro)
self.assertIn('bob', users)
(name, config) = distros.extract_default(users)
self.assertEquals(name, 'bob')
expected_config = {}
def_config = None
try:
def_config = distro.get_default_user()
except NotImplementedError:
pass
if not def_config:
def_config = {}
expected_config.update(def_config)
# Ignore these for now
expected_config.pop('name', None)
expected_config.pop('groups', None)
config.pop('groups', None)
self.assertEquals(config, expected_config)
def test_users_dict_default(self): def test_users_dict_default(self):
distro = self._make_distro('ubuntu', 'bob') distro = self._make_distro('ubuntu', 'bob')
@ -210,6 +238,8 @@ class TestUGNormalize(MockerTestCase):
self.assertIn('bob', users) self.assertIn('bob', users)
self.assertEquals(",".join(distro.get_default_user()['groups']), self.assertEquals(",".join(distro.get_default_user()['groups']),
users['bob']['groups']) users['bob']['groups'])
self.assertEquals(True,
users['bob']['default'])
def test_users_dict_trans(self): def test_users_dict_trans(self):
distro = self._make_distro('ubuntu') distro = self._make_distro('ubuntu')
@ -223,8 +253,8 @@ class TestUGNormalize(MockerTestCase):
(users, _groups) = self._norm(ug_cfg, distro) (users, _groups) = self._norm(ug_cfg, distro)
self.assertIn('joe', users) self.assertIn('joe', users)
self.assertIn('bob', users) self.assertIn('bob', users)
self.assertEquals({'tr_me': True}, users['joe']) self.assertEquals({'tr_me': True, 'default': False}, users['joe'])
self.assertEquals({}, users['bob']) self.assertEquals({'default': False}, users['bob'])
def test_users_dict(self): def test_users_dict(self):
distro = self._make_distro('ubuntu') distro = self._make_distro('ubuntu')
@ -237,5 +267,5 @@ class TestUGNormalize(MockerTestCase):
(users, _groups) = self._norm(ug_cfg, distro) (users, _groups) = self._norm(ug_cfg, distro)
self.assertIn('joe', users) self.assertIn('joe', users)
self.assertIn('bob', users) self.assertIn('bob', users)
self.assertEquals({}, users['joe']) self.assertEquals({'default': False}, users['joe'])
self.assertEquals({}, users['bob']) self.assertEquals({'default': False}, users['bob'])