summaryrefslogtreecommitdiff
path: root/cloudinit
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit')
-rw-r--r--cloudinit/config/cc_apt_configure.py4
-rw-r--r--cloudinit/config/cc_emit_upstart.py28
-rw-r--r--cloudinit/config/cc_grub_dpkg.py21
-rw-r--r--cloudinit/config/cc_locale.py6
-rw-r--r--cloudinit/config/cc_snappy.py133
-rw-r--r--cloudinit/settings.py2
-rw-r--r--cloudinit/sources/DataSourceCloudSigma.py2
-rw-r--r--cloudinit/stages.py21
-rw-r--r--cloudinit/user_data.py4
-rw-r--r--cloudinit/util.py77
10 files changed, 254 insertions, 44 deletions
diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py
index de72903f..2c51d116 100644
--- a/cloudinit/config/cc_apt_configure.py
+++ b/cloudinit/config/cc_apt_configure.py
@@ -51,6 +51,10 @@ EXPORT_GPG_KEYID = """
def handle(name, cfg, cloud, log, _args):
+ if util.is_false(cfg.get('apt_configure_enabled', True)):
+ log.debug("Skipping module named %s, disabled by config.", name)
+ return
+
release = get_release()
mirrors = find_apt_mirror_info(cloud, cfg)
if not mirrors or "primary" not in mirrors:
diff --git a/cloudinit/config/cc_emit_upstart.py b/cloudinit/config/cc_emit_upstart.py
index 6d376184..e1b9a4c2 100644
--- a/cloudinit/config/cc_emit_upstart.py
+++ b/cloudinit/config/cc_emit_upstart.py
@@ -21,11 +21,32 @@
import os
from cloudinit.settings import PER_ALWAYS
+from cloudinit import log as logging
from cloudinit import util
frequency = PER_ALWAYS
distros = ['ubuntu', 'debian']
+LOG = logging.getLogger(__name__)
+
+
+def is_upstart_system():
+ if not os.path.isfile("/sbin/initctl"):
+ LOG.debug(("Skipping module named %s,"
+ " no /sbin/initctl located"), name)
+ return False
+
+ myenv = os.environ.copy()
+ if 'UPSTART_SESSION' in myenv:
+ del myenv['UPSTART_SESSION']
+ check_cmd = ['initctl', 'version']
+ try:
+ (out, err) = util.subp(check_cmd, env=myenv)
+ return 'upstart' in out
+ except util.ProcessExecutionError as e:
+ LOG.debug("'%s' returned '%s', not using upstart",
+ ' '.join(check_cmd), e.exit_code)
+ return False
def handle(name, _cfg, cloud, log, args):
@@ -34,10 +55,11 @@ def handle(name, _cfg, cloud, log, args):
# Default to the 'cloud-config'
# event for backwards compat.
event_names = ['cloud-config']
- if not os.path.isfile("/sbin/initctl"):
- log.debug(("Skipping module named %s,"
- " no /sbin/initctl located"), name)
+
+ if not is_upstart_system():
+ log.debug("not upstart system, '%s' disabled")
return
+
cfgpath = cloud.paths.get_ipath_cur("cloud_config")
for n in event_names:
cmd = ['initctl', 'emit', str(n), 'CLOUD_CFG=%s' % cfgpath]
diff --git a/cloudinit/config/cc_grub_dpkg.py b/cloudinit/config/cc_grub_dpkg.py
index e3219e81..456597af 100644
--- a/cloudinit/config/cc_grub_dpkg.py
+++ b/cloudinit/config/cc_grub_dpkg.py
@@ -25,15 +25,20 @@ from cloudinit import util
distros = ['ubuntu', 'debian']
-def handle(_name, cfg, _cloud, log, _args):
- idevs = None
- idevs_empty = None
+def handle(name, cfg, _cloud, log, _args):
- if "grub-dpkg" in cfg:
- idevs = util.get_cfg_option_str(cfg["grub-dpkg"],
- "grub-pc/install_devices", None)
- idevs_empty = util.get_cfg_option_str(cfg["grub-dpkg"],
- "grub-pc/install_devices_empty", None)
+ mycfg = cfg.get("grub_dpkg", cfg.get("grub-dpkg", {}))
+ if not mycfg:
+ mycfg = {}
+
+ enabled = mycfg.get('enabled', True)
+ if util.is_false(enabled):
+ log.debug("%s disabled by config grub_dpkg/enabled=%s", name, enabled)
+ return
+
+ idevs = util.get_cfg_option_str(mycfg, "grub-pc/install_devices", None)
+ idevs_empty = util.get_cfg_option_str(mycfg,
+ "grub-pc/install_devices_empty", None)
if ((os.path.exists("/dev/sda1") and not os.path.exists("/dev/sda")) or
(os.path.exists("/dev/xvda1")
diff --git a/cloudinit/config/cc_locale.py b/cloudinit/config/cc_locale.py
index 6feaae9d..bbe5fcae 100644
--- a/cloudinit/config/cc_locale.py
+++ b/cloudinit/config/cc_locale.py
@@ -27,9 +27,9 @@ def handle(name, cfg, cloud, log, args):
else:
locale = util.get_cfg_option_str(cfg, "locale", cloud.get_locale())
- if not locale:
- log.debug(("Skipping module named %s, "
- "no 'locale' configuration found"), name)
+ if util.is_false(locale):
+ log.debug("Skipping module named %s, disabled by config: %s",
+ name, locale)
return
log.debug("Setting locale to %s", locale)
diff --git a/cloudinit/config/cc_snappy.py b/cloudinit/config/cc_snappy.py
new file mode 100644
index 00000000..1588443f
--- /dev/null
+++ b/cloudinit/config/cc_snappy.py
@@ -0,0 +1,133 @@
+# vi: ts=4 expandtab
+#
+
+from cloudinit import log as logging
+from cloudinit import templater
+from cloudinit import util
+from cloudinit.settings import PER_INSTANCE
+
+import glob
+import os
+
+LOG = logging.getLogger(__name__)
+
+frequency = PER_INSTANCE
+SNAPPY_ENV_PATH = "/writable/system-data/etc/snappy.env"
+
+CI_SNAPPY_CFG = {
+ 'env_file_path': SNAPPY_ENV_PATH,
+ 'packages': [],
+ 'packages_dir': '/writable/user-data/cloud-init/click_packages',
+ 'ssh_enabled': False
+}
+
+"""
+snappy:
+ ssh_enabled: True
+ packages:
+ - etcd
+ - {'name': 'pkg1', 'config': "wark"}
+"""
+
+
+def flatten(data, fill=None, tok="_", prefix='', recurse=True):
+ if fill is None:
+ fill = {}
+ for key, val in data.items():
+ key = key.replace("-", "_")
+ if isinstance(val, dict) and recurse:
+ flatten(val, fill, tok=tok, prefix=prefix + key + tok,
+ recurse=recurse)
+ elif isinstance(key, str):
+ fill[prefix + key] = val
+ return fill
+
+
+def render2env(data, tok="_", prefix=''):
+ flat = flatten(data, tok=tok, prefix=prefix)
+ ret = ["%s='%s'" % (key, val) for key, val in flat.items()]
+ return '\n'.join(ret) + '\n'
+
+
+def install_package(pkg_name, config=None):
+ cmd = ["snappy", "install"]
+ if config:
+ if os.path.isfile(config):
+ cmd.append("--config-file=" + config)
+ else:
+ cmd.append("--config=" + config)
+ cmd.append(pkg_name)
+ util.subp(cmd)
+
+
+def install_packages(package_dir, packages):
+ local_pkgs = glob.glob(os.path.sep.join([package_dir, '*.click']))
+ LOG.debug("installing local packages %s" % local_pkgs)
+ if local_pkgs:
+ for pkg in local_pkgs:
+ cfg = pkg.replace(".click", ".config")
+ if not os.path.isfile(cfg):
+ cfg = None
+ install_package(pkg, config=cfg)
+
+ LOG.debug("installing click packages")
+ if packages:
+ for pkg in packages:
+ if not pkg:
+ continue
+ if isinstance(pkg, str):
+ name = pkg
+ config = None
+ elif pkg:
+ name = pkg.get('name', pkg)
+ config = pkg.get('config')
+ install_package(pkg_name=name, config=config)
+
+
+def disable_enable_ssh(enabled):
+ LOG.debug("setting enablement of ssh to: %s", enabled)
+ # do something here that would enable or disable
+ not_to_be_run = "/etc/ssh/sshd_not_to_be_run"
+ if enabled:
+ util.del_file(not_to_be_run)
+ # this is an indempotent operation
+ util.subp(["systemctl", "start", "ssh"])
+ else:
+ # this is an indempotent operation
+ util.subp(["systemctl", "stop", "ssh"])
+ util.write_file(not_to_be_run, "cloud-init\n")
+
+
+def handle(name, cfg, cloud, log, args):
+ mycfg = cfg.get('snappy', {'ssh_enabled': False})
+
+ if not mycfg:
+ LOG.debug("%s: no top level found", name)
+ return
+
+ # take out of 'cfg' the cfg keys that cloud-init uses, so
+ # mycfg has only content external to cloud-init.
+ ci_cfg = CI_SNAPPY_CFG.copy()
+ for i in ci_cfg:
+ if i in mycfg:
+ ci_cfg[i] = mycfg[i]
+ del mycfg[i]
+
+ # render the flattened environment variable style file to a path
+ # this was useful for systemd config environment files. given:
+ # snappy:
+ # foo:
+ # bar: wark
+ # cfg1:
+ # key1: value
+ # you get the following in env_file_path.
+ # foo_bar=wark
+ # foo_cfg1_key1=value
+ contents = render2env(mycfg)
+ header = '# for internal use only, not a guaranteed interface\n'
+ util.write_file(ci_cfg['env_file_path'], header + render2env(mycfg))
+
+ install_packages(ci_cfg['packages_dir'],
+ ci_cfg['packages'])
+
+ disable_enable_ssh(ci_cfg.get('ssh_enabled', False))
diff --git a/cloudinit/settings.py b/cloudinit/settings.py
index 5efcb0b0..b61e5613 100644
--- a/cloudinit/settings.py
+++ b/cloudinit/settings.py
@@ -47,7 +47,7 @@ CFG_BUILTIN = {
],
'def_log_file': '/var/log/cloud-init.log',
'log_cfgs': [],
- 'syslog_fix_perms': 'syslog:adm',
+ 'syslog_fix_perms': ['syslog:adm', 'root:adm'],
'system_info': {
'paths': {
'cloud_dir': '/var/lib/cloud',
diff --git a/cloudinit/sources/DataSourceCloudSigma.py b/cloudinit/sources/DataSourceCloudSigma.py
index 76597116..f8f94759 100644
--- a/cloudinit/sources/DataSourceCloudSigma.py
+++ b/cloudinit/sources/DataSourceCloudSigma.py
@@ -59,7 +59,7 @@ class DataSourceCloudSigma(sources.DataSource):
LOG.warn("failed to get hypervisor product name via dmi data")
return False
else:
- LOG.debug("detected hypervisor as {}".format(sys_product_name))
+ LOG.debug("detected hypervisor as %s", sys_product_name)
return 'cloudsigma' in sys_product_name.lower()
LOG.warn("failed to query dmi data for system product name")
diff --git a/cloudinit/stages.py b/cloudinit/stages.py
index 45d64823..d28e765b 100644
--- a/cloudinit/stages.py
+++ b/cloudinit/stages.py
@@ -148,16 +148,25 @@ class Init(object):
def _initialize_filesystem(self):
util.ensure_dirs(self._initial_subdirs())
log_file = util.get_cfg_option_str(self.cfg, 'def_log_file')
- perms = util.get_cfg_option_str(self.cfg, 'syslog_fix_perms')
if log_file:
util.ensure_file(log_file)
- if perms:
- u, g = util.extract_usergroup(perms)
+ perms = self.cfg.get('syslog_fix_perms')
+ if not perms:
+ perms = {}
+ if not isinstance(perms, list):
+ perms = [perms]
+
+ error = None
+ for perm in perms:
+ u, g = util.extract_usergroup(perm)
try:
util.chownbyname(log_file, u, g)
- except OSError:
- util.logexc(LOG, "Unable to change the ownership of %s to "
- "user %s, group %s", log_file, u, g)
+ return
+ except OSError as e:
+ error = e
+
+ LOG.warn("Failed changing perms on '%s'. tried: %s. %s",
+ log_file, ','.join(perms), error)
def read_cfg(self, extra_fns=None):
# None check so that we don't keep on re-loading if empty
diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py
index 663a9048..eb3c7336 100644
--- a/cloudinit/user_data.py
+++ b/cloudinit/user_data.py
@@ -22,8 +22,6 @@
import os
-import email
-
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.nonmultipart import MIMENonMultipart
@@ -338,7 +336,7 @@ def convert_string(raw_data, headers=None):
headers = {}
data = util.decode_binary(util.decomp_gzip(raw_data))
if "mime-version:" in data[0:4096].lower():
- msg = email.message_from_string(data)
+ msg = util.message_from_string(data)
for (key, val) in headers.items():
_replace_header(msg, key, val)
else:
diff --git a/cloudinit/util.py b/cloudinit/util.py
index cc20305c..971c1c2d 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
@@ -128,6 +129,28 @@ def fully_decoded_payload(part):
# 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):
@@ -2103,24 +2126,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
@@ -2132,26 +2157,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)