diff options
author | zdc <zdc@users.noreply.github.com> | 2020-09-15 21:35:20 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-15 21:35:20 +0300 |
commit | 76adf82b8a4dbcf636151d292175b7d1ac182fcf (patch) | |
tree | f57f3db085a724df237ffa64b589c6bb6dd3b28f /cloudinit/util.py | |
parent | 1a790ee102fd405e5c3a20a17a69ba0c118ed874 (diff) | |
parent | 7cd260b313267dc7123cb99a75d4555e24909cca (diff) | |
download | vyos-cloud-init-76adf82b8a4dbcf636151d292175b7d1ac182fcf.tar.gz vyos-cloud-init-76adf82b8a4dbcf636151d292175b7d1ac182fcf.zip |
Merge pull request #18 from zdc/T2117-equuleus-20.3
T2117: Cloud-init updated to 20.3
Diffstat (limited to 'cloudinit/util.py')
-rw-r--r-- | cloudinit/util.py | 643 |
1 files changed, 190 insertions, 453 deletions
diff --git a/cloudinit/util.py b/cloudinit/util.py index c02b3d9a..cf9e349f 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -15,6 +15,7 @@ import glob import grp import gzip import hashlib +import io import json import os import os.path @@ -30,34 +31,23 @@ import string import subprocess import sys import time - -from errno import ENOENT, ENOEXEC - from base64 import b64decode, b64encode -from six.moves.urllib import parse as urlparse - -import six +from errno import ENOENT +from functools import lru_cache +from urllib import parse from cloudinit import importer from cloudinit import log as logging -from cloudinit import mergers -from cloudinit import safeyaml -from cloudinit import temp_utils -from cloudinit import type_utils -from cloudinit import url_helper -from cloudinit import version - -from cloudinit.settings import (CFG_BUILTIN) - -try: - from functools import lru_cache -except ImportError: - def lru_cache(): - """pass-thru replace for Python3's lru_cache()""" - def wrapper(f): - return f - return wrapper - +from cloudinit import subp +from cloudinit import ( + mergers, + safeyaml, + temp_utils, + type_utils, + url_helper, + version, +) +from cloudinit.settings import CFG_BUILTIN _DNS_REDIRECT_IP = None LOG = logging.getLogger(__name__) @@ -78,6 +68,10 @@ CONTAINER_TESTS = (['systemd-detect-virt', '--quiet', '--container'], ['lxc-is-container']) +def kernel_version(): + return tuple(map(int, os.uname().release.split('.')[:2])) + + @lru_cache() def get_dpkg_architecture(target=None): """Return the sanitized string output by `dpkg --print-architecture`. @@ -85,8 +79,8 @@ def get_dpkg_architecture(target=None): N.B. This function is wrapped in functools.lru_cache, so repeated calls won't shell out every time. """ - out, _ = subp(['dpkg', '--print-architecture'], capture=True, - target=target) + out, _ = subp.subp(['dpkg', '--print-architecture'], capture=True, + target=target) return out.strip() @@ -97,7 +91,8 @@ def lsb_release(target=None): data = {} try: - out, _ = subp(['lsb_release', '--all'], capture=True, target=target) + out, _ = subp.subp(['lsb_release', '--all'], capture=True, + target=target) for line in out.splitlines(): fname, _, val = line.partition(":") if fname in fmap: @@ -107,45 +102,23 @@ def lsb_release(target=None): LOG.warning("Missing fields in lsb_release --all output: %s", ','.join(missing)) - except ProcessExecutionError as err: + except subp.ProcessExecutionError as err: LOG.warning("Unable to get lsb_release --all: %s", err) data = dict((v, "UNAVAILABLE") for v in fmap.values()) return data -def target_path(target, path=None): - # return 'path' inside target, accepting target as None - if target in (None, ""): - target = "/" - elif not isinstance(target, six.string_types): - raise ValueError("Unexpected input for target: %s" % target) - else: - target = os.path.abspath(target) - # abspath("//") returns "//" specifically for 2 slashes. - if target.startswith("//"): - target = target[1:] - - if not path: - return target - - # os.path.join("/etc", "/foo") returns "/foo". Chomp all leading /. - while len(path) and path[0] == "/": - path = path[1:] - - return os.path.join(target, path) - - def decode_binary(blob, encoding='utf-8'): # Converts a binary type into a text type using given encoding. - if isinstance(blob, six.string_types): + if isinstance(blob, str): return blob return blob.decode(encoding) def encode_text(text, encoding='utf-8'): # Converts a text string into a binary type using given encoding. - if isinstance(text, six.binary_type): + if isinstance(text, bytes): return text return text.encode(encoding) @@ -175,8 +148,7 @@ def fully_decoded_payload(part): # bytes, first try to decode to str via CT charset, and failing that, try # utf-8 using surrogate escapes. cte_payload = part.get_payload(decode=True) - if (six.PY3 and - part.get_content_maintype() == 'text' and + if (part.get_content_maintype() == 'text' and isinstance(cte_payload, bytes)): charset = part.get_charset() if charset and charset.input_codec: @@ -213,91 +185,6 @@ DMIDECODE_TO_DMI_SYS_MAPPING = { } -class ProcessExecutionError(IOError): - - MESSAGE_TMPL = ('%(description)s\n' - 'Command: %(cmd)s\n' - 'Exit code: %(exit_code)s\n' - 'Reason: %(reason)s\n' - 'Stdout: %(stdout)s\n' - 'Stderr: %(stderr)s') - empty_attr = '-' - - def __init__(self, stdout=None, stderr=None, - exit_code=None, cmd=None, - description=None, reason=None, - errno=None): - if not cmd: - self.cmd = self.empty_attr - else: - self.cmd = cmd - - if not description: - if not exit_code and errno == ENOEXEC: - self.description = 'Exec format error. Missing #! in script?' - else: - self.description = 'Unexpected error while running command.' - else: - self.description = description - - if not isinstance(exit_code, six.integer_types): - self.exit_code = self.empty_attr - else: - self.exit_code = exit_code - - if not stderr: - if stderr is None: - self.stderr = self.empty_attr - else: - self.stderr = stderr - else: - self.stderr = self._indent_text(stderr) - - if not stdout: - if stdout is None: - self.stdout = self.empty_attr - else: - self.stdout = stdout - else: - self.stdout = self._indent_text(stdout) - - if reason: - self.reason = reason - else: - self.reason = self.empty_attr - - self.errno = errno - message = self.MESSAGE_TMPL % { - 'description': self._ensure_string(self.description), - 'cmd': self._ensure_string(self.cmd), - 'exit_code': self._ensure_string(self.exit_code), - 'stdout': self._ensure_string(self.stdout), - 'stderr': self._ensure_string(self.stderr), - 'reason': self._ensure_string(self.reason), - } - IOError.__init__(self, message) - - def _ensure_string(self, text): - """ - if data is bytes object, decode - """ - return text.decode() if isinstance(text, six.binary_type) else text - - def _indent_text(self, text, indent_level=8): - """ - indent text on all but the first line, allowing for easy to read output - """ - cr = '\n' - indent = ' ' * indent_level - # if input is bytes, return bytes - if isinstance(text, six.binary_type): - cr = cr.encode() - indent = indent.encode() - # remove any newlines at end of text first to prevent unneeded blank - # line in output - return text.rstrip(cr).replace(cr, cr + indent) - - class SeLinuxGuard(object): def __init__(self, path, recursive=False): # Late import since it might not always @@ -322,9 +209,6 @@ class SeLinuxGuard(object): return path = os.path.realpath(self.path) - # path should be a string, not unicode - if six.PY2: - path = str(path) try: stats = os.lstat(path) self.selinux.matchpathcon(path, stats[stat.ST_MODE]) @@ -369,7 +253,7 @@ def is_true(val, addons=None): check_set = TRUE_STRINGS if addons: check_set = list(check_set) + addons - if six.text_type(val).lower().strip() in check_set: + if str(val).lower().strip() in check_set: return True return False @@ -380,7 +264,7 @@ def is_false(val, addons=None): check_set = FALSE_STRINGS if addons: check_set = list(check_set) + addons - if six.text_type(val).lower().strip() in check_set: + if str(val).lower().strip() in check_set: return True return False @@ -441,7 +325,7 @@ def uniq_merge_sorted(*lists): def uniq_merge(*lists): combined_list = [] for a_list in lists: - if isinstance(a_list, six.string_types): + if isinstance(a_list, str): a_list = a_list.strip().split(",") # Kickout the empty ones a_list = [a for a in a_list if len(a)] @@ -464,7 +348,7 @@ def clean_filename(fn): def decomp_gzip(data, quiet=True, decode=True): try: - buf = six.BytesIO(encode_text(data)) + buf = io.BytesIO(encode_text(data)) with contextlib.closing(gzip.GzipFile(None, "rb", 1, buf)) as gh: # E1101 is https://github.com/PyCQA/pylint/issues/1444 if decode: @@ -475,7 +359,7 @@ def decomp_gzip(data, quiet=True, decode=True): if quiet: return data else: - raise DecompressionError(six.text_type(e)) + raise DecompressionError(str(e)) from e def extract_usergroup(ug_pair): @@ -533,18 +417,9 @@ def multi_log(text, console=True, stderr=True, log.log(log_level, text) -def is_ipv4(instr): - """determine if input string is a ipv4 address. return boolean.""" - toks = instr.split('.') - if len(toks) != 4: - return False - - try: - toks = [x for x in toks if 0 <= int(x) < 256] - except Exception: - return False - - return len(toks) == 4 +@lru_cache() +def is_BSD(): + return 'BSD' in platform.system() @lru_cache() @@ -552,6 +427,16 @@ def is_FreeBSD(): return system_info()['variant'] == "freebsd" +@lru_cache() +def is_NetBSD(): + return system_info()['variant'] == "netbsd" + + +@lru_cache() +def is_OpenBSD(): + return system_info()['variant'] == "openbsd" + + def get_cfg_option_bool(yobj, key, default=False): if key not in yobj: return default @@ -562,7 +447,7 @@ def get_cfg_option_str(yobj, key, default=None): if key not in yobj: return default val = yobj[key] - if not isinstance(val, six.string_types): + if not isinstance(val, str): val = str(val) return val @@ -625,10 +510,9 @@ def get_linux_distro(): flavor = match.groupdict()['codename'] if distro_name == 'rhel': distro_name = 'redhat' - elif os.path.exists('/bin/freebsd-version'): - distro_name = 'freebsd' - distro_version, _ = subp(['uname', '-r']) - distro_version = distro_version.strip() + elif is_BSD(): + distro_name = platform.system().lower() + distro_version = platform.release() else: dist = ('', '', '') try: @@ -656,7 +540,7 @@ def system_info(): 'system': platform.system(), 'release': platform.release(), 'python': platform.python_version(), - 'uname': platform.uname(), + 'uname': list(platform.uname()), 'dist': get_linux_distro() } system = info['system'].lower() @@ -664,18 +548,20 @@ def system_info(): if system == "linux": linux_dist = info['dist'][0].lower() if linux_dist in ( - 'arch', 'centos', 'debian', 'fedora', 'rhel', 'suse'): + 'alpine', 'arch', 'centos', 'debian', 'fedora', 'rhel', + 'suse'): var = linux_dist elif linux_dist in ('ubuntu', 'linuxmint', 'mint'): var = 'ubuntu' elif linux_dist == 'redhat': var = 'rhel' elif linux_dist in ( - 'opensuse', 'opensuse-tumbleweed', 'opensuse-leap', 'sles'): + 'opensuse', 'opensuse-tumbleweed', 'opensuse-leap', + 'sles', 'sle_hpc'): var = 'suse' else: var = 'linux' - elif system in ('windows', 'darwin', "freebsd"): + elif system in ('windows', 'darwin', "freebsd", "netbsd", "openbsd"): var = system info['variant'] = var @@ -703,7 +589,7 @@ def get_cfg_option_list(yobj, key, default=None): if isinstance(val, (list)): cval = [v for v in val] return cval - if not isinstance(val, six.string_types): + if not isinstance(val, str): val = str(val) return [val] @@ -724,7 +610,7 @@ def get_cfg_by_path(yobj, keyp, default=None): @return: The value of the item at keyp." is not found.""" - if isinstance(keyp, six.string_types): + if isinstance(keyp, str): keyp = keyp.split("/") cur = yobj for tok in keyp: @@ -822,7 +708,7 @@ def make_url(scheme, host, port=None, pieces.append(query or '') pieces.append(fragment or '') - return urlparse.urlunparse(pieces) + return parse.urlunparse(pieces) def mergemanydict(srcs, reverse=False): @@ -869,37 +755,6 @@ def del_dir(path): shutil.rmtree(path) -def runparts(dirp, skip_no_exist=True, exe_prefix=None): - if skip_no_exist and not os.path.isdir(dirp): - return - - failed = [] - attempted = [] - - if exe_prefix is None: - prefix = [] - elif isinstance(exe_prefix, str): - prefix = [str(exe_prefix)] - elif isinstance(exe_prefix, list): - prefix = exe_prefix - else: - raise TypeError("exe_prefix must be None, str, or list") - - for exe_name in sorted(os.listdir(dirp)): - exe_path = os.path.join(dirp, exe_name) - if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK): - attempted.append(exe_path) - try: - subp(prefix + [exe_path], capture=False) - except ProcessExecutionError as e: - logexc(LOG, "Failed running %s [%s]", exe_path, e.exit_code) - failed.append(e) - - if failed and attempted: - raise RuntimeError('Runparts: %s failures in %s attempted commands' - % (len(failed), len(attempted))) - - # read_optional_seed # returns boolean indicating success or failure (presense of files) # if files are present, populates 'fill' dictionary with 'user-data' and @@ -1031,7 +886,7 @@ def read_conf_with_confd(cfgfile): if "conf_d" in cfg: confd = cfg['conf_d'] if confd: - if not isinstance(confd, six.string_types): + if not isinstance(confd, str): raise TypeError(("Config file %s contains 'conf_d' " "with non-string type %s") % (cfgfile, type_utils.obj_name(confd))) @@ -1049,7 +904,7 @@ def read_conf_with_confd(cfgfile): def read_conf_from_cmdline(cmdline=None): - # return a dictionary or config on the cmdline or None + # return a dictionary of config on the cmdline or None return load_yaml(read_cc_from_cmdline(cmdline=cmdline)) @@ -1057,11 +912,12 @@ def read_cc_from_cmdline(cmdline=None): # this should support reading cloud-config information from # the kernel command line. It is intended to support content of the # format: - # cc: <yaml content here> [end_cc] + # cc: <yaml content here|urlencoded yaml content> [end_cc] # this would include: # cc: ssh_import_id: [smoser, kirkland]\\n # cc: ssh_import_id: [smoser, bob]\\nruncmd: [ [ ls, -l ], echo hi ] end_cc # cc:ssh_import_id: [smoser] end_cc cc:runcmd: [ [ ls, -l ] ] end_cc + # cc:ssh_import_id: %5Bsmoser%5D end_cc if cmdline is None: cmdline = get_cmdline() @@ -1076,9 +932,9 @@ def read_cc_from_cmdline(cmdline=None): end = cmdline.find(tag_end, begin + begin_l) if end < 0: end = clen - tokens.append(cmdline[begin + begin_l:end].lstrip().replace("\\n", - "\n")) - + tokens.append( + parse.unquote( + cmdline[begin + begin_l:end].lstrip()).replace("\\n", "\n")) begin = cmdline.find(tag_begin, end + end_l) return '\n'.join(tokens) @@ -1223,7 +1079,7 @@ def is_resolvable_url(url): """determine if this url is resolvable (existing or ip).""" return log_time(logfunc=LOG.debug, msg="Resolving URL: " + url, func=is_resolvable, - args=(urlparse.urlparse(url).hostname,)) + args=(parse.urlparse(url).hostname,)) def search_for_mirror(candidates): @@ -1231,9 +1087,14 @@ def search_for_mirror(candidates): Search through a list of mirror urls for one that works This needs to return quickly. """ + if candidates is None: + return None + + LOG.debug("search for mirror in candidates: '%s'", candidates) for cand in candidates: try: if is_resolvable_url(cand): + LOG.debug("found working mirror: '%s'", cand) return cand except Exception: pass @@ -1254,6 +1115,68 @@ def close_stdin(): os.dup2(fp.fileno(), sys.stdin.fileno()) +def find_devs_with_freebsd(criteria=None, oformat='device', + tag=None, no_cache=False, path=None): + devlist = [] + if not criteria: + return glob.glob("/dev/msdosfs/*") + glob.glob("/dev/iso9660/*") + if criteria.startswith("LABEL="): + label = criteria.lstrip("LABEL=") + devlist = [ + p for p in ['/dev/msdosfs/' + label, '/dev/iso9660/' + label] + if os.path.exists(p)] + elif criteria == "TYPE=vfat": + devlist = glob.glob("/dev/msdosfs/*") + elif criteria == "TYPE=iso9660": + devlist = glob.glob("/dev/iso9660/*") + return devlist + + +def find_devs_with_netbsd(criteria=None, oformat='device', + tag=None, no_cache=False, path=None): + devlist = [] + label = None + _type = None + if criteria: + if criteria.startswith("LABEL="): + label = criteria.lstrip("LABEL=") + if criteria.startswith("TYPE="): + _type = criteria.lstrip("TYPE=") + out, _err = subp.subp(['sysctl', '-n', 'hw.disknames'], rcs=[0]) + for dev in out.split(): + if label or _type: + mscdlabel_out, _ = subp.subp(['mscdlabel', dev], rcs=[0, 1]) + if label and not ('label "%s"' % label) in mscdlabel_out: + continue + if _type == "iso9660" and "ISO filesystem" not in mscdlabel_out: + continue + if _type == "vfat" and "ISO filesystem" in mscdlabel_out: + continue + devlist.append('/dev/' + dev) + return devlist + + +def find_devs_with_openbsd(criteria=None, oformat='device', + tag=None, no_cache=False, path=None): + out, _err = subp.subp(['sysctl', '-n', 'hw.disknames'], rcs=[0]) + devlist = [] + for entry in out.split(','): + if not entry.endswith(':'): + # ffs partition with a serial, not a config-drive + continue + if entry == 'fd0:': + continue + part_id = 'a' if entry.startswith('cd') else 'i' + devlist.append(entry[:-1] + part_id) + if criteria == "TYPE=iso9660": + devlist = [i for i in devlist if i.startswith('cd')] + elif criteria in ["LABEL=CONFIG-2", "TYPE=vfat"]: + devlist = [i for i in devlist if not i.startswith('cd')] + elif criteria: + LOG.debug("Unexpected criteria: %s", criteria) + return ['/dev/' + i for i in devlist] + + def find_devs_with(criteria=None, oformat='device', tag=None, no_cache=False, path=None): """ @@ -1263,6 +1186,16 @@ def find_devs_with(criteria=None, oformat='device', LABEL=<label> UUID=<uuid> """ + if is_FreeBSD(): + return find_devs_with_freebsd(criteria, oformat, + tag, no_cache, path) + elif is_NetBSD(): + return find_devs_with_netbsd(criteria, oformat, + tag, no_cache, path) + elif is_OpenBSD(): + return find_devs_with_openbsd(criteria, oformat, + tag, no_cache, path) + blk_id_cmd = ['blkid'] options = [] if criteria: @@ -1291,8 +1224,8 @@ def find_devs_with(criteria=None, oformat='device', cmd = blk_id_cmd + options # See man blkid for why 2 is added try: - (out, _err) = subp(cmd, rcs=[0, 2]) - except ProcessExecutionError as e: + (out, _err) = subp.subp(cmd, rcs=[0, 2]) + except subp.ProcessExecutionError as e: if e.errno == ENOENT: # blkid not found... out = "" @@ -1327,7 +1260,7 @@ def blkid(devs=None, disable_cache=False): # we have to decode with 'replace' as shelx.split (called by # load_shell_content) can't take bytes. So this is potentially # lossy of non-utf-8 chars in blkid output. - out, _ = subp(cmd, capture=True, decode="replace") + out, _ = subp.subp(cmd, capture=True, decode="replace") ret = {} for line in out.splitlines(): dev, _, data = line.partition(":") @@ -1355,7 +1288,7 @@ def uniq_list(in_list): def load_file(fname, read_cb=None, quiet=False, decode=True): LOG.debug("Reading from %s (quiet=%s)", fname, quiet) - ofh = six.BytesIO() + ofh = io.BytesIO() try: with open(fname, 'rb') as ifh: pipe_in_out(ifh, ofh, chunk_cb=read_cb) @@ -1430,7 +1363,7 @@ def chownbyname(fname, user=None, group=None): if group: gid = grp.getgrnam(group).gr_gid except KeyError as e: - raise OSError("Unknown user or group: %s" % (e)) + raise OSError("Unknown user or group: %s" % (e)) from e chownbyid(fname, uid, gid) @@ -1647,7 +1580,7 @@ def unmounter(umount): finally: if umount: umount_cmd = ["umount", umount] - subp(umount_cmd) + subp.subp(umount_cmd) def mounts(): @@ -1658,7 +1591,7 @@ def mounts(): mount_locs = load_file("/proc/mounts").splitlines() method = 'proc' else: - (mountoutput, _err) = subp("mount") + (mountoutput, _err) = subp.subp("mount") mount_locs = mountoutput.splitlines() method = 'mount' mountre = r'^(/dev/[\S]+) on (/.*) \((.+), .+, (.+)\)$' @@ -1742,7 +1675,7 @@ def mount_cb(device, callback, data=None, mtype=None, mountcmd.extend(['-t', mtype]) mountcmd.append(device) mountcmd.append(tmpd) - subp(mountcmd, update_env=update_env_for_mount) + subp.subp(mountcmd, update_env=update_env_for_mount) umount = tmpd # This forces it to be unmounted (when set) mountpoint = tmpd break @@ -1804,6 +1737,7 @@ def time_rfc2822(): return ts +@lru_cache() def boottime(): """Use sysctlbyname(3) via ctypes to find kern.boottime @@ -1813,6 +1747,7 @@ def boottime(): @return boottime: float to be compatible with linux """ import ctypes + import ctypes.util NULL_BYTES = b"\x00" @@ -1821,7 +1756,7 @@ def boottime(): ("tv_sec", ctypes.c_int64), ("tv_usec", ctypes.c_int64) ] - libc = ctypes.CDLL('/lib/libc.so.7') + libc = ctypes.CDLL(ctypes.util.find_library('c')) size = ctypes.c_size_t() size.value = ctypes.sizeof(timeval) buf = timeval() @@ -1872,7 +1807,15 @@ def chmod(path, mode): os.chmod(path, real_mode) -def write_file(filename, content, mode=0o644, omode="wb", copy_mode=False): +def write_file( + filename, + content, + mode=0o644, + omode="wb", + preserve_mode=False, + *, + ensure_dir_exists=True +): """ Writes a file with the given content and sets the file mode as specified. Restores the SELinux context if possible. @@ -1881,16 +1824,22 @@ def write_file(filename, content, mode=0o644, omode="wb", copy_mode=False): @param content: The content to write to the file. @param mode: The filesystem mode to set on the file. @param omode: The open mode used when opening the file (w, wb, a, etc.) + @param preserve_mode: If True and `filename` exists, preserve `filename`s + current mode instead of applying `mode`. + @param ensure_dir_exists: If True (the default), ensure that the directory + containing `filename` exists before writing to + the file. """ - if copy_mode: + if preserve_mode: try: file_stat = os.stat(filename) mode = stat.S_IMODE(file_stat.st_mode) except OSError: pass - ensure_dir(os.path.dirname(filename)) + if ensure_dir_exists: + ensure_dir(os.path.dirname(filename)) if 'b' in omode.lower(): content = encode_text(content) write_type = 'bytes' @@ -1924,185 +1873,6 @@ def delete_dir_contents(dirname): del_file(node_fullpath) -def subp_blob_in_tempfile(blob, *args, **kwargs): - """Write blob to a tempfile, and call subp with args, kwargs. Then cleanup. - - 'basename' as a kwarg allows providing the basename for the file. - The 'args' argument to subp will be updated with the full path to the - filename as the first argument. - """ - basename = kwargs.pop('basename', "subp_blob") - - if len(args) == 0 and 'args' not in kwargs: - args = [tuple()] - - # Use tmpdir over tmpfile to avoid 'text file busy' on execute - with temp_utils.tempdir(needs_exe=True) as tmpd: - tmpf = os.path.join(tmpd, basename) - if 'args' in kwargs: - kwargs['args'] = [tmpf] + list(kwargs['args']) - else: - args = list(args) - args[0] = [tmpf] + args[0] - - write_file(tmpf, blob, mode=0o700) - return subp(*args, **kwargs) - - -def subp(args, data=None, rcs=None, env=None, capture=True, - combine_capture=False, shell=False, - logstring=False, decode="replace", target=None, update_env=None, - status_cb=None): - """Run a subprocess. - - :param args: command to run in a list. [cmd, arg1, arg2...] - :param data: input to the command, made available on its stdin. - :param rcs: - a list of allowed return codes. If subprocess exits with a value not - in this list, a ProcessExecutionError will be raised. By default, - data is returned as a string. See 'decode' parameter. - :param env: a dictionary for the command's environment. - :param capture: - boolean indicating if output should be captured. If True, then stderr - and stdout will be returned. If False, they will not be redirected. - :param combine_capture: - boolean indicating if stderr should be redirected to stdout. When True, - interleaved stderr and stdout will be returned as the first element of - a tuple, the second will be empty string or bytes (per decode). - if combine_capture is True, then output is captured independent of - the value of capture. - :param shell: boolean indicating if this should be run with a shell. - :param logstring: - the command will be logged to DEBUG. If it contains info that should - not be logged, then logstring will be logged instead. - :param decode: - if False, no decoding will be done and returned stdout and stderr will - be bytes. Other allowed values are 'strict', 'ignore', and 'replace'. - These values are passed through to bytes().decode() as the 'errors' - parameter. There is no support for decoding to other than utf-8. - :param target: - not supported, kwarg present only to make function signature similar - to curtin's subp. - :param update_env: - update the enviornment for this command with this dictionary. - this will not affect the current processes os.environ. - :param status_cb: - call this fuction with a single string argument before starting - and after finishing. - - :return - if not capturing, return is (None, None) - if capturing, stdout and stderr are returned. - if decode: - entries in tuple will be python2 unicode or python3 string - if not decode: - entries in tuple will be python2 string or python3 bytes - """ - - # not supported in cloud-init (yet), for now kept in the call signature - # to ease maintaining code shared between cloud-init and curtin - if target is not None: - raise ValueError("target arg not supported by cloud-init") - - if rcs is None: - rcs = [0] - - devnull_fp = None - - if update_env: - if env is None: - env = os.environ - env = env.copy() - env.update(update_env) - - if target_path(target) != "/": - args = ['chroot', target] + list(args) - - if status_cb: - command = ' '.join(args) if isinstance(args, list) else args - status_cb('Begin run command: {command}\n'.format(command=command)) - if not logstring: - LOG.debug(("Running command %s with allowed return codes %s" - " (shell=%s, capture=%s)"), - args, rcs, shell, 'combine' if combine_capture else capture) - else: - LOG.debug(("Running hidden command to protect sensitive " - "input/output logstring: %s"), logstring) - - stdin = None - stdout = None - stderr = None - if capture: - stdout = subprocess.PIPE - stderr = subprocess.PIPE - if combine_capture: - stdout = subprocess.PIPE - stderr = subprocess.STDOUT - if data is None: - # using devnull assures any reads get null, rather - # than possibly waiting on input. - devnull_fp = open(os.devnull) - stdin = devnull_fp - else: - stdin = subprocess.PIPE - if not isinstance(data, bytes): - data = data.encode() - - # Popen converts entries in the arguments array from non-bytes to bytes. - # When locale is unset it may use ascii for that encoding which can - # cause UnicodeDecodeErrors. (LP: #1751051) - if isinstance(args, six.binary_type): - bytes_args = args - elif isinstance(args, six.string_types): - bytes_args = args.encode("utf-8") - else: - bytes_args = [ - x if isinstance(x, six.binary_type) else x.encode("utf-8") - for x in args] - try: - sp = subprocess.Popen(bytes_args, stdout=stdout, - stderr=stderr, stdin=stdin, - env=env, shell=shell) - (out, err) = sp.communicate(data) - except OSError as e: - if status_cb: - status_cb('ERROR: End run command: invalid command provided\n') - raise ProcessExecutionError( - cmd=args, reason=e, errno=e.errno, - stdout="-" if decode else b"-", - stderr="-" if decode else b"-") - finally: - if devnull_fp: - devnull_fp.close() - - # Just ensure blank instead of none. - if capture or combine_capture: - if not out: - out = b'' - if not err: - err = b'' - if decode: - def ldecode(data, m='utf-8'): - if not isinstance(data, bytes): - return data - return data.decode(m, decode) - - out = ldecode(out) - err = ldecode(err) - - rc = sp.returncode - if rc not in rcs: - if status_cb: - status_cb( - 'ERROR: End run command: exit({code})\n'.format(code=rc)) - raise ProcessExecutionError(stdout=out, stderr=err, - exit_code=rc, - cmd=args) - if status_cb: - status_cb('End run command: exit({code})\n'.format(code=rc)) - return (out, err) - - def make_header(comment_char="#", base='created'): ci_ver = version.version_string() header = str(comment_char) @@ -2111,8 +1881,8 @@ def make_header(comment_char="#", base='created'): return header -def abs_join(*paths): - return os.path.abspath(os.path.join(*paths)) +def abs_join(base, *paths): + return os.path.abspath(os.path.join(base, *paths)) # shellify, takes a list of commands @@ -2136,10 +1906,10 @@ def shellify(cmdlist, add_header=True): if isinstance(args, (list, tuple)): fixed = [] for f in args: - fixed.append("'%s'" % (six.text_type(f).replace("'", escaped))) + fixed.append("'%s'" % (str(f).replace("'", escaped))) content = "%s%s\n" % (content, ' '.join(fixed)) cmds_made += 1 - elif isinstance(args, six.string_types): + elif isinstance(args, str): content = "%s%s\n" % (content, args) cmds_made += 1 else: @@ -2168,7 +1938,7 @@ def is_container(): try: # try to run a helper program. if it returns true/zero # then we're inside a container. otherwise, no - subp(helper) + subp.subp(helper) return True except (IOError, OSError): pass @@ -2265,7 +2035,7 @@ def expand_package_list(version_fmt, pkgs): pkglist = [] for pkg in pkgs: - if isinstance(pkg, six.string_types): + if isinstance(pkg, str): pkglist.append(pkg) continue @@ -2374,7 +2144,7 @@ def find_freebsd_part(fs): return splitted[2] elif splitted[2] in ['label', 'gpt', 'ufs']: target_label = fs[5:] - (part, _err) = subp(['glabel', 'status', '-s']) + (part, _err) = subp.subp(['glabel', 'status', '-s']) for labels in part.split("\n"): items = labels.split() if len(items) > 0 and items[0] == target_label: @@ -2396,10 +2166,10 @@ def get_path_dev_freebsd(path, mnt_list): def get_mount_info_freebsd(path): - (result, err) = subp(['mount', '-p', path], rcs=[0, 1]) + (result, err) = subp.subp(['mount', '-p', path], rcs=[0, 1]) if len(err): # find a path if the input is not a mounting point - (mnt_list, err) = subp(['mount', '-p']) + (mnt_list, err) = subp.subp(['mount', '-p']) path_found = get_path_dev_freebsd(path, mnt_list) if (path_found is None): return None @@ -2415,8 +2185,8 @@ def get_device_info_from_zpool(zpool): LOG.debug('Cannot get zpool info, no /dev/zfs') return None try: - (zpoolstatus, err) = subp(['zpool', 'status', zpool]) - except ProcessExecutionError as err: + (zpoolstatus, err) = subp.subp(['zpool', 'status', zpool]) + except subp.ProcessExecutionError as err: LOG.warning("Unable to get zpool status of %s: %s", zpool, err) return None if len(err): @@ -2430,7 +2200,7 @@ def get_device_info_from_zpool(zpool): def parse_mount(path): - (mountoutput, _err) = subp(['mount']) + (mountoutput, _err) = subp.subp(['mount']) mount_locs = mountoutput.splitlines() # there are 2 types of mount outputs we have to parse therefore # the regex is a bit complex. to better understand this regex see: @@ -2503,40 +2273,6 @@ def get_mount_info(path, log=LOG, get_mnt_opts=False): return parse_mount(path) -def is_exe(fpath): - # return boolean indicating if fpath exists and is executable. - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - - -def which(program, search=None, target=None): - target = target_path(target) - - if os.path.sep in program: - # if program had a '/' in it, then do not search PATH - # 'which' does consider cwd here. (cd / && which bin/ls) = bin/ls - # so effectively we set cwd to / (or target) - if is_exe(target_path(target, program)): - return program - - if search is None: - paths = [p.strip('"') for p in - os.environ.get("PATH", "").split(os.pathsep)] - if target == "/": - search = paths - else: - search = [p for p in paths if p.startswith("/")] - - # normalize path input - search = [os.path.abspath(p) for p in search] - - for path in search: - ppath = os.path.sep.join((path, program)) - if is_exe(target_path(target, ppath)): - return ppath - - return None - - def log_time(logfunc, msg, func, args=None, kwargs=None, get_uptime=False): if args is None: args = [] @@ -2651,8 +2387,8 @@ def human2bytes(size): try: num = float(num) - except ValueError: - raise ValueError("'%s' is not valid input." % size_in) + except ValueError as e: + raise ValueError("'%s' is not valid input." % size_in) from e if num < 0: raise ValueError("'%s': cannot be negative" % size_in) @@ -2700,7 +2436,7 @@ def _call_dmidecode(key, dmidecode_path): """ try: cmd = [dmidecode_path, "--string", key] - (result, _err) = subp(cmd) + (result, _err) = subp.subp(cmd) result = result.strip() LOG.debug("dmidecode returned '%s' for '%s'", result, key) if result.replace(".", "") == "": @@ -2754,7 +2490,8 @@ def read_dmi_data(key): LOG.debug("dmidata is not supported on %s", uname_arch) return None - dmidecode_path = which('dmidecode') + print("hi, now its: %s\n", subp) + dmidecode_path = subp.which('dmidecode') if dmidecode_path: return _call_dmidecode(key, dmidecode_path) @@ -2765,12 +2502,12 @@ def read_dmi_data(key): def message_from_string(string): if sys.version_info[:2] < (2, 7): - return email.message_from_file(six.StringIO(string)) + return email.message_from_file(io.StringIO(string)) return email.message_from_string(string) def get_installed_packages(target=None): - (out, _) = subp(['dpkg-query', '--list'], target=target, capture=True) + (out, _) = subp.subp(['dpkg-query', '--list'], target=target, capture=True) pkgs_inst = set() for line in out.splitlines(): @@ -2906,7 +2643,7 @@ def udevadm_settle(exists=None, timeout=None): if timeout: settle_cmd.extend(['--timeout=%s' % timeout]) - return subp(settle_cmd) + return subp.subp(settle_cmd) def get_proc_ppid(pid): |