diff options
Diffstat (limited to 'cloudinit/distros/rhel.py')
| -rw-r--r-- | cloudinit/distros/rhel.py | 287 | 
1 files changed, 133 insertions, 154 deletions
diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index d81ee5fb..bc0877d5 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -23,6 +23,10 @@  import os  from cloudinit import distros + +from cloudinit.distros.parsers.resolv_conf import ResolvConf +from cloudinit.distros.parsers.sys_conf import SysConf +  from cloudinit import helpers  from cloudinit import log as logging  from cloudinit import util @@ -31,32 +35,24 @@ from cloudinit.settings import PER_INSTANCE  LOG = logging.getLogger(__name__) -NETWORK_FN_TPL = '/etc/sysconfig/network-scripts/ifcfg-%s' - -# See: http://tiny.cc/6r99fw -# For what alot of these files that are being written -# are and the format of them - -# This library is used to parse/write -# out the various sysconfig files edited -# -# It has to be slightly modified though -# to ensure that all values are quoted -# since these configs are usually sourced into -# bash scripts... -from configobj import ConfigObj -# See: http://tiny.cc/oezbgw -D_QUOTE_CHARS = { -    "\"": "\\\"", -    "(": "\\(", -    ")": "\\)", -    "$": '\$', -    '`': '\`', -} +def _make_sysconfig_bool(val): +    if val: +        return 'yes' +    else: +        return 'no'  class Distro(distros.Distro): +    # See: http://tiny.cc/6r99fw +    clock_conf_fn = "/etc/sysconfig/clock" +    locale_conf_fn = '/etc/sysconfig/i18n' +    network_conf_fn = "/etc/sysconfig/network" +    hostname_conf_fn = "/etc/sysconfig/network" +    network_script_tpl = '/etc/sysconfig/network-scripts/ifcfg-%s' +    resolve_conf_fn = "/etc/resolv.conf" +    tz_local_fn = "/etc/localtime" +    tz_zone_dir = "/usr/share/zoneinfo"      def __init__(self, name, cfg, paths):          distros.Distro.__init__(self, name, cfg, paths) @@ -68,95 +64,110 @@ class Distro(distros.Distro):      def install_packages(self, pkglist):          self.package_command('install', pkglist) +    def _adjust_resolve(self, dns_servers, search_servers): +        try: +            r_conf = ResolvConf(util.load_file(self.resolve_conf_fn)) +            r_conf.parse() +        except IOError: +            util.logexc(LOG, +                        "Failed at parsing %s reverting to an empty instance", +                        self.resolve_conf_fn) +            r_conf = ResolvConf('') +            r_conf.parse() +        if dns_servers: +            for s in dns_servers: +                try: +                    r_conf.add_nameserver(s) +                except ValueError: +                    util.logexc(LOG, "Failed at adding nameserver %s", s) +        if search_servers: +            for s in search_servers: +                try: +                    r_conf.add_search_domain(s) +                except ValueError: +                    util.logexc(LOG, "Failed at adding search domain %s", s) +        util.write_file(self.resolve_conf_fn, str(r_conf), 0644) +      def _write_network(self, settings):          # TODO(harlowja) fix this... since this is the ubuntu format          entries = translate_network(settings)          LOG.debug("Translated ubuntu style network settings %s into %s",                    settings, entries)          # Make the intermediate format as the rhel format... +        nameservers = [] +        searchservers = [] +        dev_names = entries.keys()          for (dev, info) in entries.iteritems(): -            net_fn = NETWORK_FN_TPL % (dev) -            net_ro_fn = self._paths.join(True, net_fn) -            (prev_exist, net_cfg) = self._read_conf(net_ro_fn) -            net_cfg['DEVICE'] = dev -            boot_proto = info.get('bootproto') -            if boot_proto: -                net_cfg['BOOTPROTO'] = boot_proto -            net_mask = info.get('netmask') -            if net_mask: -                net_cfg["NETMASK"] = net_mask -            addr = info.get('address') -            if addr: -                net_cfg["IPADDR"] = addr -            if info.get('auto'): -                net_cfg['ONBOOT'] = 'yes' -            else: -                net_cfg['ONBOOT'] = 'no' -            gtway = info.get('gateway') -            if gtway: -                net_cfg["GATEWAY"] = gtway -            bcast = info.get('broadcast') -            if bcast: -                net_cfg["BROADCAST"] = bcast -            mac_addr = info.get('hwaddress') -            if mac_addr: -                net_cfg["MACADDR"] = mac_addr -            lines = net_cfg.write() -            if not prev_exist: -                lines.insert(0, '# Created by cloud-init') -            w_contents = "\n".join(lines) -            net_rw_fn = self._paths.join(False, net_fn) -            util.write_file(net_rw_fn, w_contents, 0644) +            net_fn = self.network_script_tpl % (dev) +            net_cfg = { +                'DEVICE': dev, +                'NETMASK': info.get('netmask'), +                'IPADDR': info.get('address'), +                'BOOTPROTO': info.get('bootproto'), +                'GATEWAY': info.get('gateway'), +                'BROADCAST': info.get('broadcast'), +                'MACADDR': info.get('hwaddress'), +                'ONBOOT': _make_sysconfig_bool(info.get('auto')), +            } +            self._update_sysconfig_file(net_fn, net_cfg) +            if 'dns-nameservers' in info: +                nameservers.extend(info['dns-nameservers']) +            if 'dns-search' in info: +                searchservers.extend(info['dns-search']) +        if nameservers or searchservers: +            self._adjust_resolve(nameservers, searchservers) +        if dev_names: +            net_cfg = { +                'NETWORKING': _make_sysconfig_bool(True), +            } +            self._update_sysconfig_file(self.network_conf_fn, net_cfg) +        return dev_names -    def set_hostname(self, hostname): -        out_fn = self._paths.join(False, '/etc/sysconfig/network') -        self._write_hostname(hostname, out_fn) -        if out_fn == '/etc/sysconfig/network': -            # Only do this if we are running in non-adjusted root mode -            LOG.debug("Setting hostname to %s", hostname) -            util.subp(['hostname', hostname]) +    def _update_sysconfig_file(self, fn, adjustments, allow_empty=False): +        if not adjustments: +            return +        (exists, contents) = self._read_conf(fn) +        updated_am = 0 +        for (k, v) in adjustments.items(): +            if v is None: +                continue +            v = str(v) +            if len(v) == 0 and not allow_empty: +                continue +            contents[k] = v +            updated_am += 1 +        if updated_am: +            lines = [ +                str(contents), +            ] +            if not exists: +                lines.insert(0, util.make_header()) +            util.write_file(fn, "\n".join(lines), 0644)      def apply_locale(self, locale, out_fn=None):          if not out_fn: -            out_fn = self._paths.join(False, '/etc/sysconfig/i18n') -        ro_fn = self._paths.join(True, '/etc/sysconfig/i18n') -        (_exists, contents) = self._read_conf(ro_fn) -        contents['LANG'] = locale -        w_contents = "\n".join(contents.write()) -        util.write_file(out_fn, w_contents, 0644) +            out_fn = self.locale_conf_fn +        locale_cfg = { +            'LANG': locale, +        } +        self._update_sysconfig_file(out_fn, locale_cfg)      def _write_hostname(self, hostname, out_fn): -        (_exists, contents) = self._read_conf(out_fn) -        contents['HOSTNAME'] = hostname -        w_contents = "\n".join(contents.write()) -        util.write_file(out_fn, w_contents, 0644) +        host_cfg = { +            'HOSTNAME': hostname, +        } +        self._update_sysconfig_file(out_fn, host_cfg) + +    def _select_hostname(self, hostname, fqdn): +        # See: http://bit.ly/TwitgL +        # Should be fqdn if we can use it +        if fqdn: +            return fqdn +        return hostname -    def update_hostname(self, hostname, prev_file): -        hostname_prev = self._read_hostname(prev_file) -        read_fn = self._paths.join(True, "/etc/sysconfig/network") -        hostname_in_sys = self._read_hostname(read_fn) -        update_files = [] -        if not hostname_prev or hostname_prev != hostname: -            update_files.append(prev_file) -        if (not hostname_in_sys or -            (hostname_in_sys == hostname_prev -             and hostname_in_sys != hostname)): -            write_fn = self._paths.join(False, "/etc/sysconfig/network") -            update_files.append(write_fn) -        for fn in update_files: -            try: -                self._write_hostname(hostname, fn) -            except: -                util.logexc(LOG, "Failed to write hostname %s to %s", -                            hostname, fn) -        if (hostname_in_sys and hostname_prev and -            hostname_in_sys != hostname_prev): -            LOG.debug(("%s differs from /etc/sysconfig/network." -                        " Assuming user maintained hostname."), prev_file) -        if "/etc/sysconfig/network" in update_files: -            # Only do this if we are running in non-adjusted root mode -            LOG.debug("Setting hostname to %s", hostname) -            util.subp(['hostname', hostname]) +    def _read_system_hostname(self): +        return (self.network_conf_fn, +                self._read_hostname(self.network_conf_fn))      def _read_hostname(self, filename, default=None):          (_exists, contents) = self._read_conf(filename) @@ -167,27 +178,34 @@ class Distro(distros.Distro):      def _read_conf(self, fn):          exists = False -        if os.path.isfile(fn): +        try:              contents = util.load_file(fn).splitlines()              exists = True -        else: +        except IOError:              contents = [] -        return (exists, QuotingConfigObj(contents)) +        return (exists, +                SysConf(contents)) + +    def _bring_up_interfaces(self, device_names): +        if device_names and 'all' in device_names: +            raise RuntimeError(('Distro %s can not translate ' +                                'the device name "all"') % (self.name)) +        return distros.Distro._bring_up_interfaces(self, device_names)      def set_timezone(self, tz): -        tz_file = os.path.join("/usr/share/zoneinfo", tz) +        # TODO(harlowja): move this code into +        # the parent distro... +        tz_file = os.path.join(self.tz_zone_dir, str(tz))          if not os.path.isfile(tz_file):              raise RuntimeError(("Invalid timezone %s,"                                  " no file found at %s") % (tz, tz_file))          # Adjust the sysconfig clock zone setting -        read_fn = self._paths.join(True, "/etc/sysconfig/clock") -        (_exists, contents) = self._read_conf(read_fn) -        contents['ZONE'] = tz -        tz_contents = "\n".join(contents.write()) -        write_fn = self._paths.join(False, "/etc/sysconfig/clock") -        util.write_file(write_fn, tz_contents) +        clock_cfg = { +            'ZONE': str(tz), +        } +        self._update_sysconfig_file(self.clock_conf_fn, clock_cfg)          # This ensures that the correct tz will be used for the system -        util.copy(tz_file, self._paths.join(False, "/etc/localtime")) +        util.copy(tz_file, self.tz_local_fn)      def package_command(self, command, args=None):          cmd = ['yum'] @@ -208,52 +226,7 @@ class Distro(distros.Distro):      def update_package_sources(self):          self._runner.run("update-sources", self.package_command, -                         ["update"], freq=PER_INSTANCE) - - -# This class helps adjust the configobj -# writing to ensure that when writing a k/v -# on a line, that they are properly quoted -# and have no spaces between the '=' sign. -# - This is mainly due to the fact that -# the sysconfig scripts are often sourced -# directly into bash/shell scripts so ensure -# that it works for those types of use cases. -class QuotingConfigObj(ConfigObj): -    def __init__(self, lines): -        ConfigObj.__init__(self, lines, -                           interpolation=False, -                           write_empty_values=True) - -    def _quote_posix(self, text): -        if not text: -            return '' -        for (k, v) in D_QUOTE_CHARS.iteritems(): -            text = text.replace(k, v) -        return '"%s"' % (text) - -    def _quote_special(self, text): -        if text.lower() in ['yes', 'no', 'true', 'false']: -            return text -        else: -            return self._quote_posix(text) - -    def _write_line(self, indent_string, entry, this_entry, comment): -        # Ensure it is formatted fine for -        # how these sysconfig scripts are used -        val = self._decode_element(self._quote(this_entry)) -        # Single quoted strings should -        # always work. -        if not val.startswith("'"): -            # Perform any special quoting -            val = self._quote_special(val) -        key = self._decode_element(self._quote(entry, multiline=False)) -        cmnt = self._decode_element(comment) -        return '%s%s%s%s%s' % (indent_string, -                               key, -                               "=", -                               val, -                               cmnt) +                         ["makecache"], freq=PER_INSTANCE)  # This is a util function to translate a ubuntu /etc/network/interfaces 'blob' @@ -314,6 +287,12 @@ def translate_network(settings):                  val = info[k].strip().lower()                  if val:                      iface_info[k] = val +        # Name server info provided?? +        if 'dns-nameservers' in info: +            iface_info['dns-nameservers'] = info['dns-nameservers'].split() +        # Name server search info provided?? +        if 'dns-search' in info: +            iface_info['dns-search'] = info['dns-search'].split()          # Is any mac address spoofing going on??          if 'hwaddress' in info:              hw_info = info['hwaddress'].lower().strip()  | 
