diff options
Diffstat (limited to 'cloudinit/util.py')
-rw-r--r-- | cloudinit/util.py | 103 |
1 files changed, 86 insertions, 17 deletions
diff --git a/cloudinit/util.py b/cloudinit/util.py index 44ce9770..a8c0cceb 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -55,6 +55,7 @@ from cloudinit import url_helper as uhelp from cloudinit.settings import (CFG_BUILTIN) +_DNS_REDIRECT_IP = None LOG = logging.getLogger(__name__) # Helps cleanup filenames to ensure they aren't FS incompatible @@ -159,6 +160,10 @@ class MountFailedError(Exception): pass +class DecompressionError(Exception): + pass + + def ExtendedTemporaryFile(**kwargs): fh = tempfile.NamedTemporaryFile(**kwargs) # Replace its unlink with a quiet version @@ -256,13 +261,32 @@ def clean_filename(fn): return fn -def decomp_str(data): +def decomp_gzip(data, quiet=True): try: buf = StringIO(str(data)) with contextlib.closing(gzip.GzipFile(None, "rb", 1, buf)) as gh: return gh.read() - except: - return data + except Exception as e: + if quiet: + return data + else: + raise DecompressionError(str(e)) + + +def extract_usergroup(ug_pair): + if not ug_pair: + return (None, None) + ug_parted = ug_pair.split(':', 1) + u = ug_parted[0].strip() + if len(ug_parted) == 2: + g = ug_parted[1].strip() + else: + g = None + if not u or u == "-1" or u.lower() == "none": + u = None + if not g or g == "-1" or g.lower() == "none": + g = None + return (u, g) def find_modules(root_dir): @@ -288,8 +312,10 @@ def multi_log(text, console=True, stderr=True, wfh.write(text) wfh.flush() if log: - log.log(log_level, text) - + if text[-1] == "\n": + log.log(log_level, text[:-1]) + else: + log.log(log_level, text) def is_ipv4(instr): """ determine if input string is a ipv4 address. return boolean""" @@ -381,7 +407,16 @@ def fixup_output(cfg, mode): # # with a '|', arguments are passed to shell, so one level of # shell escape is required. +# +# if _CLOUD_INIT_SAVE_STDOUT is set in environment to a non empty and true +# value then output input will not be closed (useful for debugging). +# def redirect_output(outfmt, errfmt, o_out=None, o_err=None): + + if is_true(os.environ.get("_CLOUD_INIT_SAVE_STDOUT")): + LOG.debug("Not redirecting output due to _CLOUD_INIT_SAVE_STDOUT") + return + if not o_out: o_out = sys.stdout if not o_err: @@ -535,7 +570,7 @@ def runparts(dirp, skip_no_exist=True): if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK): attempted.append(exe_path) try: - subp([exe_path]) + subp([exe_path], capture=False) except ProcessExecutionError as e: logexc(LOG, "Failed running %s [%s]", exe_path, e.exit_code) failed.append(e) @@ -584,7 +619,10 @@ def load_yaml(blob, default=None, allowed=(dict,)): (allowed, obj_name(converted))) loaded = converted except (yaml.YAMLError, TypeError, ValueError): - logexc(LOG, "Failed loading yaml blob") + if len(blob) == 0: + LOG.debug("load_yaml given empty string, returning default") + else: + logexc(LOG, "Failed loading yaml blob") return loaded @@ -788,9 +826,43 @@ def get_cmdline_url(names=('cloud-config-url', 'url'), def is_resolvable(name): - """ determine if a url is resolvable, return a boolean """ + """ determine if a url is resolvable, return a boolean + This also attempts to be resilent against dns redirection. + + Note, that normal nsswitch resolution is used here. So in order + to avoid any utilization of 'search' entries in /etc/resolv.conf + we have to append '.'. + + The top level 'invalid' domain is invalid per RFC. And example.com + should also not exist. The random entry will be resolved inside + the search list. + """ + global _DNS_REDIRECT_IP # pylint: disable=W0603 + if _DNS_REDIRECT_IP is None: + badips = set() + badnames = ("does-not-exist.example.com.", "example.invalid.", + rand_str()) + badresults = {} + for iname in badnames: + try: + result = socket.getaddrinfo(iname, None, 0, 0, + socket.SOCK_STREAM, socket.AI_CANONNAME) + badresults[iname] = [] + for (_fam, _stype, _proto, cname, sockaddr) in result: + badresults[iname].append("%s: %s" % (cname, sockaddr[0])) + badips.add(sockaddr[0]) + except socket.gaierror: + pass + _DNS_REDIRECT_IP = badips + if badresults: + LOG.debug("detected dns redirection: %s" % badresults) + try: - socket.getaddrinfo(name, None) + result = socket.getaddrinfo(name, None) + # check first result's sockaddr field + addr = result[0][4][0] + if addr in _DNS_REDIRECT_IP: + return False return True except socket.gaierror: return False @@ -825,10 +897,10 @@ def close_stdin(): reopen stdin as /dev/null so even subprocesses or other os level things get /dev/null as input. - if _CLOUD_INIT_SAVE_STDIN is set in environment to a non empty or '0' value - then input will not be closed (only useful potentially for debugging). + if _CLOUD_INIT_SAVE_STDIN is set in environment to a non empty and true + value then input will not be closed (useful for debugging). """ - if os.environ.get("_CLOUD_INIT_SAVE_STDIN") in ("", "0", 'False'): + if is_true(os.environ.get("_CLOUD_INIT_SAVE_STDIN")): return with open(os.devnull) as fp: os.dup2(fp.fileno(), sys.stdin.fileno()) @@ -937,12 +1009,9 @@ def chownbyname(fname, user=None, group=None): uid = pwd.getpwnam(user).pw_uid if group: gid = grp.getgrnam(group).gr_gid - except KeyError: - logexc(LOG, ("Failed changing the ownership of %s using username %s " - "and groupname %s (do they exist?)"), fname, user, group) - return False + except KeyError as e: + raise OSError("Unknown user or group: %s" % (e)) chownbyid(fname, uid, gid) - return True # Always returns well formated values |