diff options
Diffstat (limited to 'cloudinit/distros/__init__.py')
-rw-r--r-- | cloudinit/distros/__init__.py | 173 |
1 files changed, 116 insertions, 57 deletions
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 5eab780b..418421b9 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -21,12 +21,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -from StringIO import StringIO +import six +from six import StringIO import abc -import itertools import os import re +import stat from cloudinit import importer from cloudinit import log as logging @@ -36,6 +37,7 @@ from cloudinit import util from cloudinit.distros.parsers import hosts + OSFAMILIES = { 'debian': ['debian', 'ubuntu'], 'redhat': ['fedora', 'rhel'], @@ -73,6 +75,9 @@ class Distro(object): # to write this blob out in a distro format raise NotImplementedError() + def _write_network_config(self, settings): + raise NotImplementedError() + def _find_tz_file(self, tz): tz_file = os.path.join(self.tz_zone_dir, str(tz)) if not os.path.isfile(tz_file): @@ -88,6 +93,13 @@ class Distro(object): self._write_hostname(writeable_hostname, self.hostname_conf_fn) self._apply_hostname(writeable_hostname) + def uses_systemd(self): + try: + res = os.lstat('/run/systemd/system') + return stat.S_ISDIR(res.st_mode) + except: + return False + @abc.abstractmethod def package_command(self, cmd, args=None, pkgs=None): raise NotImplementedError() @@ -108,12 +120,11 @@ class Distro(object): arch = self.get_primary_arch() return _get_arch_package_mirror_info(mirror_info, arch) - def get_package_mirror_info(self, arch=None, - availability_zone=None): + def get_package_mirror_info(self, arch=None, data_source=None): # This resolves the package_mirrors config option # down to a single dict of {mirror_name: mirror_url} arch_info = self._get_arch_package_mirror_info(arch) - return _get_package_mirror_info(availability_zone=availability_zone, + return _get_package_mirror_info(data_source=data_source, mirror_info=arch_info) def apply_network(self, settings, bring_up=True): @@ -124,6 +135,14 @@ class Distro(object): return self._bring_up_interfaces(dev_names) return False + def apply_network_config(self, netconfig, bring_up=False): + # Write it out + dev_names = self._write_network_config(netconfig) + # Now try to bring them up + if bring_up: + return self._bring_up_interfaces(dev_names) + return False + @abc.abstractmethod def apply_locale(self, locale, out_fn=None): raise NotImplementedError() @@ -203,10 +222,19 @@ class Distro(object): # If the system hostname is different than the previous # one or the desired one lets update it as well - if (not sys_hostname) or (sys_hostname == prev_hostname - and sys_hostname != hostname): + if ((not sys_hostname) or (sys_hostname == prev_hostname and + sys_hostname != hostname)): update_files.append(sys_fn) + # If something else has changed the hostname after we set it + # initially, we should not overwrite those changes (we should + # only be setting the hostname once per instance) + if (sys_hostname and prev_hostname and + sys_hostname != prev_hostname): + LOG.info("%s differs from %s, assuming user maintained hostname.", + prev_hostname_fn, sys_fn) + return + # Remove duplicates (incase the previous config filename) # is the same as the system config filename, don't bother # doing it twice @@ -221,11 +249,6 @@ class Distro(object): util.logexc(LOG, "Failed to write hostname %s to %s", hostname, fn) - if (sys_hostname and prev_hostname and - sys_hostname != prev_hostname): - LOG.debug("%s differs from %s, assuming user maintained hostname.", - prev_hostname_fn, sys_fn) - # If the system hostname file name was provided set the # non-fqdn as the transient hostname. if sys_fn in update_files: @@ -272,12 +295,12 @@ class Distro(object): if header: contents.write("%s\n" % (header)) contents.write("%s\n" % (eh)) - util.write_file(self.hosts_fn, contents.getvalue(), mode=0644) + util.write_file(self.hosts_fn, contents.getvalue(), mode=0o644) def _bring_up_interface(self, device_name): cmd = ['ifup', device_name] LOG.debug("Attempting to run bring up interface %s using command %s", - device_name, cmd) + device_name, cmd) try: (_out, err) = util.subp(cmd) if len(err): @@ -307,6 +330,11 @@ class Distro(object): LOG.info("User %s already exists, skipping." % name) return + if 'create_groups' in kwargs: + create_groups = kwargs.pop('create_groups') + else: + create_groups = True + adduser_cmd = ['useradd', name] log_adduser_cmd = ['useradd', name] @@ -317,6 +345,7 @@ class Distro(object): "gecos": '--comment', "homedir": '--home', "primary_group": '--gid', + "uid": '--uid', "groups": '--groups', "passwd": '--password', "shell": '--shell', @@ -333,8 +362,21 @@ class Distro(object): redact_opts = ['passwd'] + groups = kwargs.get('groups') + if groups: + if isinstance(groups, (list, tuple)): + kwargs['groups'] = ",".join(groups) + else: + groups = groups.split(",") + + if create_groups: + for group in kwargs.get('groups').split(","): + if not util.is_group(group): + self.create_group(group) + LOG.debug("created group %s for user %s", name, group) + # Check the values and create the command - for key, val in kwargs.iteritems(): + for key, val in kwargs.items(): if key in adduser_opts and val and isinstance(val, str): adduser_cmd.extend([adduser_opts[key], val]) @@ -380,6 +422,10 @@ class Distro(object): if 'plain_text_passwd' in kwargs and kwargs['plain_text_passwd']: self.set_passwd(name, kwargs['plain_text_passwd']) + # Set password if hashed password is provided and non-empty + if 'hashed_passwd' in kwargs and kwargs['hashed_passwd']: + self.set_passwd(name, kwargs['hashed_passwd'], hashed=True) + # Default locking down the account. 'lock_passwd' defaults to True. # lock account unless lock_password is False. if kwargs.get('lock_passwd', True): @@ -393,7 +439,7 @@ class Distro(object): if 'ssh_authorized_keys' in kwargs: # Try to handle this in a smart manner. keys = kwargs['ssh_authorized_keys'] - if isinstance(keys, (basestring, str)): + if isinstance(keys, six.string_types): keys = [keys] if isinstance(keys, dict): keys = list(keys.values()) @@ -468,7 +514,7 @@ class Distro(object): util.make_header(base="added"), "#includedir %s" % (path), ''] sudoers_contents = "\n".join(lines) - util.write_file(sudo_base, sudoers_contents, 0440) + util.write_file(sudo_base, sudoers_contents, 0o440) else: lines = ['', util.make_header(base="added"), "#includedir %s" % (path), ''] @@ -478,7 +524,7 @@ class Distro(object): except IOError as e: util.logexc(LOG, "Failed to write %s", sudo_base) raise e - util.ensure_dir(path, 0750) + util.ensure_dir(path, 0o750) def write_sudo_rules(self, user, rules, sudo_file=None): if not sudo_file: @@ -491,7 +537,7 @@ class Distro(object): if isinstance(rules, (list, tuple)): for rule in rules: lines.append("%s %s" % (user, rule)) - elif isinstance(rules, (basestring, str)): + elif isinstance(rules, six.string_types): lines.append("%s %s" % (user, rules)) else: msg = "Can not create sudoers rule addition with type %r" @@ -506,7 +552,7 @@ class Distro(object): content, ] try: - util.write_file(sudo_file, "\n".join(contents), 0440) + util.write_file(sudo_file, "\n".join(contents), 0o440) except IOError as e: util.logexc(LOG, "Failed to write sudoers file %s", sudo_file) raise e @@ -517,8 +563,10 @@ class Distro(object): util.logexc(LOG, "Failed to append sudoers file %s", sudo_file) raise e - def create_group(self, name, members): + def create_group(self, name, members=None): group_add_cmd = ['groupadd', name] + if not members: + members = [] # Check if group exists, and then add it doesn't if util.is_group(name): @@ -528,21 +576,21 @@ class Distro(object): util.subp(group_add_cmd) LOG.info("Created new group %s" % name) except Exception: - util.logexc("Failed to create group %s", name) + util.logexc(LOG, "Failed to create group %s", name) # Add members to the group, if so defined if len(members) > 0: for member in members: if not util.is_user(member): LOG.warn("Unable to add group member '%s' to group '%s'" - "; user does not exist.", member, name) + "; user does not exist.", member, name) continue util.subp(['usermod', '-a', '-G', name, member]) LOG.info("Added user '%s' to group '%s'" % (member, name)) -def _get_package_mirror_info(mirror_info, availability_zone=None, +def _get_package_mirror_info(mirror_info, data_source=None, mirror_filter=util.search_for_mirror): # given a arch specific 'mirror_info' entry (from package_mirrors) # search through the 'search' entries, and fallback appropriately @@ -550,21 +598,28 @@ def _get_package_mirror_info(mirror_info, availability_zone=None, if not mirror_info: mirror_info = {} - ec2_az_re = ("^[a-z][a-z]-(%s)-[1-9][0-9]*[a-z]$" % - "north|northeast|east|southeast|south|southwest|west|northwest") + # ec2 availability zones are named cc-direction-[0-9][a-d] (us-east-1b) + # the region is us-east-1. so region = az[0:-1] + directions_re = '|'.join([ + 'central', 'east', 'north', 'northeast', 'northwest', + 'south', 'southeast', 'southwest', 'west']) + ec2_az_re = ("^[a-z][a-z]-(%s)-[1-9][0-9]*[a-z]$" % directions_re) subst = {} - if availability_zone: - subst['availability_zone'] = availability_zone + if data_source and data_source.availability_zone: + subst['availability_zone'] = data_source.availability_zone + + if re.match(ec2_az_re, data_source.availability_zone): + subst['ec2_region'] = "%s" % data_source.availability_zone[0:-1] - if availability_zone and re.match(ec2_az_re, availability_zone): - subst['ec2_region'] = "%s" % availability_zone[0:-1] + if data_source and data_source.region: + subst['region'] = data_source.region results = {} - for (name, mirror) in mirror_info.get('failsafe', {}).iteritems(): + for (name, mirror) in mirror_info.get('failsafe', {}).items(): results[name] = mirror - for (name, searchlist) in mirror_info.get('search', {}).iteritems(): + for (name, searchlist) in mirror_info.get('search', {}).items(): mirrors = [] for tmpl in searchlist: try: @@ -604,30 +659,30 @@ def _get_arch_package_mirror_info(package_mirrors, arch): # is the standard form used in the rest # of cloud-init def _normalize_groups(grp_cfg): - if isinstance(grp_cfg, (str, basestring)): + if isinstance(grp_cfg, six.string_types): grp_cfg = grp_cfg.strip().split(",") - if isinstance(grp_cfg, (list)): + if isinstance(grp_cfg, list): c_grp_cfg = {} for i in grp_cfg: - if isinstance(i, (dict)): + if isinstance(i, dict): for k, v in i.items(): if k not in c_grp_cfg: - if isinstance(v, (list)): + if isinstance(v, list): c_grp_cfg[k] = list(v) - elif isinstance(v, (basestring, str)): + elif isinstance(v, six.string_types): c_grp_cfg[k] = [v] else: raise TypeError("Bad group member type %s" % type_utils.obj_name(v)) else: - if isinstance(v, (list)): + if isinstance(v, list): c_grp_cfg[k].extend(v) - elif isinstance(v, (basestring, str)): + elif isinstance(v, six.string_types): c_grp_cfg[k].append(v) else: raise TypeError("Bad group member type %s" % type_utils.obj_name(v)) - elif isinstance(i, (str, basestring)): + elif isinstance(i, six.string_types): if i not in c_grp_cfg: c_grp_cfg[i] = [] else: @@ -635,7 +690,7 @@ def _normalize_groups(grp_cfg): type_utils.obj_name(i)) grp_cfg = c_grp_cfg groups = {} - if isinstance(grp_cfg, (dict)): + if isinstance(grp_cfg, dict): for (grp_name, grp_members) in grp_cfg.items(): groups[grp_name] = util.uniq_merge_sorted(grp_members) else: @@ -661,29 +716,29 @@ def _normalize_groups(grp_cfg): # 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): - if isinstance(u_cfg, (dict)): + if isinstance(u_cfg, dict): ad_ucfg = [] for (k, v) in u_cfg.items(): - if isinstance(v, (bool, int, basestring, str, float)): + if isinstance(v, (bool, int, float) + six.string_types): if util.is_true(v): ad_ucfg.append(str(k)) - elif isinstance(v, (dict)): + elif isinstance(v, dict): v['name'] = k ad_ucfg.append(v) else: raise TypeError(("Unmappable user value type %s" " for key %s") % (type_utils.obj_name(v), k)) u_cfg = ad_ucfg - elif isinstance(u_cfg, (str, basestring)): + elif isinstance(u_cfg, six.string_types): u_cfg = util.uniq_merge_sorted(u_cfg) users = {} for user_config in u_cfg: - if isinstance(user_config, (str, basestring, list)): + if isinstance(user_config, (list,) + six.string_types): for u in util.uniq_merge(user_config): if u and u not in users: users[u] = {} - elif isinstance(user_config, (dict)): + elif isinstance(user_config, dict): if 'name' in user_config: n = user_config.pop('name') prev_config = users.get(n) or {} @@ -784,11 +839,11 @@ def normalize_users_groups(cfg, distro): old_user = cfg['user'] # Translate it into the format that is more useful # going forward - if isinstance(old_user, (basestring, str)): + if isinstance(old_user, six.string_types): old_user = { 'name': old_user, } - if not isinstance(old_user, (dict)): + if not isinstance(old_user, dict): LOG.warn(("Format for 'user' key must be a string or " "dictionary and not %s"), type_utils.obj_name(old_user)) old_user = {} @@ -813,7 +868,7 @@ def normalize_users_groups(cfg, distro): default_user_config = util.mergemanydict([old_user, distro_user_config]) base_users = cfg.get('users', []) - if not isinstance(base_users, (list, dict, str, basestring)): + if not isinstance(base_users, (list, dict) + six.string_types): LOG.warn(("Format for 'users' key must be a comma separated string" " or a dictionary or a list and not %s"), type_utils.obj_name(base_users)) @@ -822,12 +877,12 @@ def normalize_users_groups(cfg, distro): if old_user: # Ensure that when user: is provided that this user # always gets added (as the default user) - if isinstance(base_users, (list)): + if isinstance(base_users, list): # Just add it on at the end... base_users.append({'name': 'default'}) - elif isinstance(base_users, (dict)): + elif isinstance(base_users, dict): base_users['default'] = dict(base_users).get('default', True) - elif isinstance(base_users, (str, basestring)): + elif isinstance(base_users, six.string_types): # Just append it on to be re-parsed later base_users += ",default" @@ -852,11 +907,11 @@ def extract_default(users, default_name=None, default_config=None): return config['default'] tmp_users = users.items() - tmp_users = dict(itertools.ifilter(safe_find, tmp_users)) + tmp_users = dict(filter(safe_find, tmp_users)) if not tmp_users: return (default_name, default_config) else: - name = tmp_users.keys()[0] + name = list(tmp_users)[0] config = tmp_users[name] config.pop('default', None) return (name, config) @@ -866,7 +921,7 @@ def fetch(name): locs, looked_locs = importer.find_module(name, ['', __name__], ['Distro']) if not locs: raise ImportError("No distribution found for distro %s (searched %s)" - % (name, looked_locs)) + % (name, looked_locs)) mod = importer.import_module(locs[0]) cls = getattr(mod, 'Distro') return cls @@ -877,5 +932,9 @@ def set_etc_timezone(tz, tz_file=None, tz_conf="/etc/timezone", util.write_file(tz_conf, str(tz).rstrip() + "\n") # This ensures that the correct tz will be used for the system if tz_local and tz_file: - util.copy(tz_file, tz_local) + # use a symlink if there exists a symlink or tz_local is not present + if os.path.islink(tz_local) or not os.path.exists(tz_local): + os.symlink(tz_file, tz_local) + else: + util.copy(tz_file, tz_local) return |