summaryrefslogtreecommitdiff
path: root/cloudinit/util.py
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/util.py')
-rw-r--r--cloudinit/util.py99
1 files changed, 74 insertions, 25 deletions
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 4fbdf0a9..cae57770 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -23,6 +23,7 @@
import contextlib
import copy as obj_copy
import ctypes
+import email
import errno
import glob
import grp
@@ -120,14 +121,40 @@ def fully_decoded_payload(part):
if (six.PY3 and
part.get_content_maintype() == 'text' and
isinstance(cte_payload, bytes)):
- charset = part.get_charset() or 'utf-8'
- return cte_payload.decode(charset, errors='surrogateescape')
+ charset = part.get_charset()
+ if charset and charset.input_codec:
+ encoding = charset.input_codec
+ else:
+ encoding = 'utf-8'
+ return cte_payload.decode(encoding, errors='surrogateescape')
return cte_payload
# Path for DMI Data
DMI_SYS_PATH = "/sys/class/dmi/id"
+# dmidecode and /sys/class/dmi/id/* use different names for the same value,
+# this allows us to refer to them by one canonical name
+DMIDECODE_TO_DMI_SYS_MAPPING = {
+ 'baseboard-asset-tag': 'board_asset_tag',
+ 'baseboard-manufacturer': 'board_vendor',
+ 'baseboard-product-name': 'board_name',
+ 'baseboard-serial-number': 'board_serial',
+ 'baseboard-version': 'board_version',
+ 'bios-release-date': 'bios_date',
+ 'bios-vendor': 'bios_vendor',
+ 'bios-version': 'bios_version',
+ 'chassis-asset-tag': 'chassis_asset_tag',
+ 'chassis-manufacturer': 'chassis_vendor',
+ 'chassis-serial-number': 'chassis_serial',
+ 'chassis-version': 'chassis_version',
+ 'system-manufacturer': 'sys_vendor',
+ 'system-product-name': 'product_name',
+ 'system-serial-number': 'product_serial',
+ 'system-uuid': 'product_uuid',
+ 'system-version': 'product_version',
+}
+
class ProcessExecutionError(IOError):
@@ -739,6 +766,10 @@ def fetch_ssl_details(paths=None):
return ssl_details
+def load_tfile_or_url(*args, **kwargs):
+ return(decode_binary(read_file_or_url(*args, **kwargs).contents))
+
+
def read_file_or_url(url, timeout=5, retries=10,
headers=None, data=None, sec_between=1, ssl_details=None,
headers_cb=None, exception_cb=None):
@@ -750,7 +781,7 @@ def read_file_or_url(url, timeout=5, retries=10,
LOG.warn("Unable to post data to file resource %s", url)
file_path = url[len("file://"):]
try:
- contents = load_file(file_path)
+ contents = load_file(file_path, decode=False)
except IOError as e:
code = e.errno
if e.errno == errno.ENOENT:
@@ -806,7 +837,7 @@ def read_seeded(base="", ext="", timeout=5, retries=10, file_retries=0):
ud_url = "%s%s%s" % (base, "user-data", ext)
md_url = "%s%s%s" % (base, "meta-data", ext)
- md_resp = read_file_or_url(md_url, timeout, retries, file_retries)
+ md_resp = load_tfile_or_url(md_url, timeout, retries, file_retries)
md = None
if md_resp.ok():
md = load_yaml(md_resp.contents, default={})
@@ -966,7 +997,7 @@ def get_fqdn_from_hosts(hostname, filename="/etc/hosts"):
def get_cmdline_url(names=('cloud-config-url', 'url'),
- starts="#cloud-config", cmdline=None):
+ starts=b"#cloud-config", cmdline=None):
if cmdline is None:
cmdline = get_cmdline()
@@ -982,6 +1013,8 @@ def get_cmdline_url(names=('cloud-config-url', 'url'),
return (None, None, None)
resp = read_file_or_url(url)
+ # allow callers to pass starts as text when comparing to bytes contents
+ starts = encode_text(starts)
if resp.ok() and resp.contents.startswith(starts):
return (key, url, resp.contents)
@@ -2030,7 +2063,7 @@ def pathprefix2dict(base, required=None, optional=None, delim=os.path.sep):
ret = {}
for f in required + optional:
try:
- ret[f] = load_file(base + delim + f, quiet=False)
+ ret[f] = load_file(base + delim + f, quiet=False, decode=False)
except IOError as e:
if e.errno != errno.ENOENT:
raise
@@ -2097,24 +2130,26 @@ def _read_dmi_syspath(key):
"""
Reads dmi data with from /sys/class/dmi/id
"""
-
- dmi_key = "{0}/{1}".format(DMI_SYS_PATH, key)
- LOG.debug("querying dmi data {0}".format(dmi_key))
+ if key not in DMIDECODE_TO_DMI_SYS_MAPPING:
+ return None
+ mapped_key = DMIDECODE_TO_DMI_SYS_MAPPING[key]
+ dmi_key_path = "{0}/{1}".format(DMI_SYS_PATH, mapped_key)
+ LOG.debug("querying dmi data %s", dmi_key_path)
try:
- if not os.path.exists(dmi_key):
- LOG.debug("did not find {0}".format(dmi_key))
+ if not os.path.exists(dmi_key_path):
+ LOG.debug("did not find %s", dmi_key_path)
return None
- key_data = load_file(dmi_key)
+ key_data = load_file(dmi_key_path)
if not key_data:
- LOG.debug("{0} did not return any data".format(key))
+ LOG.debug("%s did not return any data", dmi_key_path)
return None
- LOG.debug("dmi data {0} returned {0}".format(dmi_key, key_data))
+ LOG.debug("dmi data %s returned %s", dmi_key_path, key_data)
return key_data.strip()
except Exception as e:
- logexc(LOG, "failed read of {0}".format(dmi_key), e)
+ logexc(LOG, "failed read of %s", dmi_key_path, e)
return None
@@ -2126,26 +2161,40 @@ def _call_dmidecode(key, dmidecode_path):
try:
cmd = [dmidecode_path, "--string", key]
(result, _err) = subp(cmd)
- LOG.debug("dmidecode returned '{0}' for '{0}'".format(result, key))
+ LOG.debug("dmidecode returned '%s' for '%s'", result, key)
return result
- except OSError as _err:
- LOG.debug('failed dmidecode cmd: {0}\n{0}'.format(cmd, _err.message))
+ except (IOError, OSError) as _err:
+ LOG.debug('failed dmidecode cmd: %s\n%s', cmd, _err.message)
return None
def read_dmi_data(key):
"""
- Wrapper for reading DMI data. This tries to determine whether the DMI
- Data can be read directly, otherwise it will fallback to using dmidecode.
+ Wrapper for reading DMI data.
+
+ This will do the following (returning the first that produces a
+ result):
+ 1) Use a mapping to translate `key` from dmidecode naming to
+ sysfs naming and look in /sys/class/dmi/... for a value.
+ 2) Use `key` as a sysfs key directly and look in /sys/class/dmi/...
+ 3) Fall-back to passing `key` to `dmidecode --string`.
+
+ If all of the above fail to find a value, None will be returned.
"""
- if os.path.exists(DMI_SYS_PATH):
- return _read_dmi_syspath(key)
+ syspath_value = _read_dmi_syspath(key)
+ if syspath_value is not None:
+ return syspath_value
dmidecode_path = which('dmidecode')
if dmidecode_path:
return _call_dmidecode(key, dmidecode_path)
- LOG.warn("did not find either path {0} or dmidecode command".format(
- DMI_SYS_PATH))
-
+ LOG.warn("did not find either path %s or dmidecode command",
+ DMI_SYS_PATH)
return None
+
+
+def message_from_string(string):
+ if sys.version_info[:2] < (2, 7):
+ return email.message_from_file(six.StringIO(string))
+ return email.message_from_string(string)