summaryrefslogtreecommitdiff
path: root/cloudinit/util.py
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/util.py')
-rw-r--r--cloudinit/util.py103
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