summaryrefslogtreecommitdiff
path: root/cloudinit/util.py
diff options
context:
space:
mode:
authorzsdc <taras@vyos.io>2020-09-15 17:05:20 +0300
committerzsdc <taras@vyos.io>2020-09-15 17:05:20 +0300
commit7cd260b313267dc7123cb99a75d4555e24909cca (patch)
treef57f3db085a724df237ffa64b589c6bb6dd3b28f /cloudinit/util.py
parent1a790ee102fd405e5c3a20a17a69ba0c118ed874 (diff)
parent948bd9c1fcd08346cf8ec0551d7f6c2b234e896b (diff)
downloadvyos-cloud-init-7cd260b313267dc7123cb99a75d4555e24909cca.tar.gz
vyos-cloud-init-7cd260b313267dc7123cb99a75d4555e24909cca.zip
T2117: Cloud-init updated to 20.3
Merged with 20.3 tag from the upstream Cloud-init repository
Diffstat (limited to 'cloudinit/util.py')
-rw-r--r--cloudinit/util.py643
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):