# Copyright (C) 2012 Yahoo! Inc. # # Author: Joshua Harlow # # This file is part of cloud-init. See LICENSE file for license information. from io import StringIO from cloudinit import log as logging from cloudinit import util from cloudinit.distros.parsers import chop_comment LOG = logging.getLogger(__name__) # See: man resolv.conf class ResolvConf(object): def __init__(self, text): self._text = text self._contents = None def parse(self): if self._contents is None: self._contents = self._parse(self._text) @property def nameservers(self): self.parse() return self._retr_option("nameserver") @property def local_domain(self): self.parse() dm = self._retr_option("domain") if dm: return dm[0] return None @property def search_domains(self): self.parse() current_sds = self._retr_option("search") flat_sds = [] for sdlist in current_sds: for sd in sdlist.split(None): if sd: flat_sds.append(sd) return flat_sds def __str__(self): self.parse() contents = StringIO() for (line_type, components) in self._contents: if line_type == "blank": contents.write("\n") elif line_type == "all_comment": contents.write("%s\n" % (components[0])) elif line_type == "option": (cfg_opt, cfg_value, comment_tail) = components line = "%s %s" % (cfg_opt, cfg_value) if len(comment_tail): line += comment_tail contents.write("%s\n" % (line)) return contents.getvalue() def _retr_option(self, opt_name): found = [] for (line_type, components) in self._contents: if line_type == "option": (cfg_opt, cfg_value, _comment_tail) = components if cfg_opt == opt_name: found.append(cfg_value) return found def add_nameserver(self, ns): self.parse() current_ns = self._retr_option("nameserver") new_ns = list(current_ns) new_ns.append(str(ns)) new_ns = util.uniq_list(new_ns) if len(new_ns) == len(current_ns): return current_ns if len(current_ns) >= 3: LOG.warning( "ignoring nameserver %r: adding would " "exceed the maximum of " "'3' name servers (see resolv.conf(5))", ns, ) return current_ns[:3] self._remove_option("nameserver") for n in new_ns: self._contents.append(("option", ["nameserver", n, ""])) return new_ns def _remove_option(self, opt_name): def remove_opt(item): line_type, components = item if line_type != "option": return False (cfg_opt, _cfg_value, _comment_tail) = components if cfg_opt != opt_name: return False return True new_contents = [] for c in self._contents: if not remove_opt(c): new_contents.append(c) self._contents = new_contents def add_search_domain(self, search_domain): flat_sds = self.search_domains new_sds = list(flat_sds) new_sds.append(str(search_domain)) new_sds = util.uniq_list(new_sds) if len(flat_sds) == len(new_sds): return new_sds if len(flat_sds) >= 6: # Hard restriction on only 6 search domains raise ValueError( "Adding %r would go beyond the '6' maximum search domains" % (search_domain) ) s_list = " ".join(new_sds) if len(s_list) > 256: # Some hard limit on 256 chars total raise ValueError( "Adding %r would go beyond the " "256 maximum search list character limit" % (search_domain) ) self._remove_option("search") self._contents.append(("option", ["search", s_list, ""])) return flat_sds @local_domain.setter def local_domain(self, domain): self.parse() self._remove_option("domain") self._contents.append(("option", ["domain", str(domain), ""])) return domain def _parse(self, contents): entries = [] for (i, line) in enumerate(contents.splitlines()): sline = line.strip() if not sline: entries.append(("blank", [line])) continue (head, tail) = chop_comment(line, ";#") if not len(head.strip()): entries.append(("all_comment", [line])) continue if not tail: tail = "" try: (cfg_opt, cfg_values) = head.split(None, 1) except (IndexError, ValueError) as e: raise IOError( "Incorrectly formatted resolv.conf line %s" % (i + 1) ) from e if cfg_opt not in [ "nameserver", "domain", "search", "sortlist", "options", ]: raise IOError("Unexpected resolv.conf option %s" % (cfg_opt)) entries.append(("option", [cfg_opt, cfg_values, tail])) return entries # vi: ts=4 expandtab