summaryrefslogtreecommitdiff
path: root/cloudinit/config
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/config')
-rw-r--r--cloudinit/config/cc_apt_configure.py30
-rw-r--r--cloudinit/config/cc_disk_setup.py790
-rw-r--r--cloudinit/config/cc_final_message.py2
-rw-r--r--cloudinit/config/cc_growpart.py7
-rw-r--r--cloudinit/config/cc_mounts.py131
-rw-r--r--cloudinit/config/cc_power_state_change.py6
-rw-r--r--cloudinit/config/cc_seed_random.py61
-rw-r--r--cloudinit/config/cc_ssh_authkey_fingerprints.py2
8 files changed, 983 insertions, 46 deletions
diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py
index 5a407016..29c13a3d 100644
--- a/cloudinit/config/cc_apt_configure.py
+++ b/cloudinit/config/cc_apt_configure.py
@@ -20,6 +20,7 @@
import glob
import os
+import re
from cloudinit import templater
from cloudinit import util
@@ -30,6 +31,9 @@ PROXY_TPL = "Acquire::HTTP::Proxy \"%s\";\n"
APT_CONFIG_FN = "/etc/apt/apt.conf.d/94cloud-init-config"
APT_PROXY_FN = "/etc/apt/apt.conf.d/95cloud-init-proxy"
+# this will match 'XXX:YYY' (ie, 'cloud-archive:foo' or 'ppa:bar')
+ADD_APT_REPO_MATCH = r"^[\w-]+:\w"
+
# A temporary shell program to get a given gpg key
# from a given keyserver
EXPORT_GPG_KEYID = """
@@ -78,7 +82,15 @@ def handle(name, cfg, cloud, log, _args):
params = mirrors
params['RELEASE'] = release
params['MIRROR'] = mirror
- errors = add_sources(cfg['apt_sources'], params)
+
+ matchcfg = cfg.get('add_apt_repo_match', ADD_APT_REPO_MATCH)
+ if matchcfg:
+ matcher = re.compile(matchcfg).search
+ else:
+ matcher = lambda f: False
+
+ errors = add_sources(cfg['apt_sources'], params,
+ aa_repo_match=matcher)
for e in errors:
log.warn("Add source error: %s", ':'.join(e))
@@ -147,7 +159,7 @@ def generate_sources_list(codename, mirrors, cloud, log):
templater.render_to_file(template_fn, '/etc/apt/sources.list', params)
-def add_sources(srclist, template_params=None):
+def add_sources(srclist, template_params=None, aa_repo_match=None):
"""
add entries in /etc/apt/sources.list.d for each abbreviated
sources.list entry in 'srclist'. When rendering template, also
@@ -156,6 +168,9 @@ def add_sources(srclist, template_params=None):
if template_params is None:
template_params = {}
+ if aa_repo_match is None:
+ aa_repo_match = lambda f: False
+
errorlist = []
for ent in srclist:
if 'source' not in ent:
@@ -163,15 +178,16 @@ def add_sources(srclist, template_params=None):
continue
source = ent['source']
- if source.startswith("ppa:"):
+ source = templater.render_string(source, template_params)
+
+ if aa_repo_match(source):
try:
util.subp(["add-apt-repository", source])
- except:
- errorlist.append([source, "add-apt-repository failed"])
+ except util.ProcessExecutionError as e:
+ errorlist.append([source,
+ ("add-apt-repository failed. " + str(e))])
continue
- source = templater.render_string(source, template_params)
-
if 'filename' not in ent:
ent['filename'] = 'cloud_config_sources.list'
diff --git a/cloudinit/config/cc_disk_setup.py b/cloudinit/config/cc_disk_setup.py
new file mode 100644
index 00000000..0b970e4e
--- /dev/null
+++ b/cloudinit/config/cc_disk_setup.py
@@ -0,0 +1,790 @@
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2009-2010 Canonical Ltd.
+# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
+#
+# Author: Ben Howard <ben.howard@canonical.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3, as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from cloudinit.settings import PER_INSTANCE
+from cloudinit import util
+import logging
+import os
+import shlex
+
+frequency = PER_INSTANCE
+
+# Define the commands to use
+UDEVADM_CMD = util.which('udevadm')
+SFDISK_CMD = util.which("sfdisk")
+LSBLK_CMD = util.which("lsblk")
+BLKID_CMD = util.which("blkid")
+BLKDEV_CMD = util.which("blockdev")
+WIPEFS_CMD = util.which("wipefs")
+
+LOG = logging.getLogger(__name__)
+
+
+def handle(_name, cfg, cloud, log, _args):
+ """
+ See doc/examples/cloud-config_disk-setup.txt for documentation on the
+ format.
+ """
+ disk_setup = cfg.get("disk_setup")
+ if isinstance(disk_setup, dict):
+ update_disk_setup_devices(disk_setup, cloud.device_name_to_device)
+ log.debug("Partitioning disks: %s", str(disk_setup))
+ for disk, definition in disk_setup.items():
+ if not isinstance(definition, dict):
+ log.warn("Invalid disk definition for %s" % disk)
+ continue
+
+ try:
+ log.debug("Creating new partition table/disk")
+ util.log_time(logfunc=LOG.debug,
+ msg="Creating partition on %s" % disk,
+ func=mkpart, args=(disk, definition))
+ except Exception as e:
+ util.logexc(LOG, "Failed partitioning operation\n%s" % e)
+
+ fs_setup = cfg.get("fs_setup")
+ if isinstance(fs_setup, list):
+ log.debug("setting up filesystems: %s", str(fs_setup))
+ update_fs_setup_devices(fs_setup, cloud.device_name_to_device)
+ for definition in fs_setup:
+ if not isinstance(definition, dict):
+ log.warn("Invalid file system definition: %s" % definition)
+ continue
+
+ try:
+ log.debug("Creating new filesystem.")
+ device = definition.get('device')
+ util.log_time(logfunc=LOG.debug,
+ msg="Creating fs for %s" % device,
+ func=mkfs, args=(definition,))
+ except Exception as e:
+ util.logexc(LOG, "Failed during filesystem operation\n%s" % e)
+
+
+def update_disk_setup_devices(disk_setup, tformer):
+ # update 'disk_setup' dictionary anywhere were a device may occur
+ # update it with the response from 'tformer'
+ for origname in disk_setup.keys():
+ transformed = tformer(origname)
+ if transformed is None or transformed == origname:
+ continue
+ if transformed in disk_setup:
+ LOG.info("Replacing %s in disk_setup for translation of %s",
+ origname, transformed)
+ del disk_setup[transformed]
+
+ disk_setup[transformed] = disk_setup[origname]
+ disk_setup[transformed]['_origname'] = origname
+ del disk_setup[origname]
+ LOG.debug("updated disk_setup device entry '%s' to '%s'",
+ origname, transformed)
+
+
+def update_fs_setup_devices(disk_setup, tformer):
+ # update 'fs_setup' dictionary anywhere were a device may occur
+ # update it with the response from 'tformer'
+ for definition in disk_setup:
+ if not isinstance(definition, dict):
+ LOG.warn("entry in disk_setup not a dict: %s", definition)
+ continue
+
+ origname = definition.get('device')
+
+ if origname is None:
+ continue
+
+ (dev, part) = util.expand_dotted_devname(origname)
+
+ tformed = tformer(dev)
+ if tformed is not None:
+ dev = tformed
+ LOG.debug("%s is mapped to disk=%s part=%s",
+ origname, tformed, part)
+ definition['_origname'] = origname
+ definition['device'] = tformed
+
+ if part and 'partition' in definition:
+ definition['_partition'] = definition['partition']
+ definition['partition'] = part
+
+
+def value_splitter(values, start=None):
+ """
+ Returns the key/value pairs of output sent as string
+ like: FOO='BAR' HOME='127.0.0.1'
+ """
+ _values = shlex.split(values)
+ if start:
+ _values = _values[start:]
+
+ for key, value in [x.split('=') for x in _values]:
+ yield key, value
+
+
+def enumerate_disk(device, nodeps=False):
+ """
+ Enumerate the elements of a child device.
+
+ Parameters:
+ device: the kernel device name
+ nodeps <BOOL>: don't enumerate children devices
+
+ Return a dict describing the disk:
+ type: the entry type, i.e disk or part
+ fstype: the filesystem type, if it exists
+ label: file system label, if it exists
+ name: the device name, i.e. sda
+ """
+
+ lsblk_cmd = [LSBLK_CMD, '--pairs', '--out', 'NAME,TYPE,FSTYPE,LABEL',
+ device]
+
+ if nodeps:
+ lsblk_cmd.append('--nodeps')
+
+ info = None
+ try:
+ info, _err = util.subp(lsblk_cmd)
+ except Exception as e:
+ raise Exception("Failed during disk check for %s\n%s" % (device, e))
+
+ parts = [x for x in (info.strip()).splitlines() if len(x.split()) > 0]
+
+ for part in parts:
+ d = {'name': None,
+ 'type': None,
+ 'fstype': None,
+ 'label': None,
+ }
+
+ for key, value in value_splitter(part):
+ d[key.lower()] = value
+
+ yield d
+
+
+def device_type(device):
+ """
+ Return the device type of the device by calling lsblk.
+ """
+
+ for d in enumerate_disk(device, nodeps=True):
+ if "type" in d:
+ return d["type"].lower()
+ return None
+
+
+def is_device_valid(name, partition=False):
+ """
+ Check if the device is a valid device.
+ """
+ d_type = ""
+ try:
+ d_type = device_type(name)
+ except:
+ LOG.warn("Query against device %s failed" % name)
+ return False
+
+ if partition and d_type == 'part':
+ return True
+ elif not partition and d_type == 'disk':
+ return True
+ return False
+
+
+def check_fs(device):
+ """
+ Check if the device has a filesystem on it
+
+ Output of blkid is generally something like:
+ /dev/sda: LABEL="Backup500G" UUID="..." TYPE="ext4"
+
+ Return values are device, label, type, uuid
+ """
+ out, label, fs_type, uuid = None, None, None, None
+
+ blkid_cmd = [BLKID_CMD, '-c', '/dev/null', device]
+ try:
+ out, _err = util.subp(blkid_cmd, rcs=[0, 2])
+ except Exception as e:
+ raise Exception("Failed during disk check for %s\n%s" % (device, e))
+
+ if out:
+ if len(out.splitlines()) == 1:
+ for key, value in value_splitter(out, start=1):
+ if key.lower() == 'label':
+ label = value
+ elif key.lower() == 'type':
+ fs_type = value
+ elif key.lower() == 'uuid':
+ uuid = value
+
+ return label, fs_type, uuid
+
+
+def is_filesystem(device):
+ """
+ Returns true if the device has a file system.
+ """
+ _, fs_type, _ = check_fs(device)
+ return fs_type
+
+
+def find_device_node(device, fs_type=None, label=None, valid_targets=None,
+ label_match=True, replace_fs=None):
+ """
+ Find a device that is either matches the spec, or the first
+
+ The return is value is (<device>, <bool>) where the device is the
+ device to use and the bool is whether the device matches the
+ fs_type and label.
+
+ Note: This works with GPT partition tables!
+ """
+ # label of None is same as no label
+ if label is None:
+ label = ""
+
+ if not valid_targets:
+ valid_targets = ['disk', 'part']
+
+ raw_device_used = False
+ for d in enumerate_disk(device):
+
+ if d['fstype'] == replace_fs and label_match is False:
+ # We found a device where we want to replace the FS
+ return ('/dev/%s' % d['name'], False)
+
+ if (d['fstype'] == fs_type and
+ ((label_match and d['label'] == label) or not label_match)):
+ # If we find a matching device, we return that
+ return ('/dev/%s' % d['name'], True)
+
+ if d['type'] in valid_targets:
+
+ if d['type'] != 'disk' or d['fstype']:
+ raw_device_used = True
+
+ if d['type'] == 'disk':
+ # Skip the raw disk, its the default
+ pass
+
+ elif not d['fstype']:
+ return ('/dev/%s' % d['name'], False)
+
+ if not raw_device_used:
+ return (device, False)
+
+ LOG.warn("Failed to find device during available device search.")
+ return (None, False)
+
+
+def is_disk_used(device):
+ """
+ Check if the device is currently used. Returns true if the device
+ has either a file system or a partition entry
+ is no filesystem found on the disk.
+ """
+
+ # If the child count is higher 1, then there are child nodes
+ # such as partition or device mapper nodes
+ use_count = [x for x in enumerate_disk(device)]
+ if len(use_count.splitlines()) > 1:
+ return True
+
+ # If we see a file system, then its used
+ _, check_fstype, _ = check_fs(device)
+ if check_fstype:
+ return True
+
+ return False
+
+
+def get_hdd_size(device):
+ """
+ Returns the hard disk size.
+ This works with any disk type, including GPT.
+ """
+
+ size_cmd = [SFDISK_CMD, '--show-size', device]
+ size = None
+ try:
+ size, _err = util.subp(size_cmd)
+ except Exception as e:
+ raise Exception("Failed to get %s size\n%s" % (device, e))
+
+ return int(size.strip())
+
+
+def get_dyn_func(*args):
+ """
+ Call the appropriate function.
+
+ The first value is the template for function name
+ The second value is the template replacement
+ The remain values are passed to the function
+
+ For example: get_dyn_func("foo_%s", 'bar', 1, 2, 3,)
+ would call "foo_bar" with args of 1, 2, 3
+ """
+ if len(args) < 2:
+ raise Exception("Unable to determine dynamic funcation name")
+
+ func_name = (args[0] % args[1])
+ func_args = args[2:]
+
+ try:
+ if func_args:
+ return globals()[func_name](*func_args)
+ else:
+ return globals()[func_name]
+
+ except KeyError:
+ raise Exception("No such function %s to call!" % func_name)
+
+
+def check_partition_mbr_layout(device, layout):
+ """
+ Returns true if the partition layout matches the one on the disk
+
+ Layout should be a list of values. At this time, this only
+ verifies that the number of partitions and their labels is correct.
+ """
+
+ read_parttbl(device)
+ prt_cmd = [SFDISK_CMD, "-l", device]
+ try:
+ out, _err = util.subp(prt_cmd, data="%s\n" % layout)
+ except Exception as e:
+ raise Exception("Error running partition command on %s\n%s" % (
+ device, e))
+
+ found_layout = []
+ for line in out.splitlines():
+ _line = line.split()
+ if len(_line) == 0:
+ continue
+
+ if device in _line[0]:
+ # We don't understand extended partitions yet
+ if _line[-1].lower() in ['extended', 'empty']:
+ continue
+
+ # Find the partition types
+ type_label = None
+ for x in sorted(range(1, len(_line)), reverse=True):
+ if _line[x].isdigit() and _line[x] != '/':
+ type_label = _line[x]
+ break
+
+ found_layout.append(type_label)
+
+ if isinstance(layout, bool):
+ # if we are using auto partitioning, or "True" be happy
+ # if a single partition exists.
+ if layout and len(found_layout) >= 1:
+ return True
+ return False
+
+ else:
+ if len(found_layout) != len(layout):
+ return False
+ else:
+ # This just makes sure that the number of requested
+ # partitions and the type labels are right
+ for x in range(1, len(layout) + 1):
+ if isinstance(layout[x - 1], tuple):
+ _, part_type = layout[x]
+ if int(found_layout[x]) != int(part_type):
+ return False
+ return True
+
+ return False
+
+
+def check_partition_layout(table_type, device, layout):
+ """
+ See if the partition lay out matches.
+
+ This is future a future proofing function. In order
+ to add support for other disk layout schemes, add a
+ function called check_partition_%s_layout
+ """
+ return get_dyn_func("check_partition_%s_layout", table_type, device,
+ layout)
+
+
+def get_partition_mbr_layout(size, layout):
+ """
+ Calculate the layout of the partition table. Partition sizes
+ are defined as percentage values or a tuple of percentage and
+ partition type.
+
+ For example:
+ [ 33, [66: 82] ]
+
+ Defines the first partition to be a size of 1/3 the disk,
+ while the remaining 2/3's will be of type Linux Swap.
+ """
+
+ if not isinstance(layout, list) and isinstance(layout, bool):
+ # Create a single partition
+ return "0,"
+
+ if ((len(layout) == 0 and isinstance(layout, list)) or
+ not isinstance(layout, list)):
+ raise Exception("Partition layout is invalid")
+
+ last_part_num = len(layout)
+ if last_part_num > 4:
+ raise Exception("Only simply partitioning is allowed.")
+
+ part_definition = []
+ part_num = 0
+ for part in layout:
+ part_type = 83 # Default to Linux
+ percent = part
+ part_num += 1
+
+ if isinstance(part, list):
+ if len(part) != 2:
+ raise Exception("Partition was incorrectly defined: %s" % part)
+ percent, part_type = part
+
+ part_size = int((float(size) * (float(percent) / 100)) / 1024)
+
+ if part_num == last_part_num:
+ part_definition.append(",,%s" % part_type)
+ else:
+ part_definition.append(",%s,%s" % (part_size, part_type))
+
+ sfdisk_definition = "\n".join(part_definition)
+ if len(part_definition) > 4:
+ raise Exception("Calculated partition definition is too big\n%s" %
+ sfdisk_definition)
+
+ return sfdisk_definition
+
+
+def purge_disk_ptable(device):
+ # wipe the first and last megabyte of a disk (or file)
+ # gpt stores partition table both at front and at end.
+ null = '\0' # pylint: disable=W1401
+ start_len = 1024 * 1024
+ end_len = 1024 * 1024
+ with open(device, "rb+") as fp:
+ fp.write(null * (start_len))
+ fp.seek(-end_len, os.SEEK_END)
+ fp.write(null * end_len)
+ fp.flush()
+
+ read_parttbl(device)
+
+
+def purge_disk(device):
+ """
+ Remove parition table entries
+ """
+
+ # wipe any file systems first
+ for d in enumerate_disk(device):
+ if d['type'] not in ["disk", "crypt"]:
+ wipefs_cmd = [WIPEFS_CMD, "--all", "/dev/%s" % d['name']]
+ try:
+ LOG.info("Purging filesystem on /dev/%s" % d['name'])
+ util.subp(wipefs_cmd)
+ except Exception:
+ raise Exception("Failed FS purge of /dev/%s" % d['name'])
+
+ purge_disk_ptable(device)
+
+
+def get_partition_layout(table_type, size, layout):
+ """
+ Call the appropriate function for creating the table
+ definition. Returns the table definition
+
+ This is a future proofing function. To add support for
+ other layouts, simply add a "get_partition_%s_layout"
+ function.
+ """
+ return get_dyn_func("get_partition_%s_layout", table_type, size, layout)
+
+
+def read_parttbl(device):
+ """
+ Use partprobe instead of 'udevadm'. Partprobe is the only
+ reliable way to probe the partition table.
+ """
+ blkdev_cmd = [BLKDEV_CMD, '--rereadpt', device]
+ udev_cmd = [UDEVADM_CMD, 'settle']
+ try:
+ util.subp(udev_cmd)
+ util.subp(blkdev_cmd)
+ util.subp(udev_cmd)
+ except Exception as e:
+ util.logexc(LOG, "Failed reading the partition table %s" % e)
+
+
+def exec_mkpart_mbr(device, layout):
+ """
+ Break out of mbr partition to allow for future partition
+ types, i.e. gpt
+ """
+ # Create the partitions
+ prt_cmd = [SFDISK_CMD, "--Linux", "-uM", device]
+ try:
+ util.subp(prt_cmd, data="%s\n" % layout)
+ except Exception as e:
+ raise Exception("Failed to partition device %s\n%s" % (device, e))
+
+ read_parttbl(device)
+
+
+def exec_mkpart(table_type, device, layout):
+ """
+ Fetches the function for creating the table type.
+ This allows to dynamically find which function to call.
+
+ Paramaters:
+ table_type: type of partition table to use
+ device: the device to work on
+ layout: layout definition specific to partition table
+ """
+ return get_dyn_func("exec_mkpart_%s", table_type, device, layout)
+
+
+def mkpart(device, definition):
+ """
+ Creates the partition table.
+
+ Parameters:
+ definition: dictionary describing how to create the partition.
+
+ The following are supported values in the dict:
+ overwrite: Should the partition table be created regardless
+ of any pre-exisiting data?
+ layout: the layout of the partition table
+ table_type: Which partition table to use, defaults to MBR
+ device: the device to work on.
+ """
+
+ LOG.debug("Checking values for %s definition" % device)
+ overwrite = definition.get('overwrite', False)
+ layout = definition.get('layout', False)
+ table_type = definition.get('table_type', 'mbr')
+
+ # Check if the default device is a partition or not
+ LOG.debug("Checking against default devices")
+
+ if (isinstance(layout, bool) and not layout) or not layout:
+ LOG.debug("Device is not to be partitioned, skipping")
+ return # Device is not to be partitioned
+
+ # This prevents you from overwriting the device
+ LOG.debug("Checking if device %s is a valid device", device)
+ if not is_device_valid(device):
+ raise Exception("Device %s is not a disk device!", device)
+
+ # Remove the partition table entries
+ if isinstance(layout, str) and layout.lower() == "remove":
+ LOG.debug("Instructed to remove partition table entries")
+ purge_disk(device)
+ return
+
+ LOG.debug("Checking if device layout matches")
+ if check_partition_layout(table_type, device, layout):
+ LOG.debug("Device partitioning layout matches")
+ return True
+
+ LOG.debug("Checking if device is safe to partition")
+ if not overwrite and (is_disk_used(device) or is_filesystem(device)):
+ LOG.debug("Skipping partitioning on configured device %s" % device)
+ return
+
+ LOG.debug("Checking for device size")
+ device_size = get_hdd_size(device)
+
+ LOG.debug("Calculating partition layout")
+ part_definition = get_partition_layout(table_type, device_size, layout)
+ LOG.debug(" Layout is: %s" % part_definition)
+
+ LOG.debug("Creating partition table on %s", device)
+ exec_mkpart(table_type, device, part_definition)
+
+ LOG.debug("Partition table created for %s", device)
+
+
+def lookup_force_flag(fs):
+ """
+ A force flag might be -F or -F, this look it up
+ """
+ flags = {'ext': '-F',
+ 'btrfs': '-f',
+ 'xfs': '-f',
+ 'reiserfs': '-f',
+ }
+
+ if 'ext' in fs.lower():
+ fs = 'ext'
+
+ if fs.lower() in flags:
+ return flags[fs]
+
+ LOG.warn("Force flag for %s is unknown." % fs)
+ return ''
+
+
+def mkfs(fs_cfg):
+ """
+ Create a file system on the device.
+
+ label: defines the label to use on the device
+ fs_cfg: defines how the filesystem is to look
+ The following values are required generally:
+ device: which device or cloud defined default_device
+ filesystem: which file system type
+ overwrite: indiscriminately create the file system
+ partition: when device does not define a partition,
+ setting this to a number will mean
+ device + partition. When set to 'auto', the
+ first free device or the first device which
+ matches both label and type will be used.
+
+ 'any' means the first filesystem that matches
+ on the device.
+
+ When 'cmd' is provided then no other parameter is required.
+ """
+ label = fs_cfg.get('label')
+ device = fs_cfg.get('device')
+ partition = str(fs_cfg.get('partition', 'any'))
+ fs_type = fs_cfg.get('filesystem')
+ fs_cmd = fs_cfg.get('cmd', [])
+ fs_opts = fs_cfg.get('extra_opts', [])
+ fs_replace = fs_cfg.get('replace_fs', False)
+ overwrite = fs_cfg.get('overwrite', False)
+
+ # This allows you to define the default ephemeral or swap
+ LOG.debug("Checking %s against default devices", device)
+
+ if not partition or partition.isdigit():
+ # Handle manual definition of partition
+ if partition.isdigit():
+ device = "%s%s" % (device, partition)
+ LOG.debug("Manual request of partition %s for %s",
+ partition, device)
+
+ # Check to see if the fs already exists
+ LOG.debug("Checking device %s", device)
+ check_label, check_fstype, _ = check_fs(device)
+ LOG.debug("Device %s has %s %s", device, check_label, check_fstype)
+
+ if check_label == label and check_fstype == fs_type:
+ LOG.debug("Existing file system found at %s", device)
+
+ if not overwrite:
+ LOG.debug("Device %s has required file system", device)
+ return
+ else:
+ LOG.warn("Destroying filesystem on %s", device)
+
+ else:
+ LOG.debug("Device %s is cleared for formating", device)
+
+ elif partition and str(partition).lower() in ('auto', 'any'):
+ # For auto devices, we match if the filesystem does exist
+ odevice = device
+ LOG.debug("Identifying device to create %s filesytem on", label)
+
+ # any mean pick the first match on the device with matching fs_type
+ label_match = True
+ if partition.lower() == 'any':
+ label_match = False
+
+ device, reuse = find_device_node(device, fs_type=fs_type, label=label,
+ label_match=label_match,
+ replace_fs=fs_replace)
+ LOG.debug("Automatic device for %s identified as %s", odevice, device)
+
+ if reuse:
+ LOG.debug("Found filesystem match, skipping formating.")
+ return
+
+ if not reuse and fs_replace and device:
+ LOG.debug("Replacing file system on %s as instructed." % device)
+
+ if not device:
+ LOG.debug("No device aviable that matches request. "
+ "Skipping fs creation for %s", fs_cfg)
+ return
+ elif not partition or str(partition).lower() == 'none':
+ LOG.debug("Using the raw device to place filesystem %s on" % label)
+
+ else:
+ LOG.debug("Error in device identification handling.")
+ return
+
+ LOG.debug("File system %s will be created on %s", label, device)
+
+ # Make sure the device is defined
+ if not device:
+ LOG.warn("Device is not known: %s", device)
+ return
+
+ # Check that we can create the FS
+ if not (fs_type or fs_cmd):
+ raise Exception("No way to create filesystem '%s'. fs_type or fs_cmd "
+ "must be set.", label)
+
+ # Create the commands
+ if fs_cmd:
+ fs_cmd = fs_cfg['cmd'] % {'label': label,
+ 'filesystem': fs_type,
+ 'device': device,
+ }
+ else:
+ # Find the mkfs command
+ mkfs_cmd = util.which("mkfs.%s" % fs_type)
+ if not mkfs_cmd:
+ mkfs_cmd = util.which("mk%s" % fs_type)
+
+ if not mkfs_cmd:
+ LOG.warn("Cannot create fstype '%s'. No mkfs.%s command", fs_type,
+ fs_type)
+ return
+
+ fs_cmd = [mkfs_cmd, device]
+
+ if label:
+ fs_cmd.extend(["-L", label])
+
+ # File systems that support the -F flag
+ if not fs_cmd and (overwrite or device_type(device) == "disk"):
+ fs_cmd.append(lookup_force_flag(fs_type))
+
+ # Add the extends FS options
+ if fs_opts:
+ fs_cmd.extend(fs_opts)
+
+ LOG.debug("Creating file system %s on %s", label, device)
+ LOG.debug(" Using cmd: %s", " ".join(fs_cmd))
+ try:
+ util.subp(fs_cmd)
+ except Exception as e:
+ raise Exception("Failed to exec of '%s':\n%s" % (fs_cmd, e))
diff --git a/cloudinit/config/cc_final_message.py b/cloudinit/config/cc_final_message.py
index 6b864fda..e92cba4a 100644
--- a/cloudinit/config/cc_final_message.py
+++ b/cloudinit/config/cc_final_message.py
@@ -54,7 +54,7 @@ def handle(_name, cfg, cloud, log, args):
'datasource': str(cloud.datasource),
}
util.multi_log("%s\n" % (templater.render_string(msg_in, subs)),
- console=False, stderr=True)
+ console=False, stderr=True, log=log)
except Exception:
util.logexc(log, "Failed to render final message template")
diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py
index 2d54aabf..0dd92a46 100644
--- a/cloudinit/config/cc_growpart.py
+++ b/cloudinit/config/cc_growpart.py
@@ -32,6 +32,7 @@ frequency = PER_ALWAYS
DEFAULT_CONFIG = {
'mode': 'auto',
'devices': ['/'],
+ 'ignore_growroot_disabled': False,
}
@@ -251,6 +252,12 @@ def handle(_name, cfg, _cloud, log, _args):
log.debug("growpart disabled: mode=%s" % mode)
return
+ if util.is_false(mycfg.get('ignore_growroot_disabled', False)):
+ if os.path.isfile("/etc/growroot-disabled"):
+ log.debug("growpart disabled: /etc/growroot-disabled exists")
+ log.debug("use ignore_growroot_disabled to ignore")
+ return
+
devices = util.get_cfg_option_list(cfg, "devices", ["/"])
if not len(devices):
log.debug("growpart: empty device list")
diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py
index 390ba711..80590118 100644
--- a/cloudinit/config/cc_mounts.py
+++ b/cloudinit/config/cc_mounts.py
@@ -20,6 +20,8 @@
from string import whitespace # pylint: disable=W0402
+import logging
+import os.path
import re
from cloudinit import type_utils
@@ -31,6 +33,8 @@ SHORTNAME = re.compile(SHORTNAME_FILTER)
WS = re.compile("[%s]+" % (whitespace))
FSTAB_PATH = "/etc/fstab"
+LOG = logging.getLogger(__name__)
+
def is_mdname(name):
# return true if this is a metadata service name
@@ -44,6 +48,33 @@ def is_mdname(name):
return False
+def sanitize_devname(startname, transformer, log):
+ log.debug("Attempting to determine the real name of %s", startname)
+
+ # workaround, allow user to specify 'ephemeral'
+ # rather than more ec2 correct 'ephemeral0'
+ devname = startname
+ if devname == "ephemeral":
+ devname = "ephemeral0"
+ log.debug("Adjusted mount option from ephemeral to ephemeral0")
+
+ (blockdev, part) = util.expand_dotted_devname(devname)
+
+ if is_mdname(blockdev):
+ orig = blockdev
+ blockdev = transformer(blockdev)
+ if not blockdev:
+ return None
+ if not blockdev.startswith("/"):
+ blockdev = "/dev/%s" % blockdev
+ log.debug("Mapped metadata name %s to %s", orig, blockdev)
+ else:
+ if SHORTNAME.match(startname):
+ blockdev = "/dev/%s" % blockdev
+
+ return devnode_for_dev_part(blockdev, part)
+
+
def handle(_name, cfg, cloud, log, _args):
# fs_spec, fs_file, fs_vfstype, fs_mntops, fs-freq, fs_passno
defvals = [None, None, "auto", "defaults,nobootwait", "0", "2"]
@@ -64,32 +95,15 @@ def handle(_name, cfg, cloud, log, _args):
(i + 1), type_utils.obj_name(cfgmnt[i]))
continue
- startname = str(cfgmnt[i][0])
- log.debug("Attempting to determine the real name of %s", startname)
-
- # workaround, allow user to specify 'ephemeral'
- # rather than more ec2 correct 'ephemeral0'
- if startname == "ephemeral":
- cfgmnt[i][0] = "ephemeral0"
- log.debug(("Adjusted mount option %s "
- "name from ephemeral to ephemeral0"), (i + 1))
-
- if is_mdname(startname):
- newname = cloud.device_name_to_device(startname)
- if not newname:
- log.debug("Ignoring nonexistant named mount %s", startname)
- cfgmnt[i][1] = None
- else:
- renamed = newname
- if not newname.startswith("/"):
- renamed = "/dev/%s" % newname
- cfgmnt[i][0] = renamed
- log.debug("Mapped metadata name %s to %s", startname, renamed)
- else:
- if SHORTNAME.match(startname):
- renamed = "/dev/%s" % startname
- log.debug("Mapped shortname name %s to %s", startname, renamed)
- cfgmnt[i][0] = renamed
+ start = str(cfgmnt[i][0])
+ sanitized = sanitize_devname(start, cloud.device_name_to_device, log)
+ if sanitized is None:
+ log.debug("Ignorming nonexistant named mount %s", start)
+ continue
+
+ if sanitized != start:
+ log.debug("changed %s => %s" % (start, sanitized))
+ cfgmnt[i][0] = sanitized
# in case the user did not quote a field (likely fs-freq, fs_passno)
# but do not convert None to 'None' (LP: #898365)
@@ -118,17 +132,14 @@ def handle(_name, cfg, cloud, log, _args):
# for each of the "default" mounts, add them only if no other
# entry has the same device name
for defmnt in defmnts:
- startname = defmnt[0]
- devname = cloud.device_name_to_device(startname)
- if devname is None:
- log.debug("Ignoring nonexistant named default mount %s", startname)
+ start = defmnt[0]
+ sanitized = sanitize_devname(start, cloud.device_name_to_device, log)
+ if sanitized is None:
+ log.debug("Ignoring nonexistant default named mount %s", start)
continue
- if devname.startswith("/"):
- defmnt[0] = devname
- else:
- defmnt[0] = "/dev/%s" % devname
-
- log.debug("Mapped default device %s to %s", startname, defmnt[0])
+ if sanitized != start:
+ log.debug("changed default device %s => %s" % (start, sanitized))
+ defmnt[0] = sanitized
cfgmnt_has = False
for cfgm in cfgmnt:
@@ -138,7 +149,7 @@ def handle(_name, cfg, cloud, log, _args):
if cfgmnt_has:
log.debug(("Not including %s, already"
- " previously included"), startname)
+ " previously included"), start)
continue
cfgmnt.append(defmnt)
@@ -198,3 +209,49 @@ def handle(_name, cfg, cloud, log, _args):
util.subp(("mount", "-a"))
except:
util.logexc(log, "Activating mounts via 'mount -a' failed")
+
+
+def devnode_for_dev_part(device, partition):
+ """
+ Find the name of the partition. While this might seem rather
+ straight forward, its not since some devices are '<device><partition>'
+ while others are '<device>p<partition>'. For example, /dev/xvda3 on EC2
+ will present as /dev/xvda3p1 for the first partition since /dev/xvda3 is
+ a block device.
+ """
+ if not os.path.exists(device):
+ return None
+
+ short_name = os.path.basename(device)
+ sys_path = "/sys/block/%s" % short_name
+
+ if not os.path.exists(sys_path):
+ LOG.debug("did not find entry for %s in /sys/block", short_name)
+ return None
+
+ sys_long_path = sys_path + "/" + short_name
+
+ if partition is not None:
+ partition = str(partition)
+
+ if partition is None:
+ valid_mappings = [sys_long_path + "1", sys_long_path + "p1"]
+ elif partition != "0":
+ valid_mappings = [sys_long_path + "%s" % partition,
+ sys_long_path + "p%s" % partition]
+ else:
+ valid_mappings = []
+
+ for cdisk in valid_mappings:
+ if not os.path.exists(cdisk):
+ continue
+
+ dev_path = "/dev/%s" % os.path.basename(cdisk)
+ if os.path.exists(dev_path):
+ return dev_path
+
+ if partition is None or partition == "0":
+ return device
+
+ LOG.debug("Did not fine partition %s for device %s", partition, device)
+ return None
diff --git a/cloudinit/config/cc_power_state_change.py b/cloudinit/config/cc_power_state_change.py
index 188047e5..e3150808 100644
--- a/cloudinit/config/cc_power_state_change.py
+++ b/cloudinit/config/cc_power_state_change.py
@@ -75,6 +75,12 @@ def load_power_state(cfg):
','.join(opt_map.keys()))
delay = pstate.get("delay", "now")
+ # convert integer 30 or string '30' to '+30'
+ try:
+ delay = "+%s" % int(delay)
+ except ValueError:
+ pass
+
if delay != "now" and not re.match(r"\+[0-9]+", delay):
raise TypeError("power_state[delay] must be 'now' or '+m' (minutes).")
diff --git a/cloudinit/config/cc_seed_random.py b/cloudinit/config/cc_seed_random.py
new file mode 100644
index 00000000..22a31f29
--- /dev/null
+++ b/cloudinit/config/cc_seed_random.py
@@ -0,0 +1,61 @@
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2013 Yahoo! Inc.
+#
+# Author: Joshua Harlow <harlowja@yahoo-inc.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3, as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import base64
+from StringIO import StringIO
+
+from cloudinit.settings import PER_INSTANCE
+from cloudinit import util
+
+frequency = PER_INSTANCE
+
+
+def _decode(data, encoding=None):
+ if not data:
+ return ''
+ if not encoding or encoding.lower() in ['raw']:
+ return data
+ elif encoding.lower() in ['base64', 'b64']:
+ return base64.b64decode(data)
+ elif encoding.lower() in ['gzip', 'gz']:
+ return util.decomp_gzip(data, quiet=False)
+ else:
+ raise IOError("Unknown random_seed encoding: %s" % (encoding))
+
+
+def handle(name, cfg, cloud, log, _args):
+ if not cfg or "random_seed" not in cfg:
+ log.debug(("Skipping module named %s, "
+ "no 'random_seed' configuration found"), name)
+ return
+
+ my_cfg = cfg['random_seed']
+ seed_path = my_cfg.get('file', '/dev/urandom')
+ seed_buf = StringIO()
+ seed_buf.write(_decode(my_cfg.get('data', ''),
+ encoding=my_cfg.get('encoding')))
+
+ metadata = cloud.datasource.metadata
+ if metadata and 'random_seed' in metadata:
+ seed_buf.write(metadata['random_seed'])
+
+ seed_data = seed_buf.getvalue()
+ if len(seed_data):
+ log.debug("%s: adding %s bytes of random seed entrophy to %s", name,
+ len(seed_data), seed_path)
+ util.append_file(seed_path, seed_data)
diff --git a/cloudinit/config/cc_ssh_authkey_fingerprints.py b/cloudinit/config/cc_ssh_authkey_fingerprints.py
index c38bcea2..be8083db 100644
--- a/cloudinit/config/cc_ssh_authkey_fingerprints.py
+++ b/cloudinit/config/cc_ssh_authkey_fingerprints.py
@@ -63,7 +63,7 @@ def _is_printable_key(entry):
def _pprint_key_entries(user, key_fn, key_entries, hash_meth='md5',
prefix='ci-info: '):
if not key_entries:
- message = ("%sno authorized ssh keys fingerprints found for user %s."
+ message = ("%sno authorized ssh keys fingerprints found for user %s.\n"
% (prefix, user))
util.multi_log(message)
return