From 787377dbc08f689e4de2a112b5446f89b46d769e Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 20 Jun 2012 16:37:10 -0700 Subject: The 'cfg' module has been removed and moved to 'helpers' to avoid confusion. These modules used the 'cfg' modules so adjusted them. --- cloudinit/config/__init__.py | 57 ++++++++ cloudinit/config/apt_pipelining.py | 57 ++++++++ cloudinit/config/apt_update_upgrade.py | 243 +++++++++++++++++++++++++++++++ cloudinit/config/bootcmd.py | 56 +++++++ cloudinit/config/byobu.py | 71 +++++++++ cloudinit/config/ca_certs.py | 99 +++++++++++++ cloudinit/config/cc_mcollective.py | 97 ++++++++++++ cloudinit/config/cc_puppet.py | 111 ++++++++++++++ cloudinit/config/chef.py | 128 ++++++++++++++++ cloudinit/config/disable_ec2_metadata.py | 36 +++++ cloudinit/config/final_message.py | 71 +++++++++ cloudinit/config/foo.py | 52 +++++++ cloudinit/config/grub_dpkg.py | 67 +++++++++ cloudinit/config/keys_to_console.py | 52 +++++++ cloudinit/config/landscape.py | 97 ++++++++++++ cloudinit/config/locale.py | 58 ++++++++ cloudinit/config/mounts.py | 200 +++++++++++++++++++++++++ cloudinit/config/phone_home.py | 111 ++++++++++++++ cloudinit/config/resizefs.py | 140 ++++++++++++++++++ cloudinit/config/rightscale_userdata.py | 102 +++++++++++++ cloudinit/config/rsyslog.py | 102 +++++++++++++ cloudinit/config/runcmd.py | 38 +++++ cloudinit/config/salt_minion.py | 60 ++++++++ cloudinit/config/scripts_per_boot.py | 41 ++++++ cloudinit/config/scripts_per_instance.py | 41 ++++++ cloudinit/config/scripts_per_once.py | 41 ++++++ cloudinit/config/scripts_user.py | 42 ++++++ cloudinit/config/set_hostname.py | 35 +++++ cloudinit/config/set_passwords.py | 151 +++++++++++++++++++ cloudinit/config/ssh.py | 131 +++++++++++++++++ cloudinit/config/ssh_import_id.py | 53 +++++++ cloudinit/config/timezone.py | 39 +++++ cloudinit/config/update_etc_hosts.py | 60 ++++++++ cloudinit/config/update_hostname.py | 41 ++++++ 34 files changed, 2780 insertions(+) create mode 100644 cloudinit/config/__init__.py create mode 100644 cloudinit/config/apt_pipelining.py create mode 100644 cloudinit/config/apt_update_upgrade.py create mode 100644 cloudinit/config/bootcmd.py create mode 100644 cloudinit/config/byobu.py create mode 100644 cloudinit/config/ca_certs.py create mode 100644 cloudinit/config/cc_mcollective.py create mode 100644 cloudinit/config/cc_puppet.py create mode 100644 cloudinit/config/chef.py create mode 100644 cloudinit/config/disable_ec2_metadata.py create mode 100644 cloudinit/config/final_message.py create mode 100644 cloudinit/config/foo.py create mode 100644 cloudinit/config/grub_dpkg.py create mode 100644 cloudinit/config/keys_to_console.py create mode 100644 cloudinit/config/landscape.py create mode 100644 cloudinit/config/locale.py create mode 100644 cloudinit/config/mounts.py create mode 100644 cloudinit/config/phone_home.py create mode 100644 cloudinit/config/resizefs.py create mode 100644 cloudinit/config/rightscale_userdata.py create mode 100644 cloudinit/config/rsyslog.py create mode 100644 cloudinit/config/runcmd.py create mode 100644 cloudinit/config/salt_minion.py create mode 100644 cloudinit/config/scripts_per_boot.py create mode 100644 cloudinit/config/scripts_per_instance.py create mode 100644 cloudinit/config/scripts_per_once.py create mode 100644 cloudinit/config/scripts_user.py create mode 100644 cloudinit/config/set_hostname.py create mode 100644 cloudinit/config/set_passwords.py create mode 100644 cloudinit/config/ssh.py create mode 100644 cloudinit/config/ssh_import_id.py create mode 100644 cloudinit/config/timezone.py create mode 100644 cloudinit/config/update_etc_hosts.py create mode 100644 cloudinit/config/update_hostname.py (limited to 'cloudinit/config') diff --git a/cloudinit/config/__init__.py b/cloudinit/config/__init__.py new file mode 100644 index 00000000..5cd08575 --- /dev/null +++ b/cloudinit/config/__init__.py @@ -0,0 +1,57 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2008-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Chuck Short +# Author: Juerg Haefliger +# +# 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 . +# + +from cloudinit.settings import (PER_INSTANCE, FREQUENCIES) + +from cloudinit import log as logging + +LOG = logging.getLogger(__name__) + +# TODO remove this from being a prefix?? +TRANSFORM_PREFIX = '' # "cc_" + + +def form_transform_name(name, mod=__name__): + canon_name = name.replace("-", "_") + if canon_name.lower().endswith(".py"): + canon_name = canon_name[0:(len(canon_name) - 3)] + canon_name = canon_name.strip() + if not canon_name: + return None + if not canon_name.startswith(TRANSFORM_PREFIX): + canon_name = '%s%s' % (TRANSFORM_PREFIX, canon_name) + return ".".join([str(mod), str(canon_name)]) + + +def fixup_transform(mod, def_freq=PER_INSTANCE): + if not hasattr(mod, 'frequency'): + setattr(mod, 'frequency', def_freq) + else: + freq = mod.frequency + if freq and freq not in FREQUENCIES: + LOG.warn("Transform %s has an unknown frequency %s", mod, freq) + if not hasattr(mod, 'handle'): + def empty_handle(_name, _cfg, _cloud, _log, _args): + pass + setattr(mod, 'handle', empty_handle) + if not hasattr(mod, 'distros'): + setattr(mod, 'distros', None) + return mod diff --git a/cloudinit/config/apt_pipelining.py b/cloudinit/config/apt_pipelining.py new file mode 100644 index 00000000..f460becb --- /dev/null +++ b/cloudinit/config/apt_pipelining.py @@ -0,0 +1,57 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Ben Howard +# +# 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 . + +from cloudinit import util +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + +distros = ['ubuntu', 'debian'] + +DEFAULT_FILE = "/etc/apt/apt.conf.d/90cloud-init-pipelining" + +# Acquire::http::Pipeline-Depth can be a value +# from 0 to 5 indicating how many outstanding requests APT should send. +# A value of zero MUST be specified if the remote host does not properly linger +# on TCP connections - otherwise data corruption will occur. + + +def handle(_name, cfg, cloud, log, _args): + + apt_pipe_value = util.get_cfg_option_str(cfg, "apt_pipelining", False) + apt_pipe_value_s = str(apt_pipe_value).lower().strip() + + if apt_pipe_value_s == "false": + write_apt_snippet(cloud, "0", log, DEFAULT_FILE) + elif apt_pipe_value_s in ("none", "unchanged", "os"): + return + elif apt_pipe_value_s in [str(b) for b in xrange(0, 6)]: + write_apt_snippet(cloud, apt_pipe_value_s, log, DEFAULT_FILE) + else: + log.warn("Invalid option for apt_pipeling: %s", apt_pipe_value) + + +def write_apt_snippet(cloud, setting, log, f_name): + """ Writes f_name with apt pipeline depth 'setting' """ + + file_contents = ("//Written by cloud-init per 'apt_pipelining'\n" + 'Acquire::http::Pipeline-Depth "%s";\n') % (setting) + + util.write_file(cloud.paths.join(False, f_name), file_contents) + + log.debug("Wrote %s with apt pipeline depth setting %s", f_name, setting) diff --git a/cloudinit/config/apt_update_upgrade.py b/cloudinit/config/apt_update_upgrade.py new file mode 100644 index 00000000..f5b4b58f --- /dev/null +++ b/cloudinit/config/apt_update_upgrade.py @@ -0,0 +1,243 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import glob +import os + +from cloudinit import templater +from cloudinit import util + +distros = ['ubuntu', 'debian'] + +PROXY_TPL = "Acquire::HTTP::Proxy \"%s\";\n" + + +def handle(_name, cfg, cloud, log, _args): + update = util.get_cfg_option_bool(cfg, 'apt_update', False) + upgrade = util.get_cfg_option_bool(cfg, 'apt_upgrade', False) + + release = get_release() + + mirror = find_apt_mirror(cloud, cfg) + + log.debug("Selected mirror at: %s" % mirror) + + if not util.get_cfg_option_bool(cfg, + 'apt_preserve_sources_list', False): + generate_sources_list(release, mirror, cloud, log) + old_mir = util.get_cfg_option_str(cfg, 'apt_old_mirror', + "archive.ubuntu.com/ubuntu") + rename_apt_lists(old_mir, mirror) + + # Set up any apt proxy + proxy = cfg.get("apt_proxy", None) + proxy_filename = "/etc/apt/apt.conf.d/95cloud-init-proxy" + if proxy: + try: + # See man 'apt.conf' + contents = PROXY_TPL % (proxy) + util.write_file(cloud.paths.join(False, proxy_filename), + contents) + except Exception as e: + util.logexc(log, "Failed to write proxy to %s", proxy_filename) + elif os.path.isfile(proxy_filename): + util.del_file(proxy_filename) + + # Process 'apt_sources' + if 'apt_sources' in cfg: + errors = add_sources(cloud, cfg['apt_sources'], + {'MIRROR': mirror, 'RELEASE': release}) + for e in errors: + log.warn("Source Error: %s", ':'.join(e)) + + dconf_sel = util.get_cfg_option_str(cfg, 'debconf_selections', False) + if dconf_sel: + log.debug("setting debconf selections per cloud config") + try: + util.subp(('debconf-set-selections', '-'), dconf_sel) + except: + util.logexc(log, "Failed to run debconf-set-selections") + + pkglist = util.get_cfg_option_list(cfg, 'packages', []) + + errors = [] + if update or len(pkglist) or upgrade: + try: + cloud.distro.update_package_sources() + except Exception as e: + util.logexc(log, "Package update failed") + errors.append(e) + + if upgrade: + try: + cloud.distro.package_command("upgrade") + except Exception as e: + util.logexc(log, "Package upgrade failed") + errors.append(e) + + if len(pkglist): + try: + cloud.distro.install_packages(pkglist) + except Exception as e: + util.logexc(log, "Failed to install packages: %s ", pkglist) + errors.append(e) + + if len(errors): + log.warn("%s failed with exceptions, re-raising the last one", + len(errors)) + raise errors[-1] + + +def mirror2lists_fileprefix(mirror): + string = mirror + # take of http:// or ftp:// + if string.endswith("/"): + string = string[0:-1] + pos = string.find("://") + if pos >= 0: + string = string[pos + 3:] + string = string.replace("/", "_") + return string + + +def rename_apt_lists(omirror, new_mirror, lists_d="/var/lib/apt/lists"): + oprefix = "%s/%s" % (lists_d, mirror2lists_fileprefix(omirror)) + nprefix = "%s/%s" % (lists_d, mirror2lists_fileprefix(new_mirror)) + if oprefix == nprefix: + return + olen = len(oprefix) + for filename in glob.glob("%s_*" % oprefix): + # TODO use the cloud.paths.join... + util.rename(filename, "%s%s" % (nprefix, filename[olen:])) + + +def get_release(): + (stdout, _stderr) = util.subp(['lsb_release', '-cs']) + return stdout.strip() + + +def generate_sources_list(codename, mirror, cloud, log): + template_fn = cloud.get_template_filename('sources.list') + if template_fn: + params = {'mirror': mirror, 'codename': codename} + out_fn = cloud.paths.join(False, '/etc/apt/sources.list') + templater.render_to_file(template_fn, out_fn, params) + else: + log.warn("No template found, not rendering /etc/apt/sources.list") + + +def add_sources(cloud, srclist, template_params=None): + """ + add entries in /etc/apt/sources.list.d for each abbreviated + sources.list entry in 'srclist'. When rendering template, also + include the values in dictionary searchList + """ + if template_params is None: + template_params = {} + + errorlist = [] + for ent in srclist: + if 'source' not in ent: + errorlist.append(["", "missing source"]) + continue + + source = ent['source'] + if source.startswith("ppa:"): + try: + util.subp(["add-apt-repository", source]) + except: + errorlist.append([source, "add-apt-repository failed"]) + continue + + source = templater.render_string(source, template_params) + + if 'filename' not in ent: + ent['filename'] = 'cloud_config_sources.list' + + if not ent['filename'].startswith("/"): + ent['filename'] = os.path.join("/etc/apt/sources.list.d/", + ent['filename']) + + if ('keyid' in ent and 'key' not in ent): + ks = "keyserver.ubuntu.com" + if 'keyserver' in ent: + ks = ent['keyserver'] + try: + ent['key'] = util.getkeybyid(ent['keyid'], ks) + except: + errorlist.append([source, "failed to get key from %s" % ks]) + continue + + if 'key' in ent: + try: + util.subp(('apt-key', 'add', '-'), ent['key']) + except: + errorlist.append([source, "failed add key"]) + + try: + contents = "%s\n" % (source) + util.write_file(cloud.paths.join(False, ent['filename']), + contents, omode="ab") + except: + errorlist.append([source, + "failed write to file %s" % ent['filename']]) + + return errorlist + + +def find_apt_mirror(cloud, cfg): + """ find an apt_mirror given the cloud and cfg provided """ + + mirror = None + + cfg_mirror = cfg.get("apt_mirror", None) + if cfg_mirror: + mirror = cfg["apt_mirror"] + elif "apt_mirror_search" in cfg: + mirror = util.search_for_mirror(cfg['apt_mirror_search']) + else: + mirror = cloud.get_local_mirror() + + mydom = "" + + doms = [] + + if not mirror: + # if we have a fqdn, then search its domain portion first + (_hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) + mydom = ".".join(fqdn.split(".")[1:]) + if mydom: + doms.append(".%s" % mydom) + + if not mirror: + doms.extend((".localdomain", "",)) + + mirror_list = [] + distro = cloud.distro.name + mirrorfmt = "http://%s-mirror%s/%s" % (distro, "%s", distro) + for post in doms: + mirror_list.append(mirrorfmt % (post)) + + mirror = util.search_for_mirror(mirror_list) + + if not mirror: + mirror = cloud.distro.get_package_mirror() + + return mirror diff --git a/cloudinit/config/bootcmd.py b/cloudinit/config/bootcmd.py new file mode 100644 index 00000000..635e3a1f --- /dev/null +++ b/cloudinit/config/bootcmd.py @@ -0,0 +1,56 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os +import tempfile + +from cloudinit import util +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + + +def handle(name, cfg, cloud, log, _args): + + if "bootcmd" not in cfg: + log.debug(("Skipping transform named %s," + " no 'bootcmd' key in configuration"), name) + return + + with tempfile.NamedTemporaryFile(suffix=".sh") as tmpf: + try: + content = util.shellify(cfg["bootcmd"]) + tmpf.write(content) + tmpf.flush() + except: + util.logexc(log, "Failed to shellify bootcmd") + raise + + try: + env = os.environ.copy() + iid = cloud.get_instance_id() + if iid: + env['INSTANCE_ID'] = str(iid) + cmd = ['/bin/sh', tmpf.name] + util.subp(cmd, env=env, capture=False) + except: + util.logexc(log, + ("Failed to run bootcmd transform %s"), name) + raise diff --git a/cloudinit/config/byobu.py b/cloudinit/config/byobu.py new file mode 100644 index 00000000..741aa934 --- /dev/null +++ b/cloudinit/config/byobu.py @@ -0,0 +1,71 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util + +distros = ['ubuntu', 'debian'] + + +def handle(name, cfg, _cloud, log, args): + if len(args) != 0: + value = args[0] + else: + value = util.get_cfg_option_str(cfg, "byobu_by_default", "") + + if not value: + log.debug("Skipping transform named %s, no 'byobu' values found", name) + return + + if value == "user" or value == "system": + value = "enable-%s" % value + + valid = ("enable-user", "enable-system", "enable", + "disable-user", "disable-system", "disable") + if not value in valid: + log.warn("Unknown value %s for byobu_by_default", value) + + mod_user = value.endswith("-user") + mod_sys = value.endswith("-system") + if value.startswith("enable"): + bl_inst = "install" + dc_val = "byobu byobu/launch-by-default boolean true" + mod_sys = True + else: + if value == "disable": + mod_user = True + mod_sys = True + bl_inst = "uninstall" + dc_val = "byobu byobu/launch-by-default boolean false" + + shcmd = "" + if mod_user: + user = util.get_cfg_option_str(cfg, "user", "ubuntu") + shcmd += " sudo -Hu \"%s\" byobu-launcher-%s" % (user, bl_inst) + shcmd += " || X=$(($X+1)); " + if mod_sys: + shcmd += "echo \"%s\" | debconf-set-selections" % dc_val + shcmd += " && dpkg-reconfigure byobu --frontend=noninteractive" + shcmd += " || X=$(($X+1)); " + + cmd = ["/bin/sh", "-c", "%s %s %s" % ("X=0;", shcmd, "exit $X")] + + log.debug("Setting byobu to %s", value) + + util.subp(cmd) diff --git a/cloudinit/config/ca_certs.py b/cloudinit/config/ca_certs.py new file mode 100644 index 00000000..56c41561 --- /dev/null +++ b/cloudinit/config/ca_certs.py @@ -0,0 +1,99 @@ +# vi: ts=4 expandtab +# +# Author: Mike Milner +# +# 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 . + +import os + +from cloudinit import util + +CA_CERT_PATH = "/usr/share/ca-certificates/" +CA_CERT_FILENAME = "cloud-init-ca-certs.crt" +CA_CERT_CONFIG = "/etc/ca-certificates.conf" +CA_CERT_SYSTEM_PATH = "/etc/ssl/certs/" + +distros = ['ubuntu'] + + +def update_ca_certs(): + """ + Updates the CA certificate cache on the current machine. + """ + util.subp(["update-ca-certificates"]) + + +def add_ca_certs(cloud, certs): + """ + Adds certificates to the system. To actually apply the new certificates + you must also call L{update_ca_certs}. + + @param certs: A list of certificate strings. + """ + if certs: + # First ensure they are strings... + cert_file_contents = "\n".join([str(c) for c in certs]) + cert_file_fullpath = os.path.join(CA_CERT_PATH, CA_CERT_FILENAME) + cert_file_fullpath = cloud.paths.join(False, cert_file_fullpath) + util.write_file(cert_file_fullpath, cert_file_contents, mode=0644) + # Append cert filename to CA_CERT_CONFIG file. + util.write_file(cloud.paths.join(False, CA_CERT_CONFIG), + "\n%s" % CA_CERT_FILENAME, omode="ab") + + +def remove_default_ca_certs(cloud): + """ + Removes all default trusted CA certificates from the system. To actually + apply the change you must also call L{update_ca_certs}. + """ + util.delete_dir_contents(cloud.paths.join(False, CA_CERT_PATH)) + util.delete_dir_contents(cloud.paths.join(False, CA_CERT_SYSTEM_PATH)) + util.write_file(cloud.paths.join(False, CA_CERT_CONFIG), "", mode=0644) + debconf_sel = "ca-certificates ca-certificates/trust_new_crts select no" + util.subp(('debconf-set-selections', '-'), debconf_sel) + + +def handle(name, cfg, cloud, log, _args): + """ + Call to handle ca-cert sections in cloud-config file. + + @param name: The module name "ca-cert" from cloud.cfg + @param cfg: A nested dict containing the entire cloud config contents. + @param cloud: The L{CloudInit} object in use. + @param log: Pre-initialized Python logger object to use for logging. + @param args: Any module arguments from cloud.cfg + """ + # If there isn't a ca-certs section in the configuration don't do anything + if "ca-certs" not in cfg: + log.debug(("Skipping transform named %s," + " no 'ca-certs' key in configuration"), name) + return + + ca_cert_cfg = cfg['ca-certs'] + + # If there is a remove-defaults option set to true, remove the system + # default trusted CA certs first. + if ca_cert_cfg.get("remove-defaults", False): + log.debug("Removing default certificates") + remove_default_ca_certs(cloud) + + # If we are given any new trusted CA certs to add, add them. + if "trusted" in ca_cert_cfg: + trusted_certs = util.get_cfg_option_list(ca_cert_cfg, "trusted") + if trusted_certs: + log.debug("Adding %d certificates" % len(trusted_certs)) + add_ca_certs(cloud, trusted_certs) + + # Update the system with the new cert configuration. + log.debug("Updating certificates") + update_ca_certs() diff --git a/cloudinit/config/cc_mcollective.py b/cloudinit/config/cc_mcollective.py new file mode 100644 index 00000000..4cec6494 --- /dev/null +++ b/cloudinit/config/cc_mcollective.py @@ -0,0 +1,97 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Marc Cluet +# Based on code by Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from StringIO import StringIO + +from cloudinit import helpers +from cloudinit import util + +PUBCERT_FILE = "/etc/mcollective/ssl/server-public.pem" +PRICERT_FILE = "/etc/mcollective/ssl/server-private.pem" + + +def handle(name, cfg, cloud, log, _args): + + # If there isn't a mcollective key in the configuration don't do anything + if 'mcollective' not in cfg: + log.debug(("Skipping transform named %s, " + "no 'mcollective' key in configuration"), name) + return + + mcollective_cfg = cfg['mcollective'] + + # Start by installing the mcollective package ... + cloud.distro.install_packages(("mcollective",)) + + # ... and then update the mcollective configuration + if 'conf' in mcollective_cfg: + # Create object for reading server.cfg values + mcollective_config = helpers.DefaultingConfigParser() + # Read server.cfg values from original file in order to be able to mix + # the rest up + server_cfg_fn = cloud.paths.join(True, '/etc/mcollective/server.cfg') + old_contents = util.load_file(server_cfg_fn) + # It doesn't contain any sections so just add one temporarily + # Use a hash id based off the contents, + # just incase of conflicts... (try to not have any...) + # This is so that an error won't occur when reading (and no + # sections exist in the file) + section_tpl = "[nullsection_%s]" + attempts = 0 + section_head = section_tpl % (attempts) + while old_contents.find(section_head) != -1: + attempts += 1 + section_head = section_tpl % (attempts) + sectioned_contents = "%s\n%s" % (section_head, old_contents) + mcollective_config.readfp(StringIO(sectioned_contents), + filename=server_cfg_fn) + for (cfg_name, cfg) in mcollective_cfg['conf'].iteritems(): + if cfg_name == 'public-cert': + pubcert_fn = cloud.paths.join(True, PUBCERT_FILE) + util.write_file(pubcert_fn, cfg, mode=0644) + mcollective_config.set(cfg_name, + 'plugin.ssl_server_public', pubcert_fn) + mcollective_config.set(cfg_name, 'securityprovider', 'ssl') + elif cfg_name == 'private-cert': + pricert_fn = cloud.paths.join(True, PRICERT_FILE) + util.write_file(pricert_fn, cfg, mode=0600) + mcollective_config.set(cfg_name, + 'plugin.ssl_server_private', pricert_fn) + mcollective_config.set(cfg_name, 'securityprovider', 'ssl') + else: + # Iterate throug the config items, we'll use ConfigParser.set + # to overwrite or create new items as needed + for (o, v) in cfg.iteritems(): + mcollective_config.set(cfg_name, o, v) + # We got all our config as wanted we'll rename + # the previous server.cfg and create our new one + old_fn = "%s.old" % (server_cfg_fn) + util.rename(server_cfg_fn, old_fn) + # Now we got the whole file, write to disk except the section + # we added so that config parser won't error out when trying to read. + # Note below, that we've just used ConfigParser because it generally + # works. Below, we remove the initial 'nullsection' header. + contents = mcollective_config.stringify() + contents = contents.replace("%s\n" % (section_head), "") + util.write_file(server_cfg_fn, contents, mode=0644) + + # Start mcollective + util.subp(['service', 'mcollective', 'start'], capture=False) diff --git a/cloudinit/config/cc_puppet.py b/cloudinit/config/cc_puppet.py new file mode 100644 index 00000000..5fb88bf2 --- /dev/null +++ b/cloudinit/config/cc_puppet.py @@ -0,0 +1,111 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from StringIO import StringIO + +import os +import pwd +import socket + +from cloudinit import helpers +from cloudinit import util + + +def handle(name, cfg, cloud, log, _args): + # If there isn't a puppet key in the configuration don't do anything + if 'puppet' not in cfg: + log.debug(("Skipping transform named %s," + " no 'puppet' configuration found"), name) + return + + puppet_cfg = cfg['puppet'] + + # Start by installing the puppet package ... + cloud.distro.install_packages(["puppet"]) + + # ... and then update the puppet configuration + if 'conf' in puppet_cfg: + # Add all sections from the conf object to puppet.conf + puppet_conf_fn = cloud.paths.join(False, '/etc/puppet/puppet.conf') + contents = util.load_file(puppet_conf_fn) + # Create object for reading puppet.conf values + puppet_config = helpers.DefaultingConfigParser() + # Read puppet.conf values from original file in order to be able to + # mix the rest up. First clean them up (TODO is this really needed??) + cleaned_lines = [i.lstrip() for i in contents.splitlines()] + cleaned_contents = '\n'.join(cleaned_lines) + puppet_config.readfp(StringIO(cleaned_contents), + filename=puppet_conf_fn) + for (cfg_name, cfg) in puppet_cfg['conf'].iteritems(): + # Cert configuration is a special case + # Dump the puppet master ca certificate in the correct place + if cfg_name == 'ca_cert': + # Puppet ssl sub-directory isn't created yet + # Create it with the proper permissions and ownership + pp_ssl_dir = cloud.paths.join(False, '/var/lib/puppet/ssl') + util.ensure_dir(pp_ssl_dir, 0771) + util.chownbyid(pp_ssl_dir, + pwd.getpwnam('puppet').pw_uid, 0) + pp_ssl_certs = cloud.paths.join(False, + '/var/lib/puppet/ssl/certs/') + util.ensure_dir(pp_ssl_certs) + util.chownbyid(pp_ssl_certs, + pwd.getpwnam('puppet').pw_uid, 0) + pp_ssl_ca_certs = cloud.paths.join(False, + ('/var/lib/puppet/' + 'ssl/certs/ca.pem')) + util.write_file(pp_ssl_ca_certs, cfg) + util.chownbyid(pp_ssl_ca_certs, + pwd.getpwnam('puppet').pw_uid, 0) + else: + # Iterate throug the config items, we'll use ConfigParser.set + # to overwrite or create new items as needed + for (o, v) in cfg.iteritems(): + if o == 'certname': + # Expand %f as the fqdn + # TODO should this use the cloud fqdn?? + v = v.replace("%f", socket.getfqdn()) + # Expand %i as the instance id + v = v.replace("%i", cloud.get_instance_id()) + # certname needs to be downcased + v = v.lower() + puppet_config.set(cfg_name, o, v) + # We got all our config as wanted we'll rename + # the previous puppet.conf and create our new one + puppet_conf_old_fn = "%s.old" % (puppet_conf_fn) + util.rename(puppet_conf_fn, puppet_conf_old_fn) + util.write_file(puppet_conf_fn, puppet_config.stringify()) + + # Set puppet to automatically start + if os.path.exists('/etc/default/puppet'): + util.subp(['sed', '-i', + '-e', 's/^START=.*/START=yes/', + '/etc/default/puppet'], capture=False) + elif os.path.exists('/bin/systemctl'): + util.subp(['/bin/systemctl', 'enable', 'puppet.service'], + capture=False) + elif os.path.exists('/sbin/chkconfig'): + util.subp(['/sbin/chkconfig', 'puppet', 'on'], capture=False) + else: + log.warn(("Sorry we do not know how to enable" + " puppet services on this system")) + + # Start puppetd + util.subp(['service', 'puppet', 'start'], capture=False) diff --git a/cloudinit/config/chef.py b/cloudinit/config/chef.py new file mode 100644 index 00000000..4e8ef346 --- /dev/null +++ b/cloudinit/config/chef.py @@ -0,0 +1,128 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Avishai Ish-Shalom +# Author: Mike Moulton +# Author: Juerg Haefliger +# +# 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 . + +import json +import os + +from cloudinit import templater +from cloudinit import util + +ruby_version_default = "1.8" + + +def handle(name, cfg, cloud, log, _args): + + # If there isn't a chef key in the configuration don't do anything + if 'chef' not in cfg: + log.debug(("Skipping transform named %s," + " no 'chef' key in configuration"), name) + return + chef_cfg = cfg['chef'] + + # Ensure the chef directories we use exist + c_dirs = [ + '/etc/chef', + '/var/log/chef', + '/var/lib/chef', + '/var/cache/chef', + '/var/backups/chef', + '/var/run/chef', + ] + for d in c_dirs: + util.ensure_dir(cloud.paths.join(False, d)) + + # Set the validation key based on the presence of either 'validation_key' + # or 'validation_cert'. In the case where both exist, 'validation_key' + # takes precedence + for key in ('validation_key', 'validation_cert'): + if key in chef_cfg and chef_cfg[key]: + v_fn = cloud.paths.join(False, '/etc/chef/validation.pem') + util.write_file(v_fn, chef_cfg[key]) + break + + # Create the chef config from template + template_fn = cloud.get_template_filename('chef_client.rb') + if template_fn: + iid = str(cloud.datasource.get_instance_id()) + params = { + 'server_url': chef_cfg['server_url'], + 'node_name': util.get_cfg_option_str(chef_cfg, 'node_name', iid), + 'environment': util.get_cfg_option_str(chef_cfg, 'environment', + '_default'), + 'validation_name': chef_cfg['validation_name'] + } + out_fn = cloud.paths.join(False, '/etc/chef/client.rb') + templater.render_to_file(template_fn, out_fn, params) + else: + log.warn("No template found, not rendering to /etc/chef/client.rb") + + # set the firstboot json + initial_json = {} + if 'run_list' in chef_cfg: + initial_json['run_list'] = chef_cfg['run_list'] + if 'initial_attributes' in chef_cfg: + initial_attributes = chef_cfg['initial_attributes'] + for k in list(initial_attributes.keys()): + initial_json[k] = initial_attributes[k] + firstboot_fn = cloud.paths.join(False, '/etc/chef/firstboot.json') + util.write_file(firstboot_fn, json.dumps(initial_json)) + + # If chef is not installed, we install chef based on 'install_type' + if not os.path.isfile('/usr/bin/chef-client'): + install_type = util.get_cfg_option_str(chef_cfg, 'install_type', + 'packages') + if install_type == "gems": + # this will install and run the chef-client from gems + chef_version = util.get_cfg_option_str(chef_cfg, 'version', None) + ruby_version = util.get_cfg_option_str(chef_cfg, 'ruby_version', + ruby_version_default) + install_chef_from_gems(cloud.distro, ruby_version, chef_version) + # and finally, run chef-client + log.debug('Running chef-client') + util.subp(['/usr/bin/chef-client', '-d', '-i', '1800', '-s', '20']) + elif install_type == 'packages': + # this will install and run the chef-client from packages + cloud.distro.install_packages(('chef',)) + else: + log.warn("Unknown chef install type %s", install_type) + + +def get_ruby_packages(version): + # return a list of packages needed to install ruby at version + pkgs = ['ruby%s' % version, 'ruby%s-dev' % version] + if version == "1.8": + pkgs.extend(('libopenssl-ruby1.8', 'rubygems1.8')) + return pkgs + + +def install_chef_from_gems(ruby_version, chef_version, distro): + distro.install_packages(get_ruby_packages(ruby_version)) + if not os.path.exists('/usr/bin/gem'): + util.sym_link('/usr/bin/gem%s' % ruby_version, '/usr/bin/gem') + if not os.path.exists('/usr/bin/ruby'): + util.sym_link('/usr/bin/ruby%s' % ruby_version, '/usr/bin/ruby') + if chef_version: + util.subp(['/usr/bin/gem', 'install', 'chef', + '-v %s' % chef_version, '--no-ri', + '--no-rdoc', '--bindir', '/usr/bin', '-q']) + else: + util.subp(['/usr/bin/gem', 'install', 'chef', + '--no-ri', '--no-rdoc', '--bindir', + '/usr/bin', '-q']) diff --git a/cloudinit/config/disable_ec2_metadata.py b/cloudinit/config/disable_ec2_metadata.py new file mode 100644 index 00000000..c7d26029 --- /dev/null +++ b/cloudinit/config/disable_ec2_metadata.py @@ -0,0 +1,36 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util + +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + +reject_cmd = ['route', 'add', '-host', '169.254.169.254', 'reject'] + + +def handle(name, cfg, _cloud, log, _args): + disabled = util.get_cfg_option_bool(cfg, "disable_ec2_metadata", False) + if disabled: + util.subp(reject_cmd) + else: + log.debug(("Skipping transform named %s," + " disabling the ec2 route not enabled"), name) diff --git a/cloudinit/config/final_message.py b/cloudinit/config/final_message.py new file mode 100644 index 00000000..c257b6d0 --- /dev/null +++ b/cloudinit/config/final_message.py @@ -0,0 +1,71 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import sys + +from cloudinit import templater +from cloudinit import util +from cloudinit import version + +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + +final_message_def = ("Cloud-init v. {{version}} finished at {{timestamp}}." + " Up {{uptime}} seconds.") + + +def handle(_name, cfg, cloud, log, args): + + msg_in = None + if len(args) != 0: + msg_in = args[0] + else: + msg_in = util.get_cfg_option_str(cfg, "final_message") + + if not msg_in: + template_fn = cloud.get_template_filename('final_message') + if template_fn: + msg_in = util.load_file(template_fn) + + if not msg_in: + msg_in = final_message_def + + uptime = util.uptime() + ts = util.time_rfc2822() + cver = version.version_string() + try: + subs = { + 'uptime': uptime, + 'timestamp': ts, + 'version': cver, + } + # Use stdout, stderr or the logger?? + content = templater.render_string(msg_in, subs) + sys.stderr.write("%s\n" % (content)) + except Exception: + util.logexc(log, "Failed to render final message template") + + boot_fin_fn = cloud.paths.boot_finished + try: + contents = "%s - %s - v. %s\n" % (uptime, ts, cver) + util.write_file(boot_fin_fn, contents) + except: + util.logexc(log, "Failed to write boot finished file %s", boot_fin_fn) diff --git a/cloudinit/config/foo.py b/cloudinit/config/foo.py new file mode 100644 index 00000000..99135704 --- /dev/null +++ b/cloudinit/config/foo.py @@ -0,0 +1,52 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit.settings import PER_INSTANCE + +# Modules are expected to have the following attributes. +# 1. A required 'handle' method which takes the following params. +# a) The name will not be this files name, but instead +# the name specified in configuration (which is the name +# which will be used to find this module). +# b) A configuration object that is the result of the merging +# of cloud configs configuration with legacy configuration +# as well as any datasource provided configuration +# c) A cloud object that can be used to access various +# datasource and paths for the given distro and data provided +# by the various datasource instance types. +# d) A argument list that may or may not be empty to this module. +# Typically those are from module configuration where the module +# is defined with some extra configuration that will eventually +# be translated from yaml into arguments to this module. +# 2. A optional 'frequency' that defines how often this module should be ran. +# Typically one of PER_INSTANCE, PER_ALWAYS, PER_ONCE. If not +# provided PER_INSTANCE will be assumed. +# See settings.py for these constants. +# 3. A optional 'distros' array/set/tuple that defines the known distros +# this module will work with (if not all of them). This is used to write +# a warning out if a module is being ran on a untested distribution for +# informational purposes. If non existent all distros are assumed and +# no warning occurs. + +frequency = PER_INSTANCE + + +def handle(name, _cfg, _cloud, log, _args): + log.debug("Hi from transform %s", name) diff --git a/cloudinit/config/grub_dpkg.py b/cloudinit/config/grub_dpkg.py new file mode 100644 index 00000000..02f05ce3 --- /dev/null +++ b/cloudinit/config/grub_dpkg.py @@ -0,0 +1,67 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +distros = ['ubuntu', 'debian'] + + +def handle(_name, cfg, _cloud, log, _args): + idevs = None + idevs_empty = None + + 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) + + if ((os.path.exists("/dev/sda1") and not os.path.exists("/dev/sda")) or + (os.path.exists("/dev/xvda1") and not os.path.exists("/dev/xvda"))): + if idevs == None: + idevs = "" + if idevs_empty == None: + idevs_empty = "true" + else: + if idevs_empty == None: + idevs_empty = "false" + if idevs == None: + idevs = "/dev/sda" + for dev in ("/dev/sda", "/dev/vda", "/dev/sda1", "/dev/vda1"): + if os.path.exists(dev): + idevs = dev + break + + # now idevs and idevs_empty are set to determined values + # or, those set by user + + dconf_sel = (("grub-pc grub-pc/install_devices string %s\n" + "grub-pc grub-pc/install_devices_empty boolean %s\n") % + (idevs, idevs_empty)) + + log.debug("Setting grub debconf-set-selections with '%s','%s'" % + (idevs, idevs_empty)) + + try: + util.subp(['debconf-set-selections'], dconf_sel) + except: + util.logexc(log, "Failed to run debconf-set-selections for grub-dpkg") diff --git a/cloudinit/config/keys_to_console.py b/cloudinit/config/keys_to_console.py new file mode 100644 index 00000000..40758198 --- /dev/null +++ b/cloudinit/config/keys_to_console.py @@ -0,0 +1,52 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit.settings import PER_INSTANCE +from cloudinit import util + +frequency = PER_INSTANCE + +# This is a tool that cloud init provides +helper_tool = '/usr/lib/cloud-init/write-ssh-key-fingerprints' + + +def handle(name, cfg, cloud, log, _args): + if not os.path.exists(helper_tool): + log.warn(("Unable to activate transform %s," + " helper tool not found at %s"), name, helper_tool) + return + + fp_blacklist = util.get_cfg_option_list(cfg, + "ssh_fp_console_blacklist", []) + key_blacklist = util.get_cfg_option_list(cfg, + "ssh_key_console_blacklist", + ["ssh-dss"]) + + try: + cmd = [helper_tool] + cmd.append(','.join(fp_blacklist)) + cmd.append(','.join(key_blacklist)) + (stdout, _stderr) = util.subp(cmd) + util.write_file(cloud.paths.join(False, '/dev/console'), stdout) + except: + log.warn("Writing keys to /dev/console failed!") + raise diff --git a/cloudinit/config/landscape.py b/cloudinit/config/landscape.py new file mode 100644 index 00000000..29ce41b9 --- /dev/null +++ b/cloudinit/config/landscape.py @@ -0,0 +1,97 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from StringIO import StringIO + +try: + from configobj import ConfigObj +except ImportError: + ConfigObj = None + +from cloudinit import util + +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + +LSC_CLIENT_CFG_FILE = "/etc/landscape/client.conf" + +distros = ['ubuntu'] + +# defaults taken from stock client.conf in landscape-client 11.07.1.1-0ubuntu2 +LSC_BUILTIN_CFG = { + 'client': { + 'log_level': "info", + 'url': "https://landscape.canonical.com/message-system", + 'ping_url': "http://landscape.canonical.com/ping", + 'data_path': "/var/lib/landscape/client", + } +} + + +def handle(name, cfg, cloud, log, _args): + """ + Basically turn a top level 'landscape' entry with a 'client' dict + and render it to ConfigObj format under '[client]' section in + /etc/landscape/client.conf + """ + if not ConfigObj: + log.warn(("'ConfigObj' support not available," + " running transform %s disabled"), name) + return + + ls_cloudcfg = cfg.get("landscape", {}) + + if not isinstance(ls_cloudcfg, dict): + raise Exception(("'landscape' key existed in config," + " but not a dictionary type," + " is a %s instead"), util.obj_name(ls_cloudcfg)) + + lsc_client_fn = cloud.paths.join(True, LSC_CLIENT_CFG_FILE) + merged = merge_together([LSC_BUILTIN_CFG, lsc_client_fn, ls_cloudcfg]) + + lsc_dir = cloud.paths.join(False, os.path.dirname(lsc_client_fn)) + if not os.path.isdir(lsc_dir): + util.ensure_dir(lsc_dir) + + contents = StringIO() + merged.write(contents) + contents.flush() + + util.write_file(lsc_client_fn, contents.getvalue()) + log.debug("Wrote landscape config file to %s", lsc_client_fn) + + +def merge_together(objs): + """ + merge together ConfigObj objects or things that ConfigObj() will take in + later entries override earlier + """ + cfg = ConfigObj({}) + for obj in objs: + if not obj: + continue + if isinstance(obj, ConfigObj): + cfg.merge(obj) + else: + cfg.merge(ConfigObj(obj)) + return cfg diff --git a/cloudinit/config/locale.py b/cloudinit/config/locale.py new file mode 100644 index 00000000..7f273123 --- /dev/null +++ b/cloudinit/config/locale.py @@ -0,0 +1,58 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import templater +from cloudinit import util + + +def apply_locale(locale, cfgfile, cloud, log): + # TODO this command might not work on RH... + if os.path.exists('/usr/sbin/locale-gen'): + util.subp(['locale-gen', locale], capture=False) + if os.path.exists('/usr/sbin/update-locale'): + util.subp(['update-locale', locale], capture=False) + if not cfgfile: + return + template_fn = cloud.get_template_filename('default-locale') + if not template_fn: + log.warn("No template filename found to write to %s", cfgfile) + else: + templater.render_to_file(template_fn, cfgfile, {'locale': locale}) + + +def handle(name, cfg, cloud, log, args): + if len(args) != 0: + locale = args[0] + else: + locale = util.get_cfg_option_str(cfg, "locale", cloud.get_locale()) + + locale_cfgfile = util.get_cfg_option_str(cfg, "locale_configfile", + "/etc/default/locale") + + if not locale: + log.debug(("Skipping transform named %s, " + "no 'locale' configuration found"), name) + return + + log.debug("Setting locale to %s", locale) + + apply_locale(locale, locale_cfgfile, cloud, log) diff --git a/cloudinit/config/mounts.py b/cloudinit/config/mounts.py new file mode 100644 index 00000000..700fbc44 --- /dev/null +++ b/cloudinit/config/mounts.py @@ -0,0 +1,200 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from string import whitespace # pylint: disable=W0402 + +import re + +from cloudinit import util + +# shortname matches 'sda', 'sda1', 'xvda', 'hda', 'sdb', xvdb, vda, vdd1 +shortname_filter = r"^[x]{0,1}[shv]d[a-z][0-9]*$" +shortname = re.compile(shortname_filter) +ws = re.compile("[%s]+" % (whitespace)) + + +def is_mdname(name): + # return true if this is a metadata service name + if name in ["ami", "root", "swap"]: + return True + # names 'ephemeral0' or 'ephemeral1' + # 'ebs[0-9]' appears when '--block-device-mapping sdf=snap-d4d90bbc' + for enumname in ("ephemeral", "ebs"): + if name.startswith(enumname) and name.find(":") == -1: + return True + return False + + +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"] + defvals = cfg.get("mount_default_fields", defvals) + + # these are our default set of mounts + defmnts = [["ephemeral0", "/mnt", "auto", defvals[3], "0", "2"], + ["swap", "none", "swap", "sw", "0", "0"]] + + cfgmnt = [] + if "mounts" in cfg: + cfgmnt = cfg["mounts"] + + + for i in range(len(cfgmnt)): + # skip something that wasn't a list + if not isinstance(cfgmnt[i], list): + log.warn("Mount option %s not a list, got a %s instead", + (i + 1), util.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 + + # in case the user did not quote a field (likely fs-freq, fs_passno) + # but do not convert None to 'None' (LP: #898365) + for j in range(len(cfgmnt[i])): + if j is None: + continue + else: + cfgmnt[i][j] = str(cfgmnt[i][j]) + + for i in range(len(cfgmnt)): + # fill in values with defaults from defvals above + for j in range(len(defvals)): + if len(cfgmnt[i]) <= j: + cfgmnt[i].append(defvals[j]) + elif cfgmnt[i][j] is None: + cfgmnt[i][j] = defvals[j] + + # if the second entry in the list is 'None' this + # clears all previous entries of that same 'fs_spec' + # (fs_spec is the first field in /etc/fstab, ie, that device) + if cfgmnt[i][1] is None: + for j in range(i): + if cfgmnt[j][0] == cfgmnt[i][0]: + cfgmnt[j][1] = None + + # 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) + continue + if devname.startswith("/"): + defmnt[0] = devname + else: + defmnt[0] = "/dev/%s" % devname + + log.debug("Mapped default device %s to %s", startname, defmnt[0]) + + cfgmnt_has = False + for cfgm in cfgmnt: + if cfgm[0] == defmnt[0]: + cfgmnt_has = True + break + + if cfgmnt_has: + log.debug(("Not including %s, already" + " previously included"), startname) + continue + cfgmnt.append(defmnt) + + # now, each entry in the cfgmnt list has all fstab values + # if the second field is None (not the string, the value) we skip it + actlist = [] + for x in cfgmnt: + if x[1] is None: + log.debug("Skipping non-existent device named %s", x[0]) + else: + actlist.append(x) + + if len(actlist) == 0: + log.debug("No modifications to fstab needed.") + return + + comment = "comment=cloudconfig" + cc_lines = [] + needswap = False + dirs = [] + for line in actlist: + # write 'comment' in the fs_mntops, entry, claiming this + line[3] = "%s,%s" % (line[3], comment) + if line[2] == "swap": + needswap = True + if line[1].startswith("/"): + dirs.append(line[1]) + cc_lines.append('\t'.join(line)) + + fstab_lines = [] + fstab = util.load_file(cloud.paths.join(True, "/etc/fstab")) + for line in fstab.splitlines(): + try: + toks = ws.split(line) + if toks[3].find(comment) != -1: + continue + except: + pass + fstab_lines.append(line) + + fstab_lines.extend(cc_lines) + contents = "%s\n" % ('\n'.join(fstab_lines)) + util.write_file(cloud.paths.join(False, "/etc/fstab"), contents) + + if needswap: + try: + util.subp(("swapon", "-a")) + except: + util.logexc(log, "Activating swap via 'swapon -a' failed") + + for d in dirs: + try: + util.ensure_dir(cloud.paths.join(False, d)) + except: + util.logexc(log, "Failed to make '%s' config-mount", d) + + try: + util.subp(("mount", "-a")) + except: + util.logexc(log, "Activating mounts via 'mount -a' failed") diff --git a/cloudinit/config/phone_home.py b/cloudinit/config/phone_home.py new file mode 100644 index 00000000..a8752527 --- /dev/null +++ b/cloudinit/config/phone_home.py @@ -0,0 +1,111 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import templater +from cloudinit import url_helper as uhelp +from cloudinit import util + +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + +post_list_all = ['pub_key_dsa', 'pub_key_rsa', 'pub_key_ecdsa', + 'instance_id', 'hostname'] + + +# phone_home: +# url: http://my.foo.bar/$INSTANCE/ +# post: all +# tries: 10 +# +# phone_home: +# url: http://my.foo.bar/$INSTANCE_ID/ +# post: [ pub_key_dsa, pub_key_rsa, pub_key_ecdsa, instance_id +# +def handle(name, cfg, cloud, log, args): + if len(args) != 0: + ph_cfg = util.read_conf(args[0]) + else: + if not 'phone_home' in cfg: + return + ph_cfg = cfg['phone_home'] + + if 'url' not in ph_cfg: + log.warn(("Skipping transform named %s, " + "no 'url' found in 'phone_home' configuration"), name) + return + + url = ph_cfg['url'] + post_list = ph_cfg.get('post', 'all') + tries = ph_cfg.get('tries', 10) + try: + tries = int(tries) + except: + tries = 10 + util.logexc(log, ("Configuration entry 'tries'" + " is not an integer, using %s instead"), tries) + + if post_list == "all": + post_list = post_list_all + + all_keys = {} + all_keys['instance_id'] = cloud.get_instance_id() + all_keys['hostname'] = cloud.get_hostname() + + pubkeys = { + 'pub_key_dsa': '/etc/ssh/ssh_host_dsa_key.pub', + 'pub_key_rsa': '/etc/ssh/ssh_host_rsa_key.pub', + 'pub_key_ecdsa': '/etc/ssh/ssh_host_ecdsa_key.pub', + } + + for (n, path) in pubkeys.iteritems(): + try: + all_keys[n] = util.load_file(cloud.paths.join(True, path)) + except: + util.logexc(log, ("%s: failed to open, can not" + " phone home that data"), path) + + submit_keys = {} + for k in post_list: + if k in all_keys: + submit_keys[k] = all_keys[k] + else: + submit_keys[k] = None + log.warn(("Requested key %s from 'post'" + " configuration list not available"), k) + + # Get them read to be posted + real_submit_keys = {} + for (k, v) in submit_keys.iteritems(): + if v is None: + real_submit_keys[k] = 'N/A' + else: + real_submit_keys[k] = str(v) + + # Incase the url is parameterized + url_params = { + 'INSTANCE_ID': all_keys['instance_id'], + } + url = templater.render_string(url, url_params) + try: + uhelp.readurl(url, data=real_submit_keys, retries=tries, sec_between=3) + except: + util.logexc(log, ("Failed to post phone home data to" + " %s in %s tries"), url, tries) diff --git a/cloudinit/config/resizefs.py b/cloudinit/config/resizefs.py new file mode 100644 index 00000000..1690094a --- /dev/null +++ b/cloudinit/config/resizefs.py @@ -0,0 +1,140 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os +import stat +import time + +from cloudinit import util +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + +resize_fs_prefixes_cmds = [ + ('ext', 'resize2fs'), + ('xfs', 'xfs_growfs'), +] + + +def nodeify_path(devpth, where, log): + try: + st_dev = os.stat(where).st_dev + dev = os.makedev(os.major(st_dev), os.minor(st_dev)) + os.mknod(devpth, 0400 | stat.S_IFBLK, dev) + return st_dev + except: + if util.is_container(): + log.debug("Inside container, ignoring mknod failure in resizefs") + return + log.warn("Failed to make device node to resize %s at %s", + where, devpth) + raise + + +def get_fs_type(st_dev, path, log): + try: + dev_entries = util.find_devs_with(tag='TYPE', oformat='value', + no_cache=True, path=path) + if not dev_entries: + return None + return dev_entries[0].strip() + except util.ProcessExecutionError: + util.logexc(log, ("Failed to get filesystem type" + " of maj=%s, min=%s for path %s"), + os.major(st_dev), os.minor(st_dev), path) + raise + + +def handle(name, cfg, cloud, log, args): + if len(args) != 0: + resize_root = args[0] + else: + resize_root = util.get_cfg_option_str(cfg, "resize_rootfs", True) + + if not util.translate_bool(resize_root): + log.debug("Skipping transform named %s, resizing disabled", name) + return + + # TODO is the directory ok to be used?? + resize_root_d = util.get_cfg_option_str(cfg, "resize_rootfs_tmp", "/run") + resize_root_d = cloud.paths.join(False, resize_root_d) + util.ensure_dir(resize_root_d) + + # TODO: allow what is to be resized to be configurable?? + resize_what = cloud.paths.join(False, "/") + with util.SilentTemporaryFile(prefix="cloudinit.resizefs.", + dir=resize_root_d, delete=True) as tfh: + devpth = tfh.name + + # Delete the file so that mknod will work + # but don't change the file handle to know that its + # removed so that when a later call that recreates + # occurs this temporary file will still benefit from + # auto deletion + tfh.unlink_now() + + st_dev = nodeify_path(devpth, resize_what, log) + fs_type = get_fs_type(st_dev, devpth, log) + if not fs_type: + log.warn("Could not determine filesystem type of %s", resize_what) + return + + resizer = None + fstype_lc = fs_type.lower() + for (pfix, root_cmd) in resize_fs_prefixes_cmds: + if fstype_lc.startswith(pfix): + resizer = root_cmd + break + + if not resizer: + log.warn("Not resizing unknown filesystem type %s for %s", + fs_type, resize_what) + return + + log.debug("Resizing %s (%s) using %s", resize_what, fs_type, resizer) + resize_cmd = [resizer, devpth] + + if resize_root == "noblock": + # Fork to a child that will run + # the resize command + util.fork_cb(do_resize, resize_cmd, log) + # Don't delete the file now in the parent + tfh.delete = False + else: + do_resize(resize_cmd, log) + + action = 'Resized' + if resize_root == "noblock": + action = 'Resizing (via forking)' + log.debug("%s root filesystem (type=%s, maj=%i, min=%i, val=%s)", + action, fs_type, os.major(st_dev), os.minor(st_dev), resize_root) + + +def do_resize(resize_cmd, log): + start = time.time() + try: + util.subp(resize_cmd) + except util.ProcessExecutionError: + util.logexc(log, "Failed to resize filesystem (cmd=%s)", resize_cmd) + raise + tot_time = int(time.time() - start) + log.debug("Resizing took %s seconds", tot_time) + # TODO: Should we add a fsck check after this to make + # sure we didn't corrupt anything? diff --git a/cloudinit/config/rightscale_userdata.py b/cloudinit/config/rightscale_userdata.py new file mode 100644 index 00000000..8385e281 --- /dev/null +++ b/cloudinit/config/rightscale_userdata.py @@ -0,0 +1,102 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +## +## The purpose of this script is to allow cloud-init to consume +## rightscale style userdata. rightscale user data is key-value pairs +## in a url-query-string like format. +## +## for cloud-init support, there will be a key named +## 'CLOUD_INIT_REMOTE_HOOK'. +## +## This cloud-config module will +## - read the blob of data from raw user data, and parse it as key/value +## - for each key that is found, download the content to +## the local instance/scripts directory and set them executable. +## - the files in that directory will be run by the user-scripts module +## Therefore, this must run before that. +## +## + +import os + +from cloudinit import url_helper as uhelp +from cloudinit import util +from cloudinit.settings import PER_INSTANCE + +from urlparse import parse_qs + +frequency = PER_INSTANCE + +MY_NAME = "cc_rightscale_userdata" +MY_HOOKNAME = 'CLOUD_INIT_REMOTE_HOOK' + + +def handle(name, _cfg, cloud, log, _args): + try: + ud = cloud.get_userdata_raw() + except: + log.warn("Failed to get raw userdata in transform %s", name) + return + + try: + mdict = parse_qs(ud) + if not mdict or not MY_HOOKNAME in mdict: + log.debug(("Skipping transform %s, " + "did not find %s in parsed" + " raw userdata"), name, MY_HOOKNAME) + return + except: + util.logexc(log, ("Failed to parse query string %s" + " into a dictionary"), ud) + raise + + wrote_fns = [] + captured_excps = [] + + # These will eventually be then ran by the cc_scripts_user + # TODO: maybe this should just be a new user data handler?? + # Instead of a late transform that acts like a user data handler? + scripts_d = cloud.get_ipath_cur('scripts') + urls = mdict[MY_HOOKNAME] + for (i, url) in enumerate(urls): + fname = os.path.join(scripts_d, "rightscale-%02i" % (i)) + try: + resp = uhelp.readurl(url) + # Ensure its a valid http response (and something gotten) + if resp.ok() and resp.contents: + util.write_file(fname, str(resp), mode=0700) + wrote_fns.append(fname) + except Exception as e: + captured_excps.append(e) + util.logexc(log, "%s failed to read %s and write %s", + MY_NAME, url, fname) + + if wrote_fns: + log.debug("Wrote out rightscale userdata to %s files", len(wrote_fns)) + + if len(wrote_fns) != len(urls): + skipped = len(urls) - len(wrote_fns) + log.debug("%s urls were skipped or failed", skipped) + + if captured_excps: + log.warn("%s failed with exceptions, re-raising the last one", + len(captured_excps)) + raise captured_excps[-1] diff --git a/cloudinit/config/rsyslog.py b/cloudinit/config/rsyslog.py new file mode 100644 index 00000000..f2c1de1e --- /dev/null +++ b/cloudinit/config/rsyslog.py @@ -0,0 +1,102 @@ +# vi: ts=4 expandtab syntax=python +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +DEF_FILENAME = "20-cloud-config.conf" +DEF_DIR = "/etc/rsyslog.d" + + +def handle(name, cfg, cloud, log, _args): + # rsyslog: + # - "*.* @@192.158.1.1" + # - content: "*.* @@192.0.2.1:10514" + # - filename: 01-examplecom.conf + # content: | + # *.* @@syslogd.example.com + + # process 'rsyslog' + if not 'rsyslog' in cfg: + log.debug(("Skipping transform named %s," + " no 'rsyslog' key in configuration"), name) + return + + def_dir = cfg.get('rsyslog_dir', DEF_DIR) + def_fname = cfg.get('rsyslog_filename', DEF_FILENAME) + + files = [] + for i, ent in enumerate(cfg['rsyslog']): + if isinstance(ent, dict): + if not "content" in ent: + log.warn("No 'content' entry in config entry %s", i + 1) + continue + content = ent['content'] + filename = ent.get("filename", def_fname) + else: + content = ent + filename = def_fname + + filename = filename.strip() + if not filename: + log.warn("Entry %s has an empty filename", i + 1) + continue + + if not filename.startswith("/"): + filename = os.path.join(def_dir, filename) + + # Truncate filename first time you see it + omode = "ab" + if filename not in files: + omode = "wb" + files.append(filename) + + try: + contents = "%s\n" % (content) + util.write_file(cloud.paths.join(False, filename), + contents, omode=omode) + except Exception: + util.logexc(log, "Failed to write to %s", filename) + + # Attempt to restart syslogd + restarted = False + try: + # If this config module is running at cloud-init time + # (before rsyslog is running) we don't actually have to + # restart syslog. + # + # Upstart actually does what we want here, in that it doesn't + # start a service that wasn't running already on 'restart' + # it will also return failure on the attempt, so 'restarted' + # won't get set. + log.debug("Restarting rsyslog") + util.subp(['service', 'rsyslog', 'restart']) + restarted = True + except Exception: + util.logexc(log, "Failed restarting rsyslog") + + if restarted: + # This only needs to run if we *actually* restarted + # syslog above. + cloud.cycle_logging() + # This should now use rsyslog if + # the logging was setup to use it... + log.debug("%s configured %s files", name, files) diff --git a/cloudinit/config/runcmd.py b/cloudinit/config/runcmd.py new file mode 100644 index 00000000..f121484b --- /dev/null +++ b/cloudinit/config/runcmd.py @@ -0,0 +1,38 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + + +def handle(name, cfg, cloud, log, _args): + if "runcmd" not in cfg: + log.debug(("Skipping transform named %s," + " no 'runcmd' key in configuration"), name) + return + + out_fn = os.path.join(cloud.get_ipath('scripts'), "runcmd") + cmd = cfg["runcmd"] + try: + content = util.shellify(cmd) + util.write_file(cloud.paths.join(False, out_fn), content, 0700) + except: + util.logexc(log, "Failed to shellify %s into file %s", cmd, out_fn) diff --git a/cloudinit/config/salt_minion.py b/cloudinit/config/salt_minion.py new file mode 100644 index 00000000..16f5286d --- /dev/null +++ b/cloudinit/config/salt_minion.py @@ -0,0 +1,60 @@ +# vi: ts=4 expandtab +# +# Author: Jeff Bauer +# +# 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 . + +import os + +from cloudinit import util + +# Note: see http://saltstack.org/topics/installation/ + + +def handle(name, cfg, cloud, log, _args): + # If there isn't a salt key in the configuration don't do anything + if 'salt_minion' not in cfg: + log.debug(("Skipping transform named %s," + " no 'salt_minion' key in configuration"), name) + return + + salt_cfg = cfg['salt_minion'] + + # Start by installing the salt package ... + cloud.distro.install_packages(["salt"]) + + # Ensure we can configure files at the right dir + config_dir = salt_cfg.get("config_dir", '/etc/salt') + config_dir = cloud.paths.join(False, config_dir) + util.ensure_dir(config_dir) + + # ... and then update the salt configuration + if 'conf' in salt_cfg: + # Add all sections from the conf object to /etc/salt/minion + minion_config = os.path.join(config_dir, 'minion') + minion_data = util.yaml_dumps(salt_cfg.get('conf')) + util.write_file(minion_config, minion_data) + + # ... copy the key pair if specified + if 'public_key' in salt_cfg and 'private_key' in salt_cfg: + pki_dir = salt_cfg.get('pki_dir', '/etc/salt/pki') + pki_dir = cloud.paths.join(pki_dir) + with util.umask(077): + util.ensure_dir(pki_dir) + pub_name = os.path.join(pki_dir, 'minion.pub') + pem_name = os.path.join(pki_dir, 'minion.pem') + util.write_file(pub_name, salt_cfg['public_key']) + util.write_file(pem_name, salt_cfg['private_key']) + + # Start salt-minion + util.subp(['service', 'salt-minion', 'start'], capture=False) diff --git a/cloudinit/config/scripts_per_boot.py b/cloudinit/config/scripts_per_boot.py new file mode 100644 index 00000000..364e1d02 --- /dev/null +++ b/cloudinit/config/scripts_per_boot.py @@ -0,0 +1,41 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + +script_subdir = 'per-boot' + + +def handle(name, _cfg, cloud, log, _args): + # Comes from the following: + # https://forums.aws.amazon.com/thread.jspa?threadID=96918 + runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) + try: + util.runparts(runparts_path) + except: + log.warn("Failed to run transform %s (%s in %s)", + name, script_subdir, runparts_path) + raise diff --git a/cloudinit/config/scripts_per_instance.py b/cloudinit/config/scripts_per_instance.py new file mode 100644 index 00000000..d75ab47d --- /dev/null +++ b/cloudinit/config/scripts_per_instance.py @@ -0,0 +1,41 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + +script_subdir = 'per-instance' + + +def handle(name, _cfg, cloud, log, _args): + # Comes from the following: + # https://forums.aws.amazon.com/thread.jspa?threadID=96918 + runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) + try: + util.runparts(runparts_path) + except: + log.warn("Failed to run transform %s (%s in %s)", + name, script_subdir, runparts_path) + raise diff --git a/cloudinit/config/scripts_per_once.py b/cloudinit/config/scripts_per_once.py new file mode 100644 index 00000000..80f8c325 --- /dev/null +++ b/cloudinit/config/scripts_per_once.py @@ -0,0 +1,41 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +from cloudinit.settings import PER_ONCE + +frequency = PER_ONCE + +script_subdir = 'per-once' + + +def handle(name, _cfg, cloud, log, _args): + # Comes from the following: + # https://forums.aws.amazon.com/thread.jspa?threadID=96918 + runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) + try: + util.runparts(runparts_path) + except: + log.warn("Failed to run transform %s (%s in %s)", + name, script_subdir, runparts_path) + raise diff --git a/cloudinit/config/scripts_user.py b/cloudinit/config/scripts_user.py new file mode 100644 index 00000000..f4fe3a2a --- /dev/null +++ b/cloudinit/config/scripts_user.py @@ -0,0 +1,42 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + +script_subdir = 'scripts' + + +def handle(name, _cfg, cloud, log, _args): + # This is written to by the user data handlers + # Ie, any custom shell scripts that come down + # go here... + runparts_path = os.path.join(cloud.get_ipath_cur(), script_subdir) + try: + util.runparts(runparts_path) + except: + log.warn("Failed to run transform %s (%s in %s)", + name, script_subdir, runparts_path) + raise diff --git a/cloudinit/config/set_hostname.py b/cloudinit/config/set_hostname.py new file mode 100644 index 00000000..3ac8a8fa --- /dev/null +++ b/cloudinit/config/set_hostname.py @@ -0,0 +1,35 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util + + +def handle(name, cfg, cloud, log, _args): + if util.get_cfg_option_bool(cfg, "preserve_hostname", False): + log.debug(("Configuration option 'preserve_hostname' is set," + " not setting the hostname in transform %s"), name) + return + + (hostname, _fqdn) = util.get_hostname_fqdn(cfg, cloud) + try: + log.debug("Setting hostname to %s", hostname) + cloud.distro.set_hostname(hostname) + except Exception: + util.logexc(log, "Failed to set hostname to %s", hostname) diff --git a/cloudinit/config/set_passwords.py b/cloudinit/config/set_passwords.py new file mode 100644 index 00000000..e7049f22 --- /dev/null +++ b/cloudinit/config/set_passwords.py @@ -0,0 +1,151 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import sys + +from cloudinit import util + +from string import letters, digits # pylint: disable=W0402 + +# We are removing certain 'painful' letters/numbers +pw_set = (letters.translate(None, 'loLOI') + + digits.translate(None, '01')) + + +def handle(_name, cfg, cloud, log, args): + if len(args) != 0: + # if run from command line, and give args, wipe the chpasswd['list'] + password = args[0] + if 'chpasswd' in cfg and 'list' in cfg['chpasswd']: + del cfg['chpasswd']['list'] + else: + password = util.get_cfg_option_str(cfg, "password", None) + + expire = True + pw_auth = "no" + change_pwauth = False + plist = None + + if 'chpasswd' in cfg: + chfg = cfg['chpasswd'] + plist = util.get_cfg_option_str(chfg, 'list', plist) + expire = util.get_cfg_option_bool(chfg, 'expire', expire) + + if not plist and password: + user = util.get_cfg_option_str(cfg, "user", "ubuntu") + plist = "%s:%s" % (user, password) + + errors = [] + if plist: + plist_in = [] + randlist = [] + users = [] + for line in plist.splitlines(): + u, p = line.split(':', 1) + if p == "R" or p == "RANDOM": + p = rand_user_password() + randlist.append("%s:%s" % (u, p)) + plist_in.append("%s:%s" % (u, p)) + users.append(u) + + ch_in = '\n'.join(plist_in) + try: + log.debug("Changing password for %s:", users) + util.subp(['chpasswd'], ch_in) + except Exception as e: + errors.append(e) + util.logexc(log, + "Failed to set passwords with chpasswd for %s", users) + + if len(randlist): + blurb = ("Set the following 'random' passwords\n", + '\n'.join(randlist)) + sys.stderr.write("%s\n%s\n" % blurb) + + if expire: + expired_users = [] + for u in users: + try: + util.subp(['passwd', '--expire', u]) + expired_users.append(u) + except Exception as e: + errors.append(e) + util.logexc(log, "Failed to set 'expire' for %s", u) + if expired_users: + log.debug("Expired passwords for: %s users", expired_users) + + change_pwauth = False + pw_auth = None + if 'ssh_pwauth' in cfg: + change_pwauth = True + if util.is_true_str(cfg['ssh_pwauth']): + pw_auth = 'yes' + if util.is_false_str(cfg['ssh_pwauth']): + pw_auth = 'no' + + if change_pwauth: + new_lines = [] + replaced_auth = False + replacement = "PasswordAuthentication %s" % (pw_auth) + + # See http://linux.die.net/man/5/sshd_config + old_lines = util.load_file('/etc/ssh/sshd_config').splitlines() + for i, line in enumerate(old_lines): + if not line.strip() or line.startswith("#"): + new_lines.append(line) + continue + splitup = line.split(None, 1) + if len(splitup) <= 1: + new_lines.append(line) + continue + (cmd, args) = splitup + # Keywords are case-insensitive and arguments are case-sensitive + cmd = cmd.lower().strip() + if cmd == 'passwordauthentication': + log.debug("Replacing auth line %s with %s", i + 1, replacement) + replaced_auth = True + new_lines.append(replacement) + else: + new_lines.append(line) + + if not replaced_auth: + log.debug("Adding new auth line %s", replacement) + replaced_auth = True + new_lines.append(replacement) + + util.write_file(cloud.paths.join(False, '/etc/ssh/sshd_config'), + "\n".join(new_lines)) + + try: + cmd = ['service'] + cmd.append(cloud.distro.get_option('ssh_svcname', 'ssh')) + cmd.append('restart') + util.subp(cmd) + log.debug("Restarted the ssh daemon") + except: + util.logexc(log, "Restarting of the ssh daemon failed") + + if len(errors): + log.debug("%s errors occured, re-raising the last one", len(errors)) + raise errors[-1] + + +def rand_user_password(pwlen=9): + return util.rand_str(pwlen, select_from=pw_set) diff --git a/cloudinit/config/ssh.py b/cloudinit/config/ssh.py new file mode 100644 index 00000000..e5e99560 --- /dev/null +++ b/cloudinit/config/ssh.py @@ -0,0 +1,131 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os +import glob + +from cloudinit import util +from cloudinit import ssh_util + +DISABLE_ROOT_OPTS = ( "no-port-forwarding,no-agent-forwarding," +"no-X11-forwarding,command=\"echo \'Please login as the user \\\"$USER\\\" " +"rather than the user \\\"root\\\".\';echo;sleep 10\"") + +key2file = { + "rsa_private": ("/etc/ssh/ssh_host_rsa_key", 0600), + "rsa_public": ("/etc/ssh/ssh_host_rsa_key.pub", 0644), + "dsa_private": ("/etc/ssh/ssh_host_dsa_key", 0600), + "dsa_public": ("/etc/ssh/ssh_host_dsa_key.pub", 0644), + "ecdsa_private": ("/etc/ssh/ssh_host_ecdsa_key", 0600), + "ecdsa_public": ("/etc/ssh/ssh_host_ecdsa_key.pub", 0644), +} + +priv2pub = { + 'rsa_private': 'rsa_public', + 'dsa_private': 'dsa_public', + 'ecdsa_private': 'ecdsa_public', +} + +key_gen_tpl = 'o=$(ssh-keygen -yf "%s") && echo "$o" root@localhost > "%s"' + +generate_keys = ['rsa', 'dsa', 'ecdsa'] + + +def handle(_name, cfg, cloud, log, _args): + + # remove the static keys from the pristine image + if cfg.get("ssh_deletekeys", True): + key_pth = cloud.paths.join(False, "/etc/ssh/", "ssh_host_*key*") + for f in glob.glob(key_pth): + try: + util.del_file(f) + except: + util.logexc(log, "Failed deleting key file %s", f) + + if "ssh_keys" in cfg: + # if there are keys in cloud-config, use them + for (key, val) in cfg["ssh_keys"].iteritems(): + if key in key2file: + tgt_fn = key2file[key][0] + tgt_perms = key2file[key][1] + util.write_file(cloud.paths.join(False, tgt_fn), + val, tgt_perms) + + for (priv, pub) in priv2pub.iteritems(): + if pub in cfg['ssh_keys'] or not priv in cfg['ssh_keys']: + continue + pair = (key2file[priv][0], key2file[pub][0]) + cmd = ['sh', '-xc', key_gen_tpl % pair] + try: + # TODO: Is this guard needed? + with util.SeLinuxGuard("/etc/ssh", recursive=True): + util.subp(cmd, capture=False) + log.debug("Generated a key for %s from %s", pair[0], pair[1]) + except: + util.logexc(log, ("Failed generated a key" + " for %s from %s"), pair[0], pair[1]) + else: + # if not, generate them + genkeys = util.get_cfg_option_list(cfg, + 'ssh_genkeytypes', + generate_keys) + for keytype in genkeys: + keyfile = '/etc/ssh/ssh_host_%s_key' % (keytype) + keyfile = cloud.paths.join(False, keyfile) + util.ensure_dir(os.path.dirname(keyfile)) + if not os.path.exists(keyfile): + cmd = ['ssh-keygen', '-t', keytype, '-N', '', '-f', keyfile] + try: + # TODO: Is this guard needed? + with util.SeLinuxGuard("/etc/ssh", recursive=True): + util.subp(cmd, capture=False) + except: + util.logexc(log, ("Failed generating key type" + " %s to file %s"), keytype, keyfile) + + try: + user = util.get_cfg_option_str(cfg, 'user') + disable_root = util.get_cfg_option_bool(cfg, "disable_root", True) + disable_root_opts = util.get_cfg_option_str(cfg, "disable_root_opts", + DISABLE_ROOT_OPTS) + + keys = cloud.get_public_ssh_keys() or [] + if "ssh_authorized_keys" in cfg: + cfgkeys = cfg["ssh_authorized_keys"] + keys.extend(cfgkeys) + + apply_credentials(keys, user, cloud.paths, + disable_root, disable_root_opts) + except: + util.logexc(log, "Applying ssh credentials failed!") + + +def apply_credentials(keys, user, paths, disable_root, disable_root_opts): + + keys = set(keys) + if user: + ssh_util.setup_user_keys(keys, user, '', paths) + + if disable_root and user: + key_prefix = disable_root_opts.replace('$USER', user) + else: + key_prefix = '' + + ssh_util.setup_user_keys(keys, 'root', key_prefix, paths) diff --git a/cloudinit/config/ssh_import_id.py b/cloudinit/config/ssh_import_id.py new file mode 100644 index 00000000..d57e4665 --- /dev/null +++ b/cloudinit/config/ssh_import_id.py @@ -0,0 +1,53 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util + +# The ssh-import-id only seems to exist on ubuntu (for now) +# https://launchpad.net/ssh-import-id +distros = ['ubuntu'] + + +def handle(name, cfg, _cloud, log, args): + if len(args) != 0: + user = args[0] + ids = [] + if len(args) > 1: + ids = args[1:] + else: + user = util.get_cfg_option_str(cfg, "user", "ubuntu") + ids = util.get_cfg_option_list(cfg, "ssh_import_id", []) + + if len(ids) == 0: + log.debug("Skipping transform named %s, no ids found to import", name) + return + + if not user: + log.debug("Skipping transform named %s, no user found to import", name) + return + + cmd = ["sudo", "-Hu", user, "ssh-import-id"] + ids + log.debug("Importing ssh ids for user %s.", user) + + try: + util.subp(cmd, capture=False) + except util.ProcessExecutionError as e: + util.logexc(log, "Failed to run command to import %s ssh ids", user) + raise e diff --git a/cloudinit/config/timezone.py b/cloudinit/config/timezone.py new file mode 100644 index 00000000..747c436c --- /dev/null +++ b/cloudinit/config/timezone.py @@ -0,0 +1,39 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util + +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + + +def handle(name, cfg, cloud, log, args): + if len(args) != 0: + timezone = args[0] + else: + timezone = util.get_cfg_option_str(cfg, "timezone", False) + + if not timezone: + log.debug("Skipping transform named %s, no 'timezone' specified", name) + return + + # Let the distro handle settings its timezone + cloud.distro.set_timezone(timezone) diff --git a/cloudinit/config/update_etc_hosts.py b/cloudinit/config/update_etc_hosts.py new file mode 100644 index 00000000..75615db1 --- /dev/null +++ b/cloudinit/config/update_etc_hosts.py @@ -0,0 +1,60 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util +from cloudinit import templater + +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + + +def handle(name, cfg, cloud, log, _args): + manage_hosts = util.get_cfg_option_str(cfg, "manage_etc_hosts", False) + if util.translate_bool(manage_hosts, addons=['template']): + (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) + if not hostname: + log.warn(("Option 'manage_etc_hosts' was set," + " but no hostname was found")) + return + + # Render from a template file + distro_n = cloud.distro.name + tpl_fn_name = cloud.get_template_filename("hosts.%s" % (distro_n)) + if not tpl_fn_name: + raise Exception(("No hosts template could be" + " found for distro %s") % (distro_n)) + + out_fn = cloud.paths.join(False, '/etc/hosts') + templater.render_to_file(tpl_fn_name, out_fn, + {'hostname': hostname, 'fqdn': fqdn}) + + elif manage_hosts == "localhost": + (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) + if not hostname: + log.warn(("Option 'manage_etc_hosts' was set," + " but no hostname was found")) + return + + log.debug("Managing localhost in /etc/hosts") + cloud.distro.update_etc_hosts(hostname, fqdn) + else: + log.debug(("Configuration option 'manage_etc_hosts' is not set," + " not managing /etc/hosts in transform %s"), name) diff --git a/cloudinit/config/update_hostname.py b/cloudinit/config/update_hostname.py new file mode 100644 index 00000000..58444fab --- /dev/null +++ b/cloudinit/config/update_hostname.py @@ -0,0 +1,41 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + + +def handle(name, cfg, cloud, log, _args): + if util.get_cfg_option_bool(cfg, "preserve_hostname", False): + log.debug(("Configuration option 'preserve_hostname' is set," + " not updating the hostname in transform %s"), name) + return + + (hostname, _fqdn) = util.get_hostname_fqdn(cfg, cloud) + try: + prev_fn = os.path.join(cloud.get_cpath('data'), "previous-hostname") + cloud.distro.update_hostname(hostname, prev_fn) + except Exception: + util.logexc(log, "Failed to set the hostname to %s", hostname) + raise -- cgit v1.2.3 From f7bfe4aaae9850ab179a39436d4b6a9c9da707a5 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 20 Jun 2012 16:39:09 -0700 Subject: Renamed back to 'cc_*' with the reasoning being that 'cc_' provides some protection against module name collisions when importing. --- cloudinit/config/__init__.py | 19 ++- cloudinit/config/apt_pipelining.py | 57 ------- cloudinit/config/apt_update_upgrade.py | 243 ---------------------------- cloudinit/config/bootcmd.py | 56 ------- cloudinit/config/byobu.py | 71 -------- cloudinit/config/ca_certs.py | 99 ------------ cloudinit/config/cc_apt_pipelining.py | 57 +++++++ cloudinit/config/cc_apt_update_upgrade.py | 243 ++++++++++++++++++++++++++++ cloudinit/config/cc_bootcmd.py | 56 +++++++ cloudinit/config/cc_byobu.py | 71 ++++++++ cloudinit/config/cc_ca_certs.py | 99 ++++++++++++ cloudinit/config/cc_chef.py | 128 +++++++++++++++ cloudinit/config/cc_disable_ec2_metadata.py | 36 +++++ cloudinit/config/cc_final_message.py | 71 ++++++++ cloudinit/config/cc_foo.py | 52 ++++++ cloudinit/config/cc_grub_dpkg.py | 67 ++++++++ cloudinit/config/cc_keys_to_console.py | 52 ++++++ cloudinit/config/cc_landscape.py | 97 +++++++++++ cloudinit/config/cc_locale.py | 58 +++++++ cloudinit/config/cc_mounts.py | 200 +++++++++++++++++++++++ cloudinit/config/cc_phone_home.py | 111 +++++++++++++ cloudinit/config/cc_resizefs.py | 140 ++++++++++++++++ cloudinit/config/cc_rightscale_userdata.py | 102 ++++++++++++ cloudinit/config/cc_rsyslog.py | 102 ++++++++++++ cloudinit/config/cc_runcmd.py | 38 +++++ cloudinit/config/cc_salt_minion.py | 60 +++++++ cloudinit/config/cc_scripts_per_boot.py | 41 +++++ cloudinit/config/cc_scripts_per_instance.py | 41 +++++ cloudinit/config/cc_scripts_per_once.py | 41 +++++ cloudinit/config/cc_scripts_user.py | 42 +++++ cloudinit/config/cc_set_hostname.py | 35 ++++ cloudinit/config/cc_set_passwords.py | 151 +++++++++++++++++ cloudinit/config/cc_ssh.py | 131 +++++++++++++++ cloudinit/config/cc_ssh_import_id.py | 53 ++++++ cloudinit/config/cc_timezone.py | 39 +++++ cloudinit/config/cc_update_etc_hosts.py | 60 +++++++ cloudinit/config/cc_update_hostname.py | 41 +++++ cloudinit/config/chef.py | 128 --------------- cloudinit/config/disable_ec2_metadata.py | 36 ----- cloudinit/config/final_message.py | 71 -------- cloudinit/config/foo.py | 52 ------ cloudinit/config/grub_dpkg.py | 67 -------- cloudinit/config/keys_to_console.py | 52 ------ cloudinit/config/landscape.py | 97 ----------- cloudinit/config/locale.py | 58 ------- cloudinit/config/mounts.py | 200 ----------------------- cloudinit/config/phone_home.py | 111 ------------- cloudinit/config/resizefs.py | 140 ---------------- cloudinit/config/rightscale_userdata.py | 102 ------------ cloudinit/config/rsyslog.py | 102 ------------ cloudinit/config/runcmd.py | 38 ----- cloudinit/config/salt_minion.py | 60 ------- cloudinit/config/scripts_per_boot.py | 41 ----- cloudinit/config/scripts_per_instance.py | 41 ----- cloudinit/config/scripts_per_once.py | 41 ----- cloudinit/config/scripts_user.py | 42 ----- cloudinit/config/set_hostname.py | 35 ---- cloudinit/config/set_passwords.py | 151 ----------------- cloudinit/config/ssh.py | 131 --------------- cloudinit/config/ssh_import_id.py | 53 ------ cloudinit/config/timezone.py | 39 ----- cloudinit/config/update_etc_hosts.py | 60 ------- cloudinit/config/update_hostname.py | 41 ----- 63 files changed, 2526 insertions(+), 2523 deletions(-) delete mode 100644 cloudinit/config/apt_pipelining.py delete mode 100644 cloudinit/config/apt_update_upgrade.py delete mode 100644 cloudinit/config/bootcmd.py delete mode 100644 cloudinit/config/byobu.py delete mode 100644 cloudinit/config/ca_certs.py create mode 100644 cloudinit/config/cc_apt_pipelining.py create mode 100644 cloudinit/config/cc_apt_update_upgrade.py create mode 100644 cloudinit/config/cc_bootcmd.py create mode 100644 cloudinit/config/cc_byobu.py create mode 100644 cloudinit/config/cc_ca_certs.py create mode 100644 cloudinit/config/cc_chef.py create mode 100644 cloudinit/config/cc_disable_ec2_metadata.py create mode 100644 cloudinit/config/cc_final_message.py create mode 100644 cloudinit/config/cc_foo.py create mode 100644 cloudinit/config/cc_grub_dpkg.py create mode 100644 cloudinit/config/cc_keys_to_console.py create mode 100644 cloudinit/config/cc_landscape.py create mode 100644 cloudinit/config/cc_locale.py create mode 100644 cloudinit/config/cc_mounts.py create mode 100644 cloudinit/config/cc_phone_home.py create mode 100644 cloudinit/config/cc_resizefs.py create mode 100644 cloudinit/config/cc_rightscale_userdata.py create mode 100644 cloudinit/config/cc_rsyslog.py create mode 100644 cloudinit/config/cc_runcmd.py create mode 100644 cloudinit/config/cc_salt_minion.py create mode 100644 cloudinit/config/cc_scripts_per_boot.py create mode 100644 cloudinit/config/cc_scripts_per_instance.py create mode 100644 cloudinit/config/cc_scripts_per_once.py create mode 100644 cloudinit/config/cc_scripts_user.py create mode 100644 cloudinit/config/cc_set_hostname.py create mode 100644 cloudinit/config/cc_set_passwords.py create mode 100644 cloudinit/config/cc_ssh.py create mode 100644 cloudinit/config/cc_ssh_import_id.py create mode 100644 cloudinit/config/cc_timezone.py create mode 100644 cloudinit/config/cc_update_etc_hosts.py create mode 100644 cloudinit/config/cc_update_hostname.py delete mode 100644 cloudinit/config/chef.py delete mode 100644 cloudinit/config/disable_ec2_metadata.py delete mode 100644 cloudinit/config/final_message.py delete mode 100644 cloudinit/config/foo.py delete mode 100644 cloudinit/config/grub_dpkg.py delete mode 100644 cloudinit/config/keys_to_console.py delete mode 100644 cloudinit/config/landscape.py delete mode 100644 cloudinit/config/locale.py delete mode 100644 cloudinit/config/mounts.py delete mode 100644 cloudinit/config/phone_home.py delete mode 100644 cloudinit/config/resizefs.py delete mode 100644 cloudinit/config/rightscale_userdata.py delete mode 100644 cloudinit/config/rsyslog.py delete mode 100644 cloudinit/config/runcmd.py delete mode 100644 cloudinit/config/salt_minion.py delete mode 100644 cloudinit/config/scripts_per_boot.py delete mode 100644 cloudinit/config/scripts_per_instance.py delete mode 100644 cloudinit/config/scripts_per_once.py delete mode 100644 cloudinit/config/scripts_user.py delete mode 100644 cloudinit/config/set_hostname.py delete mode 100644 cloudinit/config/set_passwords.py delete mode 100644 cloudinit/config/ssh.py delete mode 100644 cloudinit/config/ssh_import_id.py delete mode 100644 cloudinit/config/timezone.py delete mode 100644 cloudinit/config/update_etc_hosts.py delete mode 100644 cloudinit/config/update_hostname.py (limited to 'cloudinit/config') diff --git a/cloudinit/config/__init__.py b/cloudinit/config/__init__.py index 5cd08575..74e2f275 100644 --- a/cloudinit/config/__init__.py +++ b/cloudinit/config/__init__.py @@ -25,29 +25,32 @@ from cloudinit import log as logging LOG = logging.getLogger(__name__) -# TODO remove this from being a prefix?? -TRANSFORM_PREFIX = '' # "cc_" +# This prefix is used to make it less +# of a change that when importing +# we will not find something else with the same +# name in the lookup path... +MOD_PREFIX = "cc_" -def form_transform_name(name, mod=__name__): +def form_module_name(name): canon_name = name.replace("-", "_") if canon_name.lower().endswith(".py"): canon_name = canon_name[0:(len(canon_name) - 3)] canon_name = canon_name.strip() if not canon_name: return None - if not canon_name.startswith(TRANSFORM_PREFIX): - canon_name = '%s%s' % (TRANSFORM_PREFIX, canon_name) - return ".".join([str(mod), str(canon_name)]) + if not canon_name.startswith(MOD_PREFIX): + canon_name = '%s%s' % (MOD_PREFIX, canon_name) + return canon_name -def fixup_transform(mod, def_freq=PER_INSTANCE): +def fixup_module(mod, def_freq=PER_INSTANCE): if not hasattr(mod, 'frequency'): setattr(mod, 'frequency', def_freq) else: freq = mod.frequency if freq and freq not in FREQUENCIES: - LOG.warn("Transform %s has an unknown frequency %s", mod, freq) + LOG.warn("Module %s has an unknown frequency %s", mod, freq) if not hasattr(mod, 'handle'): def empty_handle(_name, _cfg, _cloud, _log, _args): pass diff --git a/cloudinit/config/apt_pipelining.py b/cloudinit/config/apt_pipelining.py deleted file mode 100644 index f460becb..00000000 --- a/cloudinit/config/apt_pipelining.py +++ /dev/null @@ -1,57 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# -# Author: Ben Howard -# -# 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 . - -from cloudinit import util -from cloudinit.settings import PER_INSTANCE - -frequency = PER_INSTANCE - -distros = ['ubuntu', 'debian'] - -DEFAULT_FILE = "/etc/apt/apt.conf.d/90cloud-init-pipelining" - -# Acquire::http::Pipeline-Depth can be a value -# from 0 to 5 indicating how many outstanding requests APT should send. -# A value of zero MUST be specified if the remote host does not properly linger -# on TCP connections - otherwise data corruption will occur. - - -def handle(_name, cfg, cloud, log, _args): - - apt_pipe_value = util.get_cfg_option_str(cfg, "apt_pipelining", False) - apt_pipe_value_s = str(apt_pipe_value).lower().strip() - - if apt_pipe_value_s == "false": - write_apt_snippet(cloud, "0", log, DEFAULT_FILE) - elif apt_pipe_value_s in ("none", "unchanged", "os"): - return - elif apt_pipe_value_s in [str(b) for b in xrange(0, 6)]: - write_apt_snippet(cloud, apt_pipe_value_s, log, DEFAULT_FILE) - else: - log.warn("Invalid option for apt_pipeling: %s", apt_pipe_value) - - -def write_apt_snippet(cloud, setting, log, f_name): - """ Writes f_name with apt pipeline depth 'setting' """ - - file_contents = ("//Written by cloud-init per 'apt_pipelining'\n" - 'Acquire::http::Pipeline-Depth "%s";\n') % (setting) - - util.write_file(cloud.paths.join(False, f_name), file_contents) - - log.debug("Wrote %s with apt pipeline depth setting %s", f_name, setting) diff --git a/cloudinit/config/apt_update_upgrade.py b/cloudinit/config/apt_update_upgrade.py deleted file mode 100644 index f5b4b58f..00000000 --- a/cloudinit/config/apt_update_upgrade.py +++ /dev/null @@ -1,243 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import glob -import os - -from cloudinit import templater -from cloudinit import util - -distros = ['ubuntu', 'debian'] - -PROXY_TPL = "Acquire::HTTP::Proxy \"%s\";\n" - - -def handle(_name, cfg, cloud, log, _args): - update = util.get_cfg_option_bool(cfg, 'apt_update', False) - upgrade = util.get_cfg_option_bool(cfg, 'apt_upgrade', False) - - release = get_release() - - mirror = find_apt_mirror(cloud, cfg) - - log.debug("Selected mirror at: %s" % mirror) - - if not util.get_cfg_option_bool(cfg, - 'apt_preserve_sources_list', False): - generate_sources_list(release, mirror, cloud, log) - old_mir = util.get_cfg_option_str(cfg, 'apt_old_mirror', - "archive.ubuntu.com/ubuntu") - rename_apt_lists(old_mir, mirror) - - # Set up any apt proxy - proxy = cfg.get("apt_proxy", None) - proxy_filename = "/etc/apt/apt.conf.d/95cloud-init-proxy" - if proxy: - try: - # See man 'apt.conf' - contents = PROXY_TPL % (proxy) - util.write_file(cloud.paths.join(False, proxy_filename), - contents) - except Exception as e: - util.logexc(log, "Failed to write proxy to %s", proxy_filename) - elif os.path.isfile(proxy_filename): - util.del_file(proxy_filename) - - # Process 'apt_sources' - if 'apt_sources' in cfg: - errors = add_sources(cloud, cfg['apt_sources'], - {'MIRROR': mirror, 'RELEASE': release}) - for e in errors: - log.warn("Source Error: %s", ':'.join(e)) - - dconf_sel = util.get_cfg_option_str(cfg, 'debconf_selections', False) - if dconf_sel: - log.debug("setting debconf selections per cloud config") - try: - util.subp(('debconf-set-selections', '-'), dconf_sel) - except: - util.logexc(log, "Failed to run debconf-set-selections") - - pkglist = util.get_cfg_option_list(cfg, 'packages', []) - - errors = [] - if update or len(pkglist) or upgrade: - try: - cloud.distro.update_package_sources() - except Exception as e: - util.logexc(log, "Package update failed") - errors.append(e) - - if upgrade: - try: - cloud.distro.package_command("upgrade") - except Exception as e: - util.logexc(log, "Package upgrade failed") - errors.append(e) - - if len(pkglist): - try: - cloud.distro.install_packages(pkglist) - except Exception as e: - util.logexc(log, "Failed to install packages: %s ", pkglist) - errors.append(e) - - if len(errors): - log.warn("%s failed with exceptions, re-raising the last one", - len(errors)) - raise errors[-1] - - -def mirror2lists_fileprefix(mirror): - string = mirror - # take of http:// or ftp:// - if string.endswith("/"): - string = string[0:-1] - pos = string.find("://") - if pos >= 0: - string = string[pos + 3:] - string = string.replace("/", "_") - return string - - -def rename_apt_lists(omirror, new_mirror, lists_d="/var/lib/apt/lists"): - oprefix = "%s/%s" % (lists_d, mirror2lists_fileprefix(omirror)) - nprefix = "%s/%s" % (lists_d, mirror2lists_fileprefix(new_mirror)) - if oprefix == nprefix: - return - olen = len(oprefix) - for filename in glob.glob("%s_*" % oprefix): - # TODO use the cloud.paths.join... - util.rename(filename, "%s%s" % (nprefix, filename[olen:])) - - -def get_release(): - (stdout, _stderr) = util.subp(['lsb_release', '-cs']) - return stdout.strip() - - -def generate_sources_list(codename, mirror, cloud, log): - template_fn = cloud.get_template_filename('sources.list') - if template_fn: - params = {'mirror': mirror, 'codename': codename} - out_fn = cloud.paths.join(False, '/etc/apt/sources.list') - templater.render_to_file(template_fn, out_fn, params) - else: - log.warn("No template found, not rendering /etc/apt/sources.list") - - -def add_sources(cloud, srclist, template_params=None): - """ - add entries in /etc/apt/sources.list.d for each abbreviated - sources.list entry in 'srclist'. When rendering template, also - include the values in dictionary searchList - """ - if template_params is None: - template_params = {} - - errorlist = [] - for ent in srclist: - if 'source' not in ent: - errorlist.append(["", "missing source"]) - continue - - source = ent['source'] - if source.startswith("ppa:"): - try: - util.subp(["add-apt-repository", source]) - except: - errorlist.append([source, "add-apt-repository failed"]) - continue - - source = templater.render_string(source, template_params) - - if 'filename' not in ent: - ent['filename'] = 'cloud_config_sources.list' - - if not ent['filename'].startswith("/"): - ent['filename'] = os.path.join("/etc/apt/sources.list.d/", - ent['filename']) - - if ('keyid' in ent and 'key' not in ent): - ks = "keyserver.ubuntu.com" - if 'keyserver' in ent: - ks = ent['keyserver'] - try: - ent['key'] = util.getkeybyid(ent['keyid'], ks) - except: - errorlist.append([source, "failed to get key from %s" % ks]) - continue - - if 'key' in ent: - try: - util.subp(('apt-key', 'add', '-'), ent['key']) - except: - errorlist.append([source, "failed add key"]) - - try: - contents = "%s\n" % (source) - util.write_file(cloud.paths.join(False, ent['filename']), - contents, omode="ab") - except: - errorlist.append([source, - "failed write to file %s" % ent['filename']]) - - return errorlist - - -def find_apt_mirror(cloud, cfg): - """ find an apt_mirror given the cloud and cfg provided """ - - mirror = None - - cfg_mirror = cfg.get("apt_mirror", None) - if cfg_mirror: - mirror = cfg["apt_mirror"] - elif "apt_mirror_search" in cfg: - mirror = util.search_for_mirror(cfg['apt_mirror_search']) - else: - mirror = cloud.get_local_mirror() - - mydom = "" - - doms = [] - - if not mirror: - # if we have a fqdn, then search its domain portion first - (_hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) - mydom = ".".join(fqdn.split(".")[1:]) - if mydom: - doms.append(".%s" % mydom) - - if not mirror: - doms.extend((".localdomain", "",)) - - mirror_list = [] - distro = cloud.distro.name - mirrorfmt = "http://%s-mirror%s/%s" % (distro, "%s", distro) - for post in doms: - mirror_list.append(mirrorfmt % (post)) - - mirror = util.search_for_mirror(mirror_list) - - if not mirror: - mirror = cloud.distro.get_package_mirror() - - return mirror diff --git a/cloudinit/config/bootcmd.py b/cloudinit/config/bootcmd.py deleted file mode 100644 index 635e3a1f..00000000 --- a/cloudinit/config/bootcmd.py +++ /dev/null @@ -1,56 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os -import tempfile - -from cloudinit import util -from cloudinit.settings import PER_ALWAYS - -frequency = PER_ALWAYS - - -def handle(name, cfg, cloud, log, _args): - - if "bootcmd" not in cfg: - log.debug(("Skipping transform named %s," - " no 'bootcmd' key in configuration"), name) - return - - with tempfile.NamedTemporaryFile(suffix=".sh") as tmpf: - try: - content = util.shellify(cfg["bootcmd"]) - tmpf.write(content) - tmpf.flush() - except: - util.logexc(log, "Failed to shellify bootcmd") - raise - - try: - env = os.environ.copy() - iid = cloud.get_instance_id() - if iid: - env['INSTANCE_ID'] = str(iid) - cmd = ['/bin/sh', tmpf.name] - util.subp(cmd, env=env, capture=False) - except: - util.logexc(log, - ("Failed to run bootcmd transform %s"), name) - raise diff --git a/cloudinit/config/byobu.py b/cloudinit/config/byobu.py deleted file mode 100644 index 741aa934..00000000 --- a/cloudinit/config/byobu.py +++ /dev/null @@ -1,71 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -from cloudinit import util - -distros = ['ubuntu', 'debian'] - - -def handle(name, cfg, _cloud, log, args): - if len(args) != 0: - value = args[0] - else: - value = util.get_cfg_option_str(cfg, "byobu_by_default", "") - - if not value: - log.debug("Skipping transform named %s, no 'byobu' values found", name) - return - - if value == "user" or value == "system": - value = "enable-%s" % value - - valid = ("enable-user", "enable-system", "enable", - "disable-user", "disable-system", "disable") - if not value in valid: - log.warn("Unknown value %s for byobu_by_default", value) - - mod_user = value.endswith("-user") - mod_sys = value.endswith("-system") - if value.startswith("enable"): - bl_inst = "install" - dc_val = "byobu byobu/launch-by-default boolean true" - mod_sys = True - else: - if value == "disable": - mod_user = True - mod_sys = True - bl_inst = "uninstall" - dc_val = "byobu byobu/launch-by-default boolean false" - - shcmd = "" - if mod_user: - user = util.get_cfg_option_str(cfg, "user", "ubuntu") - shcmd += " sudo -Hu \"%s\" byobu-launcher-%s" % (user, bl_inst) - shcmd += " || X=$(($X+1)); " - if mod_sys: - shcmd += "echo \"%s\" | debconf-set-selections" % dc_val - shcmd += " && dpkg-reconfigure byobu --frontend=noninteractive" - shcmd += " || X=$(($X+1)); " - - cmd = ["/bin/sh", "-c", "%s %s %s" % ("X=0;", shcmd, "exit $X")] - - log.debug("Setting byobu to %s", value) - - util.subp(cmd) diff --git a/cloudinit/config/ca_certs.py b/cloudinit/config/ca_certs.py deleted file mode 100644 index 56c41561..00000000 --- a/cloudinit/config/ca_certs.py +++ /dev/null @@ -1,99 +0,0 @@ -# vi: ts=4 expandtab -# -# Author: Mike Milner -# -# 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 . - -import os - -from cloudinit import util - -CA_CERT_PATH = "/usr/share/ca-certificates/" -CA_CERT_FILENAME = "cloud-init-ca-certs.crt" -CA_CERT_CONFIG = "/etc/ca-certificates.conf" -CA_CERT_SYSTEM_PATH = "/etc/ssl/certs/" - -distros = ['ubuntu'] - - -def update_ca_certs(): - """ - Updates the CA certificate cache on the current machine. - """ - util.subp(["update-ca-certificates"]) - - -def add_ca_certs(cloud, certs): - """ - Adds certificates to the system. To actually apply the new certificates - you must also call L{update_ca_certs}. - - @param certs: A list of certificate strings. - """ - if certs: - # First ensure they are strings... - cert_file_contents = "\n".join([str(c) for c in certs]) - cert_file_fullpath = os.path.join(CA_CERT_PATH, CA_CERT_FILENAME) - cert_file_fullpath = cloud.paths.join(False, cert_file_fullpath) - util.write_file(cert_file_fullpath, cert_file_contents, mode=0644) - # Append cert filename to CA_CERT_CONFIG file. - util.write_file(cloud.paths.join(False, CA_CERT_CONFIG), - "\n%s" % CA_CERT_FILENAME, omode="ab") - - -def remove_default_ca_certs(cloud): - """ - Removes all default trusted CA certificates from the system. To actually - apply the change you must also call L{update_ca_certs}. - """ - util.delete_dir_contents(cloud.paths.join(False, CA_CERT_PATH)) - util.delete_dir_contents(cloud.paths.join(False, CA_CERT_SYSTEM_PATH)) - util.write_file(cloud.paths.join(False, CA_CERT_CONFIG), "", mode=0644) - debconf_sel = "ca-certificates ca-certificates/trust_new_crts select no" - util.subp(('debconf-set-selections', '-'), debconf_sel) - - -def handle(name, cfg, cloud, log, _args): - """ - Call to handle ca-cert sections in cloud-config file. - - @param name: The module name "ca-cert" from cloud.cfg - @param cfg: A nested dict containing the entire cloud config contents. - @param cloud: The L{CloudInit} object in use. - @param log: Pre-initialized Python logger object to use for logging. - @param args: Any module arguments from cloud.cfg - """ - # If there isn't a ca-certs section in the configuration don't do anything - if "ca-certs" not in cfg: - log.debug(("Skipping transform named %s," - " no 'ca-certs' key in configuration"), name) - return - - ca_cert_cfg = cfg['ca-certs'] - - # If there is a remove-defaults option set to true, remove the system - # default trusted CA certs first. - if ca_cert_cfg.get("remove-defaults", False): - log.debug("Removing default certificates") - remove_default_ca_certs(cloud) - - # If we are given any new trusted CA certs to add, add them. - if "trusted" in ca_cert_cfg: - trusted_certs = util.get_cfg_option_list(ca_cert_cfg, "trusted") - if trusted_certs: - log.debug("Adding %d certificates" % len(trusted_certs)) - add_ca_certs(cloud, trusted_certs) - - # Update the system with the new cert configuration. - log.debug("Updating certificates") - update_ca_certs() diff --git a/cloudinit/config/cc_apt_pipelining.py b/cloudinit/config/cc_apt_pipelining.py new file mode 100644 index 00000000..f460becb --- /dev/null +++ b/cloudinit/config/cc_apt_pipelining.py @@ -0,0 +1,57 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Ben Howard +# +# 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 . + +from cloudinit import util +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + +distros = ['ubuntu', 'debian'] + +DEFAULT_FILE = "/etc/apt/apt.conf.d/90cloud-init-pipelining" + +# Acquire::http::Pipeline-Depth can be a value +# from 0 to 5 indicating how many outstanding requests APT should send. +# A value of zero MUST be specified if the remote host does not properly linger +# on TCP connections - otherwise data corruption will occur. + + +def handle(_name, cfg, cloud, log, _args): + + apt_pipe_value = util.get_cfg_option_str(cfg, "apt_pipelining", False) + apt_pipe_value_s = str(apt_pipe_value).lower().strip() + + if apt_pipe_value_s == "false": + write_apt_snippet(cloud, "0", log, DEFAULT_FILE) + elif apt_pipe_value_s in ("none", "unchanged", "os"): + return + elif apt_pipe_value_s in [str(b) for b in xrange(0, 6)]: + write_apt_snippet(cloud, apt_pipe_value_s, log, DEFAULT_FILE) + else: + log.warn("Invalid option for apt_pipeling: %s", apt_pipe_value) + + +def write_apt_snippet(cloud, setting, log, f_name): + """ Writes f_name with apt pipeline depth 'setting' """ + + file_contents = ("//Written by cloud-init per 'apt_pipelining'\n" + 'Acquire::http::Pipeline-Depth "%s";\n') % (setting) + + util.write_file(cloud.paths.join(False, f_name), file_contents) + + log.debug("Wrote %s with apt pipeline depth setting %s", f_name, setting) diff --git a/cloudinit/config/cc_apt_update_upgrade.py b/cloudinit/config/cc_apt_update_upgrade.py new file mode 100644 index 00000000..f5b4b58f --- /dev/null +++ b/cloudinit/config/cc_apt_update_upgrade.py @@ -0,0 +1,243 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import glob +import os + +from cloudinit import templater +from cloudinit import util + +distros = ['ubuntu', 'debian'] + +PROXY_TPL = "Acquire::HTTP::Proxy \"%s\";\n" + + +def handle(_name, cfg, cloud, log, _args): + update = util.get_cfg_option_bool(cfg, 'apt_update', False) + upgrade = util.get_cfg_option_bool(cfg, 'apt_upgrade', False) + + release = get_release() + + mirror = find_apt_mirror(cloud, cfg) + + log.debug("Selected mirror at: %s" % mirror) + + if not util.get_cfg_option_bool(cfg, + 'apt_preserve_sources_list', False): + generate_sources_list(release, mirror, cloud, log) + old_mir = util.get_cfg_option_str(cfg, 'apt_old_mirror', + "archive.ubuntu.com/ubuntu") + rename_apt_lists(old_mir, mirror) + + # Set up any apt proxy + proxy = cfg.get("apt_proxy", None) + proxy_filename = "/etc/apt/apt.conf.d/95cloud-init-proxy" + if proxy: + try: + # See man 'apt.conf' + contents = PROXY_TPL % (proxy) + util.write_file(cloud.paths.join(False, proxy_filename), + contents) + except Exception as e: + util.logexc(log, "Failed to write proxy to %s", proxy_filename) + elif os.path.isfile(proxy_filename): + util.del_file(proxy_filename) + + # Process 'apt_sources' + if 'apt_sources' in cfg: + errors = add_sources(cloud, cfg['apt_sources'], + {'MIRROR': mirror, 'RELEASE': release}) + for e in errors: + log.warn("Source Error: %s", ':'.join(e)) + + dconf_sel = util.get_cfg_option_str(cfg, 'debconf_selections', False) + if dconf_sel: + log.debug("setting debconf selections per cloud config") + try: + util.subp(('debconf-set-selections', '-'), dconf_sel) + except: + util.logexc(log, "Failed to run debconf-set-selections") + + pkglist = util.get_cfg_option_list(cfg, 'packages', []) + + errors = [] + if update or len(pkglist) or upgrade: + try: + cloud.distro.update_package_sources() + except Exception as e: + util.logexc(log, "Package update failed") + errors.append(e) + + if upgrade: + try: + cloud.distro.package_command("upgrade") + except Exception as e: + util.logexc(log, "Package upgrade failed") + errors.append(e) + + if len(pkglist): + try: + cloud.distro.install_packages(pkglist) + except Exception as e: + util.logexc(log, "Failed to install packages: %s ", pkglist) + errors.append(e) + + if len(errors): + log.warn("%s failed with exceptions, re-raising the last one", + len(errors)) + raise errors[-1] + + +def mirror2lists_fileprefix(mirror): + string = mirror + # take of http:// or ftp:// + if string.endswith("/"): + string = string[0:-1] + pos = string.find("://") + if pos >= 0: + string = string[pos + 3:] + string = string.replace("/", "_") + return string + + +def rename_apt_lists(omirror, new_mirror, lists_d="/var/lib/apt/lists"): + oprefix = "%s/%s" % (lists_d, mirror2lists_fileprefix(omirror)) + nprefix = "%s/%s" % (lists_d, mirror2lists_fileprefix(new_mirror)) + if oprefix == nprefix: + return + olen = len(oprefix) + for filename in glob.glob("%s_*" % oprefix): + # TODO use the cloud.paths.join... + util.rename(filename, "%s%s" % (nprefix, filename[olen:])) + + +def get_release(): + (stdout, _stderr) = util.subp(['lsb_release', '-cs']) + return stdout.strip() + + +def generate_sources_list(codename, mirror, cloud, log): + template_fn = cloud.get_template_filename('sources.list') + if template_fn: + params = {'mirror': mirror, 'codename': codename} + out_fn = cloud.paths.join(False, '/etc/apt/sources.list') + templater.render_to_file(template_fn, out_fn, params) + else: + log.warn("No template found, not rendering /etc/apt/sources.list") + + +def add_sources(cloud, srclist, template_params=None): + """ + add entries in /etc/apt/sources.list.d for each abbreviated + sources.list entry in 'srclist'. When rendering template, also + include the values in dictionary searchList + """ + if template_params is None: + template_params = {} + + errorlist = [] + for ent in srclist: + if 'source' not in ent: + errorlist.append(["", "missing source"]) + continue + + source = ent['source'] + if source.startswith("ppa:"): + try: + util.subp(["add-apt-repository", source]) + except: + errorlist.append([source, "add-apt-repository failed"]) + continue + + source = templater.render_string(source, template_params) + + if 'filename' not in ent: + ent['filename'] = 'cloud_config_sources.list' + + if not ent['filename'].startswith("/"): + ent['filename'] = os.path.join("/etc/apt/sources.list.d/", + ent['filename']) + + if ('keyid' in ent and 'key' not in ent): + ks = "keyserver.ubuntu.com" + if 'keyserver' in ent: + ks = ent['keyserver'] + try: + ent['key'] = util.getkeybyid(ent['keyid'], ks) + except: + errorlist.append([source, "failed to get key from %s" % ks]) + continue + + if 'key' in ent: + try: + util.subp(('apt-key', 'add', '-'), ent['key']) + except: + errorlist.append([source, "failed add key"]) + + try: + contents = "%s\n" % (source) + util.write_file(cloud.paths.join(False, ent['filename']), + contents, omode="ab") + except: + errorlist.append([source, + "failed write to file %s" % ent['filename']]) + + return errorlist + + +def find_apt_mirror(cloud, cfg): + """ find an apt_mirror given the cloud and cfg provided """ + + mirror = None + + cfg_mirror = cfg.get("apt_mirror", None) + if cfg_mirror: + mirror = cfg["apt_mirror"] + elif "apt_mirror_search" in cfg: + mirror = util.search_for_mirror(cfg['apt_mirror_search']) + else: + mirror = cloud.get_local_mirror() + + mydom = "" + + doms = [] + + if not mirror: + # if we have a fqdn, then search its domain portion first + (_hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) + mydom = ".".join(fqdn.split(".")[1:]) + if mydom: + doms.append(".%s" % mydom) + + if not mirror: + doms.extend((".localdomain", "",)) + + mirror_list = [] + distro = cloud.distro.name + mirrorfmt = "http://%s-mirror%s/%s" % (distro, "%s", distro) + for post in doms: + mirror_list.append(mirrorfmt % (post)) + + mirror = util.search_for_mirror(mirror_list) + + if not mirror: + mirror = cloud.distro.get_package_mirror() + + return mirror diff --git a/cloudinit/config/cc_bootcmd.py b/cloudinit/config/cc_bootcmd.py new file mode 100644 index 00000000..635e3a1f --- /dev/null +++ b/cloudinit/config/cc_bootcmd.py @@ -0,0 +1,56 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os +import tempfile + +from cloudinit import util +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + + +def handle(name, cfg, cloud, log, _args): + + if "bootcmd" not in cfg: + log.debug(("Skipping transform named %s," + " no 'bootcmd' key in configuration"), name) + return + + with tempfile.NamedTemporaryFile(suffix=".sh") as tmpf: + try: + content = util.shellify(cfg["bootcmd"]) + tmpf.write(content) + tmpf.flush() + except: + util.logexc(log, "Failed to shellify bootcmd") + raise + + try: + env = os.environ.copy() + iid = cloud.get_instance_id() + if iid: + env['INSTANCE_ID'] = str(iid) + cmd = ['/bin/sh', tmpf.name] + util.subp(cmd, env=env, capture=False) + except: + util.logexc(log, + ("Failed to run bootcmd transform %s"), name) + raise diff --git a/cloudinit/config/cc_byobu.py b/cloudinit/config/cc_byobu.py new file mode 100644 index 00000000..741aa934 --- /dev/null +++ b/cloudinit/config/cc_byobu.py @@ -0,0 +1,71 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util + +distros = ['ubuntu', 'debian'] + + +def handle(name, cfg, _cloud, log, args): + if len(args) != 0: + value = args[0] + else: + value = util.get_cfg_option_str(cfg, "byobu_by_default", "") + + if not value: + log.debug("Skipping transform named %s, no 'byobu' values found", name) + return + + if value == "user" or value == "system": + value = "enable-%s" % value + + valid = ("enable-user", "enable-system", "enable", + "disable-user", "disable-system", "disable") + if not value in valid: + log.warn("Unknown value %s for byobu_by_default", value) + + mod_user = value.endswith("-user") + mod_sys = value.endswith("-system") + if value.startswith("enable"): + bl_inst = "install" + dc_val = "byobu byobu/launch-by-default boolean true" + mod_sys = True + else: + if value == "disable": + mod_user = True + mod_sys = True + bl_inst = "uninstall" + dc_val = "byobu byobu/launch-by-default boolean false" + + shcmd = "" + if mod_user: + user = util.get_cfg_option_str(cfg, "user", "ubuntu") + shcmd += " sudo -Hu \"%s\" byobu-launcher-%s" % (user, bl_inst) + shcmd += " || X=$(($X+1)); " + if mod_sys: + shcmd += "echo \"%s\" | debconf-set-selections" % dc_val + shcmd += " && dpkg-reconfigure byobu --frontend=noninteractive" + shcmd += " || X=$(($X+1)); " + + cmd = ["/bin/sh", "-c", "%s %s %s" % ("X=0;", shcmd, "exit $X")] + + log.debug("Setting byobu to %s", value) + + util.subp(cmd) diff --git a/cloudinit/config/cc_ca_certs.py b/cloudinit/config/cc_ca_certs.py new file mode 100644 index 00000000..56c41561 --- /dev/null +++ b/cloudinit/config/cc_ca_certs.py @@ -0,0 +1,99 @@ +# vi: ts=4 expandtab +# +# Author: Mike Milner +# +# 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 . + +import os + +from cloudinit import util + +CA_CERT_PATH = "/usr/share/ca-certificates/" +CA_CERT_FILENAME = "cloud-init-ca-certs.crt" +CA_CERT_CONFIG = "/etc/ca-certificates.conf" +CA_CERT_SYSTEM_PATH = "/etc/ssl/certs/" + +distros = ['ubuntu'] + + +def update_ca_certs(): + """ + Updates the CA certificate cache on the current machine. + """ + util.subp(["update-ca-certificates"]) + + +def add_ca_certs(cloud, certs): + """ + Adds certificates to the system. To actually apply the new certificates + you must also call L{update_ca_certs}. + + @param certs: A list of certificate strings. + """ + if certs: + # First ensure they are strings... + cert_file_contents = "\n".join([str(c) for c in certs]) + cert_file_fullpath = os.path.join(CA_CERT_PATH, CA_CERT_FILENAME) + cert_file_fullpath = cloud.paths.join(False, cert_file_fullpath) + util.write_file(cert_file_fullpath, cert_file_contents, mode=0644) + # Append cert filename to CA_CERT_CONFIG file. + util.write_file(cloud.paths.join(False, CA_CERT_CONFIG), + "\n%s" % CA_CERT_FILENAME, omode="ab") + + +def remove_default_ca_certs(cloud): + """ + Removes all default trusted CA certificates from the system. To actually + apply the change you must also call L{update_ca_certs}. + """ + util.delete_dir_contents(cloud.paths.join(False, CA_CERT_PATH)) + util.delete_dir_contents(cloud.paths.join(False, CA_CERT_SYSTEM_PATH)) + util.write_file(cloud.paths.join(False, CA_CERT_CONFIG), "", mode=0644) + debconf_sel = "ca-certificates ca-certificates/trust_new_crts select no" + util.subp(('debconf-set-selections', '-'), debconf_sel) + + +def handle(name, cfg, cloud, log, _args): + """ + Call to handle ca-cert sections in cloud-config file. + + @param name: The module name "ca-cert" from cloud.cfg + @param cfg: A nested dict containing the entire cloud config contents. + @param cloud: The L{CloudInit} object in use. + @param log: Pre-initialized Python logger object to use for logging. + @param args: Any module arguments from cloud.cfg + """ + # If there isn't a ca-certs section in the configuration don't do anything + if "ca-certs" not in cfg: + log.debug(("Skipping transform named %s," + " no 'ca-certs' key in configuration"), name) + return + + ca_cert_cfg = cfg['ca-certs'] + + # If there is a remove-defaults option set to true, remove the system + # default trusted CA certs first. + if ca_cert_cfg.get("remove-defaults", False): + log.debug("Removing default certificates") + remove_default_ca_certs(cloud) + + # If we are given any new trusted CA certs to add, add them. + if "trusted" in ca_cert_cfg: + trusted_certs = util.get_cfg_option_list(ca_cert_cfg, "trusted") + if trusted_certs: + log.debug("Adding %d certificates" % len(trusted_certs)) + add_ca_certs(cloud, trusted_certs) + + # Update the system with the new cert configuration. + log.debug("Updating certificates") + update_ca_certs() diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py new file mode 100644 index 00000000..4e8ef346 --- /dev/null +++ b/cloudinit/config/cc_chef.py @@ -0,0 +1,128 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Avishai Ish-Shalom +# Author: Mike Moulton +# Author: Juerg Haefliger +# +# 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 . + +import json +import os + +from cloudinit import templater +from cloudinit import util + +ruby_version_default = "1.8" + + +def handle(name, cfg, cloud, log, _args): + + # If there isn't a chef key in the configuration don't do anything + if 'chef' not in cfg: + log.debug(("Skipping transform named %s," + " no 'chef' key in configuration"), name) + return + chef_cfg = cfg['chef'] + + # Ensure the chef directories we use exist + c_dirs = [ + '/etc/chef', + '/var/log/chef', + '/var/lib/chef', + '/var/cache/chef', + '/var/backups/chef', + '/var/run/chef', + ] + for d in c_dirs: + util.ensure_dir(cloud.paths.join(False, d)) + + # Set the validation key based on the presence of either 'validation_key' + # or 'validation_cert'. In the case where both exist, 'validation_key' + # takes precedence + for key in ('validation_key', 'validation_cert'): + if key in chef_cfg and chef_cfg[key]: + v_fn = cloud.paths.join(False, '/etc/chef/validation.pem') + util.write_file(v_fn, chef_cfg[key]) + break + + # Create the chef config from template + template_fn = cloud.get_template_filename('chef_client.rb') + if template_fn: + iid = str(cloud.datasource.get_instance_id()) + params = { + 'server_url': chef_cfg['server_url'], + 'node_name': util.get_cfg_option_str(chef_cfg, 'node_name', iid), + 'environment': util.get_cfg_option_str(chef_cfg, 'environment', + '_default'), + 'validation_name': chef_cfg['validation_name'] + } + out_fn = cloud.paths.join(False, '/etc/chef/client.rb') + templater.render_to_file(template_fn, out_fn, params) + else: + log.warn("No template found, not rendering to /etc/chef/client.rb") + + # set the firstboot json + initial_json = {} + if 'run_list' in chef_cfg: + initial_json['run_list'] = chef_cfg['run_list'] + if 'initial_attributes' in chef_cfg: + initial_attributes = chef_cfg['initial_attributes'] + for k in list(initial_attributes.keys()): + initial_json[k] = initial_attributes[k] + firstboot_fn = cloud.paths.join(False, '/etc/chef/firstboot.json') + util.write_file(firstboot_fn, json.dumps(initial_json)) + + # If chef is not installed, we install chef based on 'install_type' + if not os.path.isfile('/usr/bin/chef-client'): + install_type = util.get_cfg_option_str(chef_cfg, 'install_type', + 'packages') + if install_type == "gems": + # this will install and run the chef-client from gems + chef_version = util.get_cfg_option_str(chef_cfg, 'version', None) + ruby_version = util.get_cfg_option_str(chef_cfg, 'ruby_version', + ruby_version_default) + install_chef_from_gems(cloud.distro, ruby_version, chef_version) + # and finally, run chef-client + log.debug('Running chef-client') + util.subp(['/usr/bin/chef-client', '-d', '-i', '1800', '-s', '20']) + elif install_type == 'packages': + # this will install and run the chef-client from packages + cloud.distro.install_packages(('chef',)) + else: + log.warn("Unknown chef install type %s", install_type) + + +def get_ruby_packages(version): + # return a list of packages needed to install ruby at version + pkgs = ['ruby%s' % version, 'ruby%s-dev' % version] + if version == "1.8": + pkgs.extend(('libopenssl-ruby1.8', 'rubygems1.8')) + return pkgs + + +def install_chef_from_gems(ruby_version, chef_version, distro): + distro.install_packages(get_ruby_packages(ruby_version)) + if not os.path.exists('/usr/bin/gem'): + util.sym_link('/usr/bin/gem%s' % ruby_version, '/usr/bin/gem') + if not os.path.exists('/usr/bin/ruby'): + util.sym_link('/usr/bin/ruby%s' % ruby_version, '/usr/bin/ruby') + if chef_version: + util.subp(['/usr/bin/gem', 'install', 'chef', + '-v %s' % chef_version, '--no-ri', + '--no-rdoc', '--bindir', '/usr/bin', '-q']) + else: + util.subp(['/usr/bin/gem', 'install', 'chef', + '--no-ri', '--no-rdoc', '--bindir', + '/usr/bin', '-q']) diff --git a/cloudinit/config/cc_disable_ec2_metadata.py b/cloudinit/config/cc_disable_ec2_metadata.py new file mode 100644 index 00000000..c7d26029 --- /dev/null +++ b/cloudinit/config/cc_disable_ec2_metadata.py @@ -0,0 +1,36 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util + +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + +reject_cmd = ['route', 'add', '-host', '169.254.169.254', 'reject'] + + +def handle(name, cfg, _cloud, log, _args): + disabled = util.get_cfg_option_bool(cfg, "disable_ec2_metadata", False) + if disabled: + util.subp(reject_cmd) + else: + log.debug(("Skipping transform named %s," + " disabling the ec2 route not enabled"), name) diff --git a/cloudinit/config/cc_final_message.py b/cloudinit/config/cc_final_message.py new file mode 100644 index 00000000..c257b6d0 --- /dev/null +++ b/cloudinit/config/cc_final_message.py @@ -0,0 +1,71 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import sys + +from cloudinit import templater +from cloudinit import util +from cloudinit import version + +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + +final_message_def = ("Cloud-init v. {{version}} finished at {{timestamp}}." + " Up {{uptime}} seconds.") + + +def handle(_name, cfg, cloud, log, args): + + msg_in = None + if len(args) != 0: + msg_in = args[0] + else: + msg_in = util.get_cfg_option_str(cfg, "final_message") + + if not msg_in: + template_fn = cloud.get_template_filename('final_message') + if template_fn: + msg_in = util.load_file(template_fn) + + if not msg_in: + msg_in = final_message_def + + uptime = util.uptime() + ts = util.time_rfc2822() + cver = version.version_string() + try: + subs = { + 'uptime': uptime, + 'timestamp': ts, + 'version': cver, + } + # Use stdout, stderr or the logger?? + content = templater.render_string(msg_in, subs) + sys.stderr.write("%s\n" % (content)) + except Exception: + util.logexc(log, "Failed to render final message template") + + boot_fin_fn = cloud.paths.boot_finished + try: + contents = "%s - %s - v. %s\n" % (uptime, ts, cver) + util.write_file(boot_fin_fn, contents) + except: + util.logexc(log, "Failed to write boot finished file %s", boot_fin_fn) diff --git a/cloudinit/config/cc_foo.py b/cloudinit/config/cc_foo.py new file mode 100644 index 00000000..99135704 --- /dev/null +++ b/cloudinit/config/cc_foo.py @@ -0,0 +1,52 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit.settings import PER_INSTANCE + +# Modules are expected to have the following attributes. +# 1. A required 'handle' method which takes the following params. +# a) The name will not be this files name, but instead +# the name specified in configuration (which is the name +# which will be used to find this module). +# b) A configuration object that is the result of the merging +# of cloud configs configuration with legacy configuration +# as well as any datasource provided configuration +# c) A cloud object that can be used to access various +# datasource and paths for the given distro and data provided +# by the various datasource instance types. +# d) A argument list that may or may not be empty to this module. +# Typically those are from module configuration where the module +# is defined with some extra configuration that will eventually +# be translated from yaml into arguments to this module. +# 2. A optional 'frequency' that defines how often this module should be ran. +# Typically one of PER_INSTANCE, PER_ALWAYS, PER_ONCE. If not +# provided PER_INSTANCE will be assumed. +# See settings.py for these constants. +# 3. A optional 'distros' array/set/tuple that defines the known distros +# this module will work with (if not all of them). This is used to write +# a warning out if a module is being ran on a untested distribution for +# informational purposes. If non existent all distros are assumed and +# no warning occurs. + +frequency = PER_INSTANCE + + +def handle(name, _cfg, _cloud, log, _args): + log.debug("Hi from transform %s", name) diff --git a/cloudinit/config/cc_grub_dpkg.py b/cloudinit/config/cc_grub_dpkg.py new file mode 100644 index 00000000..02f05ce3 --- /dev/null +++ b/cloudinit/config/cc_grub_dpkg.py @@ -0,0 +1,67 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +distros = ['ubuntu', 'debian'] + + +def handle(_name, cfg, _cloud, log, _args): + idevs = None + idevs_empty = None + + 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) + + if ((os.path.exists("/dev/sda1") and not os.path.exists("/dev/sda")) or + (os.path.exists("/dev/xvda1") and not os.path.exists("/dev/xvda"))): + if idevs == None: + idevs = "" + if idevs_empty == None: + idevs_empty = "true" + else: + if idevs_empty == None: + idevs_empty = "false" + if idevs == None: + idevs = "/dev/sda" + for dev in ("/dev/sda", "/dev/vda", "/dev/sda1", "/dev/vda1"): + if os.path.exists(dev): + idevs = dev + break + + # now idevs and idevs_empty are set to determined values + # or, those set by user + + dconf_sel = (("grub-pc grub-pc/install_devices string %s\n" + "grub-pc grub-pc/install_devices_empty boolean %s\n") % + (idevs, idevs_empty)) + + log.debug("Setting grub debconf-set-selections with '%s','%s'" % + (idevs, idevs_empty)) + + try: + util.subp(['debconf-set-selections'], dconf_sel) + except: + util.logexc(log, "Failed to run debconf-set-selections for grub-dpkg") diff --git a/cloudinit/config/cc_keys_to_console.py b/cloudinit/config/cc_keys_to_console.py new file mode 100644 index 00000000..40758198 --- /dev/null +++ b/cloudinit/config/cc_keys_to_console.py @@ -0,0 +1,52 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit.settings import PER_INSTANCE +from cloudinit import util + +frequency = PER_INSTANCE + +# This is a tool that cloud init provides +helper_tool = '/usr/lib/cloud-init/write-ssh-key-fingerprints' + + +def handle(name, cfg, cloud, log, _args): + if not os.path.exists(helper_tool): + log.warn(("Unable to activate transform %s," + " helper tool not found at %s"), name, helper_tool) + return + + fp_blacklist = util.get_cfg_option_list(cfg, + "ssh_fp_console_blacklist", []) + key_blacklist = util.get_cfg_option_list(cfg, + "ssh_key_console_blacklist", + ["ssh-dss"]) + + try: + cmd = [helper_tool] + cmd.append(','.join(fp_blacklist)) + cmd.append(','.join(key_blacklist)) + (stdout, _stderr) = util.subp(cmd) + util.write_file(cloud.paths.join(False, '/dev/console'), stdout) + except: + log.warn("Writing keys to /dev/console failed!") + raise diff --git a/cloudinit/config/cc_landscape.py b/cloudinit/config/cc_landscape.py new file mode 100644 index 00000000..29ce41b9 --- /dev/null +++ b/cloudinit/config/cc_landscape.py @@ -0,0 +1,97 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from StringIO import StringIO + +try: + from configobj import ConfigObj +except ImportError: + ConfigObj = None + +from cloudinit import util + +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + +LSC_CLIENT_CFG_FILE = "/etc/landscape/client.conf" + +distros = ['ubuntu'] + +# defaults taken from stock client.conf in landscape-client 11.07.1.1-0ubuntu2 +LSC_BUILTIN_CFG = { + 'client': { + 'log_level': "info", + 'url': "https://landscape.canonical.com/message-system", + 'ping_url': "http://landscape.canonical.com/ping", + 'data_path': "/var/lib/landscape/client", + } +} + + +def handle(name, cfg, cloud, log, _args): + """ + Basically turn a top level 'landscape' entry with a 'client' dict + and render it to ConfigObj format under '[client]' section in + /etc/landscape/client.conf + """ + if not ConfigObj: + log.warn(("'ConfigObj' support not available," + " running transform %s disabled"), name) + return + + ls_cloudcfg = cfg.get("landscape", {}) + + if not isinstance(ls_cloudcfg, dict): + raise Exception(("'landscape' key existed in config," + " but not a dictionary type," + " is a %s instead"), util.obj_name(ls_cloudcfg)) + + lsc_client_fn = cloud.paths.join(True, LSC_CLIENT_CFG_FILE) + merged = merge_together([LSC_BUILTIN_CFG, lsc_client_fn, ls_cloudcfg]) + + lsc_dir = cloud.paths.join(False, os.path.dirname(lsc_client_fn)) + if not os.path.isdir(lsc_dir): + util.ensure_dir(lsc_dir) + + contents = StringIO() + merged.write(contents) + contents.flush() + + util.write_file(lsc_client_fn, contents.getvalue()) + log.debug("Wrote landscape config file to %s", lsc_client_fn) + + +def merge_together(objs): + """ + merge together ConfigObj objects or things that ConfigObj() will take in + later entries override earlier + """ + cfg = ConfigObj({}) + for obj in objs: + if not obj: + continue + if isinstance(obj, ConfigObj): + cfg.merge(obj) + else: + cfg.merge(ConfigObj(obj)) + return cfg diff --git a/cloudinit/config/cc_locale.py b/cloudinit/config/cc_locale.py new file mode 100644 index 00000000..7f273123 --- /dev/null +++ b/cloudinit/config/cc_locale.py @@ -0,0 +1,58 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import templater +from cloudinit import util + + +def apply_locale(locale, cfgfile, cloud, log): + # TODO this command might not work on RH... + if os.path.exists('/usr/sbin/locale-gen'): + util.subp(['locale-gen', locale], capture=False) + if os.path.exists('/usr/sbin/update-locale'): + util.subp(['update-locale', locale], capture=False) + if not cfgfile: + return + template_fn = cloud.get_template_filename('default-locale') + if not template_fn: + log.warn("No template filename found to write to %s", cfgfile) + else: + templater.render_to_file(template_fn, cfgfile, {'locale': locale}) + + +def handle(name, cfg, cloud, log, args): + if len(args) != 0: + locale = args[0] + else: + locale = util.get_cfg_option_str(cfg, "locale", cloud.get_locale()) + + locale_cfgfile = util.get_cfg_option_str(cfg, "locale_configfile", + "/etc/default/locale") + + if not locale: + log.debug(("Skipping transform named %s, " + "no 'locale' configuration found"), name) + return + + log.debug("Setting locale to %s", locale) + + apply_locale(locale, locale_cfgfile, cloud, log) diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py new file mode 100644 index 00000000..700fbc44 --- /dev/null +++ b/cloudinit/config/cc_mounts.py @@ -0,0 +1,200 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from string import whitespace # pylint: disable=W0402 + +import re + +from cloudinit import util + +# shortname matches 'sda', 'sda1', 'xvda', 'hda', 'sdb', xvdb, vda, vdd1 +shortname_filter = r"^[x]{0,1}[shv]d[a-z][0-9]*$" +shortname = re.compile(shortname_filter) +ws = re.compile("[%s]+" % (whitespace)) + + +def is_mdname(name): + # return true if this is a metadata service name + if name in ["ami", "root", "swap"]: + return True + # names 'ephemeral0' or 'ephemeral1' + # 'ebs[0-9]' appears when '--block-device-mapping sdf=snap-d4d90bbc' + for enumname in ("ephemeral", "ebs"): + if name.startswith(enumname) and name.find(":") == -1: + return True + return False + + +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"] + defvals = cfg.get("mount_default_fields", defvals) + + # these are our default set of mounts + defmnts = [["ephemeral0", "/mnt", "auto", defvals[3], "0", "2"], + ["swap", "none", "swap", "sw", "0", "0"]] + + cfgmnt = [] + if "mounts" in cfg: + cfgmnt = cfg["mounts"] + + + for i in range(len(cfgmnt)): + # skip something that wasn't a list + if not isinstance(cfgmnt[i], list): + log.warn("Mount option %s not a list, got a %s instead", + (i + 1), util.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 + + # in case the user did not quote a field (likely fs-freq, fs_passno) + # but do not convert None to 'None' (LP: #898365) + for j in range(len(cfgmnt[i])): + if j is None: + continue + else: + cfgmnt[i][j] = str(cfgmnt[i][j]) + + for i in range(len(cfgmnt)): + # fill in values with defaults from defvals above + for j in range(len(defvals)): + if len(cfgmnt[i]) <= j: + cfgmnt[i].append(defvals[j]) + elif cfgmnt[i][j] is None: + cfgmnt[i][j] = defvals[j] + + # if the second entry in the list is 'None' this + # clears all previous entries of that same 'fs_spec' + # (fs_spec is the first field in /etc/fstab, ie, that device) + if cfgmnt[i][1] is None: + for j in range(i): + if cfgmnt[j][0] == cfgmnt[i][0]: + cfgmnt[j][1] = None + + # 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) + continue + if devname.startswith("/"): + defmnt[0] = devname + else: + defmnt[0] = "/dev/%s" % devname + + log.debug("Mapped default device %s to %s", startname, defmnt[0]) + + cfgmnt_has = False + for cfgm in cfgmnt: + if cfgm[0] == defmnt[0]: + cfgmnt_has = True + break + + if cfgmnt_has: + log.debug(("Not including %s, already" + " previously included"), startname) + continue + cfgmnt.append(defmnt) + + # now, each entry in the cfgmnt list has all fstab values + # if the second field is None (not the string, the value) we skip it + actlist = [] + for x in cfgmnt: + if x[1] is None: + log.debug("Skipping non-existent device named %s", x[0]) + else: + actlist.append(x) + + if len(actlist) == 0: + log.debug("No modifications to fstab needed.") + return + + comment = "comment=cloudconfig" + cc_lines = [] + needswap = False + dirs = [] + for line in actlist: + # write 'comment' in the fs_mntops, entry, claiming this + line[3] = "%s,%s" % (line[3], comment) + if line[2] == "swap": + needswap = True + if line[1].startswith("/"): + dirs.append(line[1]) + cc_lines.append('\t'.join(line)) + + fstab_lines = [] + fstab = util.load_file(cloud.paths.join(True, "/etc/fstab")) + for line in fstab.splitlines(): + try: + toks = ws.split(line) + if toks[3].find(comment) != -1: + continue + except: + pass + fstab_lines.append(line) + + fstab_lines.extend(cc_lines) + contents = "%s\n" % ('\n'.join(fstab_lines)) + util.write_file(cloud.paths.join(False, "/etc/fstab"), contents) + + if needswap: + try: + util.subp(("swapon", "-a")) + except: + util.logexc(log, "Activating swap via 'swapon -a' failed") + + for d in dirs: + try: + util.ensure_dir(cloud.paths.join(False, d)) + except: + util.logexc(log, "Failed to make '%s' config-mount", d) + + try: + util.subp(("mount", "-a")) + except: + util.logexc(log, "Activating mounts via 'mount -a' failed") diff --git a/cloudinit/config/cc_phone_home.py b/cloudinit/config/cc_phone_home.py new file mode 100644 index 00000000..a8752527 --- /dev/null +++ b/cloudinit/config/cc_phone_home.py @@ -0,0 +1,111 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import templater +from cloudinit import url_helper as uhelp +from cloudinit import util + +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + +post_list_all = ['pub_key_dsa', 'pub_key_rsa', 'pub_key_ecdsa', + 'instance_id', 'hostname'] + + +# phone_home: +# url: http://my.foo.bar/$INSTANCE/ +# post: all +# tries: 10 +# +# phone_home: +# url: http://my.foo.bar/$INSTANCE_ID/ +# post: [ pub_key_dsa, pub_key_rsa, pub_key_ecdsa, instance_id +# +def handle(name, cfg, cloud, log, args): + if len(args) != 0: + ph_cfg = util.read_conf(args[0]) + else: + if not 'phone_home' in cfg: + return + ph_cfg = cfg['phone_home'] + + if 'url' not in ph_cfg: + log.warn(("Skipping transform named %s, " + "no 'url' found in 'phone_home' configuration"), name) + return + + url = ph_cfg['url'] + post_list = ph_cfg.get('post', 'all') + tries = ph_cfg.get('tries', 10) + try: + tries = int(tries) + except: + tries = 10 + util.logexc(log, ("Configuration entry 'tries'" + " is not an integer, using %s instead"), tries) + + if post_list == "all": + post_list = post_list_all + + all_keys = {} + all_keys['instance_id'] = cloud.get_instance_id() + all_keys['hostname'] = cloud.get_hostname() + + pubkeys = { + 'pub_key_dsa': '/etc/ssh/ssh_host_dsa_key.pub', + 'pub_key_rsa': '/etc/ssh/ssh_host_rsa_key.pub', + 'pub_key_ecdsa': '/etc/ssh/ssh_host_ecdsa_key.pub', + } + + for (n, path) in pubkeys.iteritems(): + try: + all_keys[n] = util.load_file(cloud.paths.join(True, path)) + except: + util.logexc(log, ("%s: failed to open, can not" + " phone home that data"), path) + + submit_keys = {} + for k in post_list: + if k in all_keys: + submit_keys[k] = all_keys[k] + else: + submit_keys[k] = None + log.warn(("Requested key %s from 'post'" + " configuration list not available"), k) + + # Get them read to be posted + real_submit_keys = {} + for (k, v) in submit_keys.iteritems(): + if v is None: + real_submit_keys[k] = 'N/A' + else: + real_submit_keys[k] = str(v) + + # Incase the url is parameterized + url_params = { + 'INSTANCE_ID': all_keys['instance_id'], + } + url = templater.render_string(url, url_params) + try: + uhelp.readurl(url, data=real_submit_keys, retries=tries, sec_between=3) + except: + util.logexc(log, ("Failed to post phone home data to" + " %s in %s tries"), url, tries) diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py new file mode 100644 index 00000000..1690094a --- /dev/null +++ b/cloudinit/config/cc_resizefs.py @@ -0,0 +1,140 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os +import stat +import time + +from cloudinit import util +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + +resize_fs_prefixes_cmds = [ + ('ext', 'resize2fs'), + ('xfs', 'xfs_growfs'), +] + + +def nodeify_path(devpth, where, log): + try: + st_dev = os.stat(where).st_dev + dev = os.makedev(os.major(st_dev), os.minor(st_dev)) + os.mknod(devpth, 0400 | stat.S_IFBLK, dev) + return st_dev + except: + if util.is_container(): + log.debug("Inside container, ignoring mknod failure in resizefs") + return + log.warn("Failed to make device node to resize %s at %s", + where, devpth) + raise + + +def get_fs_type(st_dev, path, log): + try: + dev_entries = util.find_devs_with(tag='TYPE', oformat='value', + no_cache=True, path=path) + if not dev_entries: + return None + return dev_entries[0].strip() + except util.ProcessExecutionError: + util.logexc(log, ("Failed to get filesystem type" + " of maj=%s, min=%s for path %s"), + os.major(st_dev), os.minor(st_dev), path) + raise + + +def handle(name, cfg, cloud, log, args): + if len(args) != 0: + resize_root = args[0] + else: + resize_root = util.get_cfg_option_str(cfg, "resize_rootfs", True) + + if not util.translate_bool(resize_root): + log.debug("Skipping transform named %s, resizing disabled", name) + return + + # TODO is the directory ok to be used?? + resize_root_d = util.get_cfg_option_str(cfg, "resize_rootfs_tmp", "/run") + resize_root_d = cloud.paths.join(False, resize_root_d) + util.ensure_dir(resize_root_d) + + # TODO: allow what is to be resized to be configurable?? + resize_what = cloud.paths.join(False, "/") + with util.SilentTemporaryFile(prefix="cloudinit.resizefs.", + dir=resize_root_d, delete=True) as tfh: + devpth = tfh.name + + # Delete the file so that mknod will work + # but don't change the file handle to know that its + # removed so that when a later call that recreates + # occurs this temporary file will still benefit from + # auto deletion + tfh.unlink_now() + + st_dev = nodeify_path(devpth, resize_what, log) + fs_type = get_fs_type(st_dev, devpth, log) + if not fs_type: + log.warn("Could not determine filesystem type of %s", resize_what) + return + + resizer = None + fstype_lc = fs_type.lower() + for (pfix, root_cmd) in resize_fs_prefixes_cmds: + if fstype_lc.startswith(pfix): + resizer = root_cmd + break + + if not resizer: + log.warn("Not resizing unknown filesystem type %s for %s", + fs_type, resize_what) + return + + log.debug("Resizing %s (%s) using %s", resize_what, fs_type, resizer) + resize_cmd = [resizer, devpth] + + if resize_root == "noblock": + # Fork to a child that will run + # the resize command + util.fork_cb(do_resize, resize_cmd, log) + # Don't delete the file now in the parent + tfh.delete = False + else: + do_resize(resize_cmd, log) + + action = 'Resized' + if resize_root == "noblock": + action = 'Resizing (via forking)' + log.debug("%s root filesystem (type=%s, maj=%i, min=%i, val=%s)", + action, fs_type, os.major(st_dev), os.minor(st_dev), resize_root) + + +def do_resize(resize_cmd, log): + start = time.time() + try: + util.subp(resize_cmd) + except util.ProcessExecutionError: + util.logexc(log, "Failed to resize filesystem (cmd=%s)", resize_cmd) + raise + tot_time = int(time.time() - start) + log.debug("Resizing took %s seconds", tot_time) + # TODO: Should we add a fsck check after this to make + # sure we didn't corrupt anything? diff --git a/cloudinit/config/cc_rightscale_userdata.py b/cloudinit/config/cc_rightscale_userdata.py new file mode 100644 index 00000000..8385e281 --- /dev/null +++ b/cloudinit/config/cc_rightscale_userdata.py @@ -0,0 +1,102 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +## +## The purpose of this script is to allow cloud-init to consume +## rightscale style userdata. rightscale user data is key-value pairs +## in a url-query-string like format. +## +## for cloud-init support, there will be a key named +## 'CLOUD_INIT_REMOTE_HOOK'. +## +## This cloud-config module will +## - read the blob of data from raw user data, and parse it as key/value +## - for each key that is found, download the content to +## the local instance/scripts directory and set them executable. +## - the files in that directory will be run by the user-scripts module +## Therefore, this must run before that. +## +## + +import os + +from cloudinit import url_helper as uhelp +from cloudinit import util +from cloudinit.settings import PER_INSTANCE + +from urlparse import parse_qs + +frequency = PER_INSTANCE + +MY_NAME = "cc_rightscale_userdata" +MY_HOOKNAME = 'CLOUD_INIT_REMOTE_HOOK' + + +def handle(name, _cfg, cloud, log, _args): + try: + ud = cloud.get_userdata_raw() + except: + log.warn("Failed to get raw userdata in transform %s", name) + return + + try: + mdict = parse_qs(ud) + if not mdict or not MY_HOOKNAME in mdict: + log.debug(("Skipping transform %s, " + "did not find %s in parsed" + " raw userdata"), name, MY_HOOKNAME) + return + except: + util.logexc(log, ("Failed to parse query string %s" + " into a dictionary"), ud) + raise + + wrote_fns = [] + captured_excps = [] + + # These will eventually be then ran by the cc_scripts_user + # TODO: maybe this should just be a new user data handler?? + # Instead of a late transform that acts like a user data handler? + scripts_d = cloud.get_ipath_cur('scripts') + urls = mdict[MY_HOOKNAME] + for (i, url) in enumerate(urls): + fname = os.path.join(scripts_d, "rightscale-%02i" % (i)) + try: + resp = uhelp.readurl(url) + # Ensure its a valid http response (and something gotten) + if resp.ok() and resp.contents: + util.write_file(fname, str(resp), mode=0700) + wrote_fns.append(fname) + except Exception as e: + captured_excps.append(e) + util.logexc(log, "%s failed to read %s and write %s", + MY_NAME, url, fname) + + if wrote_fns: + log.debug("Wrote out rightscale userdata to %s files", len(wrote_fns)) + + if len(wrote_fns) != len(urls): + skipped = len(urls) - len(wrote_fns) + log.debug("%s urls were skipped or failed", skipped) + + if captured_excps: + log.warn("%s failed with exceptions, re-raising the last one", + len(captured_excps)) + raise captured_excps[-1] diff --git a/cloudinit/config/cc_rsyslog.py b/cloudinit/config/cc_rsyslog.py new file mode 100644 index 00000000..f2c1de1e --- /dev/null +++ b/cloudinit/config/cc_rsyslog.py @@ -0,0 +1,102 @@ +# vi: ts=4 expandtab syntax=python +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +DEF_FILENAME = "20-cloud-config.conf" +DEF_DIR = "/etc/rsyslog.d" + + +def handle(name, cfg, cloud, log, _args): + # rsyslog: + # - "*.* @@192.158.1.1" + # - content: "*.* @@192.0.2.1:10514" + # - filename: 01-examplecom.conf + # content: | + # *.* @@syslogd.example.com + + # process 'rsyslog' + if not 'rsyslog' in cfg: + log.debug(("Skipping transform named %s," + " no 'rsyslog' key in configuration"), name) + return + + def_dir = cfg.get('rsyslog_dir', DEF_DIR) + def_fname = cfg.get('rsyslog_filename', DEF_FILENAME) + + files = [] + for i, ent in enumerate(cfg['rsyslog']): + if isinstance(ent, dict): + if not "content" in ent: + log.warn("No 'content' entry in config entry %s", i + 1) + continue + content = ent['content'] + filename = ent.get("filename", def_fname) + else: + content = ent + filename = def_fname + + filename = filename.strip() + if not filename: + log.warn("Entry %s has an empty filename", i + 1) + continue + + if not filename.startswith("/"): + filename = os.path.join(def_dir, filename) + + # Truncate filename first time you see it + omode = "ab" + if filename not in files: + omode = "wb" + files.append(filename) + + try: + contents = "%s\n" % (content) + util.write_file(cloud.paths.join(False, filename), + contents, omode=omode) + except Exception: + util.logexc(log, "Failed to write to %s", filename) + + # Attempt to restart syslogd + restarted = False + try: + # If this config module is running at cloud-init time + # (before rsyslog is running) we don't actually have to + # restart syslog. + # + # Upstart actually does what we want here, in that it doesn't + # start a service that wasn't running already on 'restart' + # it will also return failure on the attempt, so 'restarted' + # won't get set. + log.debug("Restarting rsyslog") + util.subp(['service', 'rsyslog', 'restart']) + restarted = True + except Exception: + util.logexc(log, "Failed restarting rsyslog") + + if restarted: + # This only needs to run if we *actually* restarted + # syslog above. + cloud.cycle_logging() + # This should now use rsyslog if + # the logging was setup to use it... + log.debug("%s configured %s files", name, files) diff --git a/cloudinit/config/cc_runcmd.py b/cloudinit/config/cc_runcmd.py new file mode 100644 index 00000000..f121484b --- /dev/null +++ b/cloudinit/config/cc_runcmd.py @@ -0,0 +1,38 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + + +def handle(name, cfg, cloud, log, _args): + if "runcmd" not in cfg: + log.debug(("Skipping transform named %s," + " no 'runcmd' key in configuration"), name) + return + + out_fn = os.path.join(cloud.get_ipath('scripts'), "runcmd") + cmd = cfg["runcmd"] + try: + content = util.shellify(cmd) + util.write_file(cloud.paths.join(False, out_fn), content, 0700) + except: + util.logexc(log, "Failed to shellify %s into file %s", cmd, out_fn) diff --git a/cloudinit/config/cc_salt_minion.py b/cloudinit/config/cc_salt_minion.py new file mode 100644 index 00000000..16f5286d --- /dev/null +++ b/cloudinit/config/cc_salt_minion.py @@ -0,0 +1,60 @@ +# vi: ts=4 expandtab +# +# Author: Jeff Bauer +# +# 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 . + +import os + +from cloudinit import util + +# Note: see http://saltstack.org/topics/installation/ + + +def handle(name, cfg, cloud, log, _args): + # If there isn't a salt key in the configuration don't do anything + if 'salt_minion' not in cfg: + log.debug(("Skipping transform named %s," + " no 'salt_minion' key in configuration"), name) + return + + salt_cfg = cfg['salt_minion'] + + # Start by installing the salt package ... + cloud.distro.install_packages(["salt"]) + + # Ensure we can configure files at the right dir + config_dir = salt_cfg.get("config_dir", '/etc/salt') + config_dir = cloud.paths.join(False, config_dir) + util.ensure_dir(config_dir) + + # ... and then update the salt configuration + if 'conf' in salt_cfg: + # Add all sections from the conf object to /etc/salt/minion + minion_config = os.path.join(config_dir, 'minion') + minion_data = util.yaml_dumps(salt_cfg.get('conf')) + util.write_file(minion_config, minion_data) + + # ... copy the key pair if specified + if 'public_key' in salt_cfg and 'private_key' in salt_cfg: + pki_dir = salt_cfg.get('pki_dir', '/etc/salt/pki') + pki_dir = cloud.paths.join(pki_dir) + with util.umask(077): + util.ensure_dir(pki_dir) + pub_name = os.path.join(pki_dir, 'minion.pub') + pem_name = os.path.join(pki_dir, 'minion.pem') + util.write_file(pub_name, salt_cfg['public_key']) + util.write_file(pem_name, salt_cfg['private_key']) + + # Start salt-minion + util.subp(['service', 'salt-minion', 'start'], capture=False) diff --git a/cloudinit/config/cc_scripts_per_boot.py b/cloudinit/config/cc_scripts_per_boot.py new file mode 100644 index 00000000..364e1d02 --- /dev/null +++ b/cloudinit/config/cc_scripts_per_boot.py @@ -0,0 +1,41 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + +script_subdir = 'per-boot' + + +def handle(name, _cfg, cloud, log, _args): + # Comes from the following: + # https://forums.aws.amazon.com/thread.jspa?threadID=96918 + runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) + try: + util.runparts(runparts_path) + except: + log.warn("Failed to run transform %s (%s in %s)", + name, script_subdir, runparts_path) + raise diff --git a/cloudinit/config/cc_scripts_per_instance.py b/cloudinit/config/cc_scripts_per_instance.py new file mode 100644 index 00000000..d75ab47d --- /dev/null +++ b/cloudinit/config/cc_scripts_per_instance.py @@ -0,0 +1,41 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + +script_subdir = 'per-instance' + + +def handle(name, _cfg, cloud, log, _args): + # Comes from the following: + # https://forums.aws.amazon.com/thread.jspa?threadID=96918 + runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) + try: + util.runparts(runparts_path) + except: + log.warn("Failed to run transform %s (%s in %s)", + name, script_subdir, runparts_path) + raise diff --git a/cloudinit/config/cc_scripts_per_once.py b/cloudinit/config/cc_scripts_per_once.py new file mode 100644 index 00000000..80f8c325 --- /dev/null +++ b/cloudinit/config/cc_scripts_per_once.py @@ -0,0 +1,41 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +from cloudinit.settings import PER_ONCE + +frequency = PER_ONCE + +script_subdir = 'per-once' + + +def handle(name, _cfg, cloud, log, _args): + # Comes from the following: + # https://forums.aws.amazon.com/thread.jspa?threadID=96918 + runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) + try: + util.runparts(runparts_path) + except: + log.warn("Failed to run transform %s (%s in %s)", + name, script_subdir, runparts_path) + raise diff --git a/cloudinit/config/cc_scripts_user.py b/cloudinit/config/cc_scripts_user.py new file mode 100644 index 00000000..f4fe3a2a --- /dev/null +++ b/cloudinit/config/cc_scripts_user.py @@ -0,0 +1,42 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util + +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + +script_subdir = 'scripts' + + +def handle(name, _cfg, cloud, log, _args): + # This is written to by the user data handlers + # Ie, any custom shell scripts that come down + # go here... + runparts_path = os.path.join(cloud.get_ipath_cur(), script_subdir) + try: + util.runparts(runparts_path) + except: + log.warn("Failed to run transform %s (%s in %s)", + name, script_subdir, runparts_path) + raise diff --git a/cloudinit/config/cc_set_hostname.py b/cloudinit/config/cc_set_hostname.py new file mode 100644 index 00000000..3ac8a8fa --- /dev/null +++ b/cloudinit/config/cc_set_hostname.py @@ -0,0 +1,35 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util + + +def handle(name, cfg, cloud, log, _args): + if util.get_cfg_option_bool(cfg, "preserve_hostname", False): + log.debug(("Configuration option 'preserve_hostname' is set," + " not setting the hostname in transform %s"), name) + return + + (hostname, _fqdn) = util.get_hostname_fqdn(cfg, cloud) + try: + log.debug("Setting hostname to %s", hostname) + cloud.distro.set_hostname(hostname) + except Exception: + util.logexc(log, "Failed to set hostname to %s", hostname) diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py new file mode 100644 index 00000000..e7049f22 --- /dev/null +++ b/cloudinit/config/cc_set_passwords.py @@ -0,0 +1,151 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import sys + +from cloudinit import util + +from string import letters, digits # pylint: disable=W0402 + +# We are removing certain 'painful' letters/numbers +pw_set = (letters.translate(None, 'loLOI') + + digits.translate(None, '01')) + + +def handle(_name, cfg, cloud, log, args): + if len(args) != 0: + # if run from command line, and give args, wipe the chpasswd['list'] + password = args[0] + if 'chpasswd' in cfg and 'list' in cfg['chpasswd']: + del cfg['chpasswd']['list'] + else: + password = util.get_cfg_option_str(cfg, "password", None) + + expire = True + pw_auth = "no" + change_pwauth = False + plist = None + + if 'chpasswd' in cfg: + chfg = cfg['chpasswd'] + plist = util.get_cfg_option_str(chfg, 'list', plist) + expire = util.get_cfg_option_bool(chfg, 'expire', expire) + + if not plist and password: + user = util.get_cfg_option_str(cfg, "user", "ubuntu") + plist = "%s:%s" % (user, password) + + errors = [] + if plist: + plist_in = [] + randlist = [] + users = [] + for line in plist.splitlines(): + u, p = line.split(':', 1) + if p == "R" or p == "RANDOM": + p = rand_user_password() + randlist.append("%s:%s" % (u, p)) + plist_in.append("%s:%s" % (u, p)) + users.append(u) + + ch_in = '\n'.join(plist_in) + try: + log.debug("Changing password for %s:", users) + util.subp(['chpasswd'], ch_in) + except Exception as e: + errors.append(e) + util.logexc(log, + "Failed to set passwords with chpasswd for %s", users) + + if len(randlist): + blurb = ("Set the following 'random' passwords\n", + '\n'.join(randlist)) + sys.stderr.write("%s\n%s\n" % blurb) + + if expire: + expired_users = [] + for u in users: + try: + util.subp(['passwd', '--expire', u]) + expired_users.append(u) + except Exception as e: + errors.append(e) + util.logexc(log, "Failed to set 'expire' for %s", u) + if expired_users: + log.debug("Expired passwords for: %s users", expired_users) + + change_pwauth = False + pw_auth = None + if 'ssh_pwauth' in cfg: + change_pwauth = True + if util.is_true_str(cfg['ssh_pwauth']): + pw_auth = 'yes' + if util.is_false_str(cfg['ssh_pwauth']): + pw_auth = 'no' + + if change_pwauth: + new_lines = [] + replaced_auth = False + replacement = "PasswordAuthentication %s" % (pw_auth) + + # See http://linux.die.net/man/5/sshd_config + old_lines = util.load_file('/etc/ssh/sshd_config').splitlines() + for i, line in enumerate(old_lines): + if not line.strip() or line.startswith("#"): + new_lines.append(line) + continue + splitup = line.split(None, 1) + if len(splitup) <= 1: + new_lines.append(line) + continue + (cmd, args) = splitup + # Keywords are case-insensitive and arguments are case-sensitive + cmd = cmd.lower().strip() + if cmd == 'passwordauthentication': + log.debug("Replacing auth line %s with %s", i + 1, replacement) + replaced_auth = True + new_lines.append(replacement) + else: + new_lines.append(line) + + if not replaced_auth: + log.debug("Adding new auth line %s", replacement) + replaced_auth = True + new_lines.append(replacement) + + util.write_file(cloud.paths.join(False, '/etc/ssh/sshd_config'), + "\n".join(new_lines)) + + try: + cmd = ['service'] + cmd.append(cloud.distro.get_option('ssh_svcname', 'ssh')) + cmd.append('restart') + util.subp(cmd) + log.debug("Restarted the ssh daemon") + except: + util.logexc(log, "Restarting of the ssh daemon failed") + + if len(errors): + log.debug("%s errors occured, re-raising the last one", len(errors)) + raise errors[-1] + + +def rand_user_password(pwlen=9): + return util.rand_str(pwlen, select_from=pw_set) diff --git a/cloudinit/config/cc_ssh.py b/cloudinit/config/cc_ssh.py new file mode 100644 index 00000000..e5e99560 --- /dev/null +++ b/cloudinit/config/cc_ssh.py @@ -0,0 +1,131 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os +import glob + +from cloudinit import util +from cloudinit import ssh_util + +DISABLE_ROOT_OPTS = ( "no-port-forwarding,no-agent-forwarding," +"no-X11-forwarding,command=\"echo \'Please login as the user \\\"$USER\\\" " +"rather than the user \\\"root\\\".\';echo;sleep 10\"") + +key2file = { + "rsa_private": ("/etc/ssh/ssh_host_rsa_key", 0600), + "rsa_public": ("/etc/ssh/ssh_host_rsa_key.pub", 0644), + "dsa_private": ("/etc/ssh/ssh_host_dsa_key", 0600), + "dsa_public": ("/etc/ssh/ssh_host_dsa_key.pub", 0644), + "ecdsa_private": ("/etc/ssh/ssh_host_ecdsa_key", 0600), + "ecdsa_public": ("/etc/ssh/ssh_host_ecdsa_key.pub", 0644), +} + +priv2pub = { + 'rsa_private': 'rsa_public', + 'dsa_private': 'dsa_public', + 'ecdsa_private': 'ecdsa_public', +} + +key_gen_tpl = 'o=$(ssh-keygen -yf "%s") && echo "$o" root@localhost > "%s"' + +generate_keys = ['rsa', 'dsa', 'ecdsa'] + + +def handle(_name, cfg, cloud, log, _args): + + # remove the static keys from the pristine image + if cfg.get("ssh_deletekeys", True): + key_pth = cloud.paths.join(False, "/etc/ssh/", "ssh_host_*key*") + for f in glob.glob(key_pth): + try: + util.del_file(f) + except: + util.logexc(log, "Failed deleting key file %s", f) + + if "ssh_keys" in cfg: + # if there are keys in cloud-config, use them + for (key, val) in cfg["ssh_keys"].iteritems(): + if key in key2file: + tgt_fn = key2file[key][0] + tgt_perms = key2file[key][1] + util.write_file(cloud.paths.join(False, tgt_fn), + val, tgt_perms) + + for (priv, pub) in priv2pub.iteritems(): + if pub in cfg['ssh_keys'] or not priv in cfg['ssh_keys']: + continue + pair = (key2file[priv][0], key2file[pub][0]) + cmd = ['sh', '-xc', key_gen_tpl % pair] + try: + # TODO: Is this guard needed? + with util.SeLinuxGuard("/etc/ssh", recursive=True): + util.subp(cmd, capture=False) + log.debug("Generated a key for %s from %s", pair[0], pair[1]) + except: + util.logexc(log, ("Failed generated a key" + " for %s from %s"), pair[0], pair[1]) + else: + # if not, generate them + genkeys = util.get_cfg_option_list(cfg, + 'ssh_genkeytypes', + generate_keys) + for keytype in genkeys: + keyfile = '/etc/ssh/ssh_host_%s_key' % (keytype) + keyfile = cloud.paths.join(False, keyfile) + util.ensure_dir(os.path.dirname(keyfile)) + if not os.path.exists(keyfile): + cmd = ['ssh-keygen', '-t', keytype, '-N', '', '-f', keyfile] + try: + # TODO: Is this guard needed? + with util.SeLinuxGuard("/etc/ssh", recursive=True): + util.subp(cmd, capture=False) + except: + util.logexc(log, ("Failed generating key type" + " %s to file %s"), keytype, keyfile) + + try: + user = util.get_cfg_option_str(cfg, 'user') + disable_root = util.get_cfg_option_bool(cfg, "disable_root", True) + disable_root_opts = util.get_cfg_option_str(cfg, "disable_root_opts", + DISABLE_ROOT_OPTS) + + keys = cloud.get_public_ssh_keys() or [] + if "ssh_authorized_keys" in cfg: + cfgkeys = cfg["ssh_authorized_keys"] + keys.extend(cfgkeys) + + apply_credentials(keys, user, cloud.paths, + disable_root, disable_root_opts) + except: + util.logexc(log, "Applying ssh credentials failed!") + + +def apply_credentials(keys, user, paths, disable_root, disable_root_opts): + + keys = set(keys) + if user: + ssh_util.setup_user_keys(keys, user, '', paths) + + if disable_root and user: + key_prefix = disable_root_opts.replace('$USER', user) + else: + key_prefix = '' + + ssh_util.setup_user_keys(keys, 'root', key_prefix, paths) diff --git a/cloudinit/config/cc_ssh_import_id.py b/cloudinit/config/cc_ssh_import_id.py new file mode 100644 index 00000000..d57e4665 --- /dev/null +++ b/cloudinit/config/cc_ssh_import_id.py @@ -0,0 +1,53 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util + +# The ssh-import-id only seems to exist on ubuntu (for now) +# https://launchpad.net/ssh-import-id +distros = ['ubuntu'] + + +def handle(name, cfg, _cloud, log, args): + if len(args) != 0: + user = args[0] + ids = [] + if len(args) > 1: + ids = args[1:] + else: + user = util.get_cfg_option_str(cfg, "user", "ubuntu") + ids = util.get_cfg_option_list(cfg, "ssh_import_id", []) + + if len(ids) == 0: + log.debug("Skipping transform named %s, no ids found to import", name) + return + + if not user: + log.debug("Skipping transform named %s, no user found to import", name) + return + + cmd = ["sudo", "-Hu", user, "ssh-import-id"] + ids + log.debug("Importing ssh ids for user %s.", user) + + try: + util.subp(cmd, capture=False) + except util.ProcessExecutionError as e: + util.logexc(log, "Failed to run command to import %s ssh ids", user) + raise e diff --git a/cloudinit/config/cc_timezone.py b/cloudinit/config/cc_timezone.py new file mode 100644 index 00000000..747c436c --- /dev/null +++ b/cloudinit/config/cc_timezone.py @@ -0,0 +1,39 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util + +from cloudinit.settings import PER_INSTANCE + +frequency = PER_INSTANCE + + +def handle(name, cfg, cloud, log, args): + if len(args) != 0: + timezone = args[0] + else: + timezone = util.get_cfg_option_str(cfg, "timezone", False) + + if not timezone: + log.debug("Skipping transform named %s, no 'timezone' specified", name) + return + + # Let the distro handle settings its timezone + cloud.distro.set_timezone(timezone) diff --git a/cloudinit/config/cc_update_etc_hosts.py b/cloudinit/config/cc_update_etc_hosts.py new file mode 100644 index 00000000..75615db1 --- /dev/null +++ b/cloudinit/config/cc_update_etc_hosts.py @@ -0,0 +1,60 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +from cloudinit import util +from cloudinit import templater + +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + + +def handle(name, cfg, cloud, log, _args): + manage_hosts = util.get_cfg_option_str(cfg, "manage_etc_hosts", False) + if util.translate_bool(manage_hosts, addons=['template']): + (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) + if not hostname: + log.warn(("Option 'manage_etc_hosts' was set," + " but no hostname was found")) + return + + # Render from a template file + distro_n = cloud.distro.name + tpl_fn_name = cloud.get_template_filename("hosts.%s" % (distro_n)) + if not tpl_fn_name: + raise Exception(("No hosts template could be" + " found for distro %s") % (distro_n)) + + out_fn = cloud.paths.join(False, '/etc/hosts') + templater.render_to_file(tpl_fn_name, out_fn, + {'hostname': hostname, 'fqdn': fqdn}) + + elif manage_hosts == "localhost": + (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) + if not hostname: + log.warn(("Option 'manage_etc_hosts' was set," + " but no hostname was found")) + return + + log.debug("Managing localhost in /etc/hosts") + cloud.distro.update_etc_hosts(hostname, fqdn) + else: + log.debug(("Configuration option 'manage_etc_hosts' is not set," + " not managing /etc/hosts in transform %s"), name) diff --git a/cloudinit/config/cc_update_hostname.py b/cloudinit/config/cc_update_hostname.py new file mode 100644 index 00000000..58444fab --- /dev/null +++ b/cloudinit/config/cc_update_hostname.py @@ -0,0 +1,41 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# +# 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 . + +import os + +from cloudinit import util +from cloudinit.settings import PER_ALWAYS + +frequency = PER_ALWAYS + + +def handle(name, cfg, cloud, log, _args): + if util.get_cfg_option_bool(cfg, "preserve_hostname", False): + log.debug(("Configuration option 'preserve_hostname' is set," + " not updating the hostname in transform %s"), name) + return + + (hostname, _fqdn) = util.get_hostname_fqdn(cfg, cloud) + try: + prev_fn = os.path.join(cloud.get_cpath('data'), "previous-hostname") + cloud.distro.update_hostname(hostname, prev_fn) + except Exception: + util.logexc(log, "Failed to set the hostname to %s", hostname) + raise diff --git a/cloudinit/config/chef.py b/cloudinit/config/chef.py deleted file mode 100644 index 4e8ef346..00000000 --- a/cloudinit/config/chef.py +++ /dev/null @@ -1,128 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Avishai Ish-Shalom -# Author: Mike Moulton -# Author: Juerg Haefliger -# -# 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 . - -import json -import os - -from cloudinit import templater -from cloudinit import util - -ruby_version_default = "1.8" - - -def handle(name, cfg, cloud, log, _args): - - # If there isn't a chef key in the configuration don't do anything - if 'chef' not in cfg: - log.debug(("Skipping transform named %s," - " no 'chef' key in configuration"), name) - return - chef_cfg = cfg['chef'] - - # Ensure the chef directories we use exist - c_dirs = [ - '/etc/chef', - '/var/log/chef', - '/var/lib/chef', - '/var/cache/chef', - '/var/backups/chef', - '/var/run/chef', - ] - for d in c_dirs: - util.ensure_dir(cloud.paths.join(False, d)) - - # Set the validation key based on the presence of either 'validation_key' - # or 'validation_cert'. In the case where both exist, 'validation_key' - # takes precedence - for key in ('validation_key', 'validation_cert'): - if key in chef_cfg and chef_cfg[key]: - v_fn = cloud.paths.join(False, '/etc/chef/validation.pem') - util.write_file(v_fn, chef_cfg[key]) - break - - # Create the chef config from template - template_fn = cloud.get_template_filename('chef_client.rb') - if template_fn: - iid = str(cloud.datasource.get_instance_id()) - params = { - 'server_url': chef_cfg['server_url'], - 'node_name': util.get_cfg_option_str(chef_cfg, 'node_name', iid), - 'environment': util.get_cfg_option_str(chef_cfg, 'environment', - '_default'), - 'validation_name': chef_cfg['validation_name'] - } - out_fn = cloud.paths.join(False, '/etc/chef/client.rb') - templater.render_to_file(template_fn, out_fn, params) - else: - log.warn("No template found, not rendering to /etc/chef/client.rb") - - # set the firstboot json - initial_json = {} - if 'run_list' in chef_cfg: - initial_json['run_list'] = chef_cfg['run_list'] - if 'initial_attributes' in chef_cfg: - initial_attributes = chef_cfg['initial_attributes'] - for k in list(initial_attributes.keys()): - initial_json[k] = initial_attributes[k] - firstboot_fn = cloud.paths.join(False, '/etc/chef/firstboot.json') - util.write_file(firstboot_fn, json.dumps(initial_json)) - - # If chef is not installed, we install chef based on 'install_type' - if not os.path.isfile('/usr/bin/chef-client'): - install_type = util.get_cfg_option_str(chef_cfg, 'install_type', - 'packages') - if install_type == "gems": - # this will install and run the chef-client from gems - chef_version = util.get_cfg_option_str(chef_cfg, 'version', None) - ruby_version = util.get_cfg_option_str(chef_cfg, 'ruby_version', - ruby_version_default) - install_chef_from_gems(cloud.distro, ruby_version, chef_version) - # and finally, run chef-client - log.debug('Running chef-client') - util.subp(['/usr/bin/chef-client', '-d', '-i', '1800', '-s', '20']) - elif install_type == 'packages': - # this will install and run the chef-client from packages - cloud.distro.install_packages(('chef',)) - else: - log.warn("Unknown chef install type %s", install_type) - - -def get_ruby_packages(version): - # return a list of packages needed to install ruby at version - pkgs = ['ruby%s' % version, 'ruby%s-dev' % version] - if version == "1.8": - pkgs.extend(('libopenssl-ruby1.8', 'rubygems1.8')) - return pkgs - - -def install_chef_from_gems(ruby_version, chef_version, distro): - distro.install_packages(get_ruby_packages(ruby_version)) - if not os.path.exists('/usr/bin/gem'): - util.sym_link('/usr/bin/gem%s' % ruby_version, '/usr/bin/gem') - if not os.path.exists('/usr/bin/ruby'): - util.sym_link('/usr/bin/ruby%s' % ruby_version, '/usr/bin/ruby') - if chef_version: - util.subp(['/usr/bin/gem', 'install', 'chef', - '-v %s' % chef_version, '--no-ri', - '--no-rdoc', '--bindir', '/usr/bin', '-q']) - else: - util.subp(['/usr/bin/gem', 'install', 'chef', - '--no-ri', '--no-rdoc', '--bindir', - '/usr/bin', '-q']) diff --git a/cloudinit/config/disable_ec2_metadata.py b/cloudinit/config/disable_ec2_metadata.py deleted file mode 100644 index c7d26029..00000000 --- a/cloudinit/config/disable_ec2_metadata.py +++ /dev/null @@ -1,36 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -from cloudinit import util - -from cloudinit.settings import PER_ALWAYS - -frequency = PER_ALWAYS - -reject_cmd = ['route', 'add', '-host', '169.254.169.254', 'reject'] - - -def handle(name, cfg, _cloud, log, _args): - disabled = util.get_cfg_option_bool(cfg, "disable_ec2_metadata", False) - if disabled: - util.subp(reject_cmd) - else: - log.debug(("Skipping transform named %s," - " disabling the ec2 route not enabled"), name) diff --git a/cloudinit/config/final_message.py b/cloudinit/config/final_message.py deleted file mode 100644 index c257b6d0..00000000 --- a/cloudinit/config/final_message.py +++ /dev/null @@ -1,71 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import sys - -from cloudinit import templater -from cloudinit import util -from cloudinit import version - -from cloudinit.settings import PER_ALWAYS - -frequency = PER_ALWAYS - -final_message_def = ("Cloud-init v. {{version}} finished at {{timestamp}}." - " Up {{uptime}} seconds.") - - -def handle(_name, cfg, cloud, log, args): - - msg_in = None - if len(args) != 0: - msg_in = args[0] - else: - msg_in = util.get_cfg_option_str(cfg, "final_message") - - if not msg_in: - template_fn = cloud.get_template_filename('final_message') - if template_fn: - msg_in = util.load_file(template_fn) - - if not msg_in: - msg_in = final_message_def - - uptime = util.uptime() - ts = util.time_rfc2822() - cver = version.version_string() - try: - subs = { - 'uptime': uptime, - 'timestamp': ts, - 'version': cver, - } - # Use stdout, stderr or the logger?? - content = templater.render_string(msg_in, subs) - sys.stderr.write("%s\n" % (content)) - except Exception: - util.logexc(log, "Failed to render final message template") - - boot_fin_fn = cloud.paths.boot_finished - try: - contents = "%s - %s - v. %s\n" % (uptime, ts, cver) - util.write_file(boot_fin_fn, contents) - except: - util.logexc(log, "Failed to write boot finished file %s", boot_fin_fn) diff --git a/cloudinit/config/foo.py b/cloudinit/config/foo.py deleted file mode 100644 index 99135704..00000000 --- a/cloudinit/config/foo.py +++ /dev/null @@ -1,52 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -from cloudinit.settings import PER_INSTANCE - -# Modules are expected to have the following attributes. -# 1. A required 'handle' method which takes the following params. -# a) The name will not be this files name, but instead -# the name specified in configuration (which is the name -# which will be used to find this module). -# b) A configuration object that is the result of the merging -# of cloud configs configuration with legacy configuration -# as well as any datasource provided configuration -# c) A cloud object that can be used to access various -# datasource and paths for the given distro and data provided -# by the various datasource instance types. -# d) A argument list that may or may not be empty to this module. -# Typically those are from module configuration where the module -# is defined with some extra configuration that will eventually -# be translated from yaml into arguments to this module. -# 2. A optional 'frequency' that defines how often this module should be ran. -# Typically one of PER_INSTANCE, PER_ALWAYS, PER_ONCE. If not -# provided PER_INSTANCE will be assumed. -# See settings.py for these constants. -# 3. A optional 'distros' array/set/tuple that defines the known distros -# this module will work with (if not all of them). This is used to write -# a warning out if a module is being ran on a untested distribution for -# informational purposes. If non existent all distros are assumed and -# no warning occurs. - -frequency = PER_INSTANCE - - -def handle(name, _cfg, _cloud, log, _args): - log.debug("Hi from transform %s", name) diff --git a/cloudinit/config/grub_dpkg.py b/cloudinit/config/grub_dpkg.py deleted file mode 100644 index 02f05ce3..00000000 --- a/cloudinit/config/grub_dpkg.py +++ /dev/null @@ -1,67 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os - -from cloudinit import util - -distros = ['ubuntu', 'debian'] - - -def handle(_name, cfg, _cloud, log, _args): - idevs = None - idevs_empty = None - - 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) - - if ((os.path.exists("/dev/sda1") and not os.path.exists("/dev/sda")) or - (os.path.exists("/dev/xvda1") and not os.path.exists("/dev/xvda"))): - if idevs == None: - idevs = "" - if idevs_empty == None: - idevs_empty = "true" - else: - if idevs_empty == None: - idevs_empty = "false" - if idevs == None: - idevs = "/dev/sda" - for dev in ("/dev/sda", "/dev/vda", "/dev/sda1", "/dev/vda1"): - if os.path.exists(dev): - idevs = dev - break - - # now idevs and idevs_empty are set to determined values - # or, those set by user - - dconf_sel = (("grub-pc grub-pc/install_devices string %s\n" - "grub-pc grub-pc/install_devices_empty boolean %s\n") % - (idevs, idevs_empty)) - - log.debug("Setting grub debconf-set-selections with '%s','%s'" % - (idevs, idevs_empty)) - - try: - util.subp(['debconf-set-selections'], dconf_sel) - except: - util.logexc(log, "Failed to run debconf-set-selections for grub-dpkg") diff --git a/cloudinit/config/keys_to_console.py b/cloudinit/config/keys_to_console.py deleted file mode 100644 index 40758198..00000000 --- a/cloudinit/config/keys_to_console.py +++ /dev/null @@ -1,52 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os - -from cloudinit.settings import PER_INSTANCE -from cloudinit import util - -frequency = PER_INSTANCE - -# This is a tool that cloud init provides -helper_tool = '/usr/lib/cloud-init/write-ssh-key-fingerprints' - - -def handle(name, cfg, cloud, log, _args): - if not os.path.exists(helper_tool): - log.warn(("Unable to activate transform %s," - " helper tool not found at %s"), name, helper_tool) - return - - fp_blacklist = util.get_cfg_option_list(cfg, - "ssh_fp_console_blacklist", []) - key_blacklist = util.get_cfg_option_list(cfg, - "ssh_key_console_blacklist", - ["ssh-dss"]) - - try: - cmd = [helper_tool] - cmd.append(','.join(fp_blacklist)) - cmd.append(','.join(key_blacklist)) - (stdout, _stderr) = util.subp(cmd) - util.write_file(cloud.paths.join(False, '/dev/console'), stdout) - except: - log.warn("Writing keys to /dev/console failed!") - raise diff --git a/cloudinit/config/landscape.py b/cloudinit/config/landscape.py deleted file mode 100644 index 29ce41b9..00000000 --- a/cloudinit/config/landscape.py +++ /dev/null @@ -1,97 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os - -from StringIO import StringIO - -try: - from configobj import ConfigObj -except ImportError: - ConfigObj = None - -from cloudinit import util - -from cloudinit.settings import PER_INSTANCE - -frequency = PER_INSTANCE - -LSC_CLIENT_CFG_FILE = "/etc/landscape/client.conf" - -distros = ['ubuntu'] - -# defaults taken from stock client.conf in landscape-client 11.07.1.1-0ubuntu2 -LSC_BUILTIN_CFG = { - 'client': { - 'log_level': "info", - 'url': "https://landscape.canonical.com/message-system", - 'ping_url': "http://landscape.canonical.com/ping", - 'data_path': "/var/lib/landscape/client", - } -} - - -def handle(name, cfg, cloud, log, _args): - """ - Basically turn a top level 'landscape' entry with a 'client' dict - and render it to ConfigObj format under '[client]' section in - /etc/landscape/client.conf - """ - if not ConfigObj: - log.warn(("'ConfigObj' support not available," - " running transform %s disabled"), name) - return - - ls_cloudcfg = cfg.get("landscape", {}) - - if not isinstance(ls_cloudcfg, dict): - raise Exception(("'landscape' key existed in config," - " but not a dictionary type," - " is a %s instead"), util.obj_name(ls_cloudcfg)) - - lsc_client_fn = cloud.paths.join(True, LSC_CLIENT_CFG_FILE) - merged = merge_together([LSC_BUILTIN_CFG, lsc_client_fn, ls_cloudcfg]) - - lsc_dir = cloud.paths.join(False, os.path.dirname(lsc_client_fn)) - if not os.path.isdir(lsc_dir): - util.ensure_dir(lsc_dir) - - contents = StringIO() - merged.write(contents) - contents.flush() - - util.write_file(lsc_client_fn, contents.getvalue()) - log.debug("Wrote landscape config file to %s", lsc_client_fn) - - -def merge_together(objs): - """ - merge together ConfigObj objects or things that ConfigObj() will take in - later entries override earlier - """ - cfg = ConfigObj({}) - for obj in objs: - if not obj: - continue - if isinstance(obj, ConfigObj): - cfg.merge(obj) - else: - cfg.merge(ConfigObj(obj)) - return cfg diff --git a/cloudinit/config/locale.py b/cloudinit/config/locale.py deleted file mode 100644 index 7f273123..00000000 --- a/cloudinit/config/locale.py +++ /dev/null @@ -1,58 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os - -from cloudinit import templater -from cloudinit import util - - -def apply_locale(locale, cfgfile, cloud, log): - # TODO this command might not work on RH... - if os.path.exists('/usr/sbin/locale-gen'): - util.subp(['locale-gen', locale], capture=False) - if os.path.exists('/usr/sbin/update-locale'): - util.subp(['update-locale', locale], capture=False) - if not cfgfile: - return - template_fn = cloud.get_template_filename('default-locale') - if not template_fn: - log.warn("No template filename found to write to %s", cfgfile) - else: - templater.render_to_file(template_fn, cfgfile, {'locale': locale}) - - -def handle(name, cfg, cloud, log, args): - if len(args) != 0: - locale = args[0] - else: - locale = util.get_cfg_option_str(cfg, "locale", cloud.get_locale()) - - locale_cfgfile = util.get_cfg_option_str(cfg, "locale_configfile", - "/etc/default/locale") - - if not locale: - log.debug(("Skipping transform named %s, " - "no 'locale' configuration found"), name) - return - - log.debug("Setting locale to %s", locale) - - apply_locale(locale, locale_cfgfile, cloud, log) diff --git a/cloudinit/config/mounts.py b/cloudinit/config/mounts.py deleted file mode 100644 index 700fbc44..00000000 --- a/cloudinit/config/mounts.py +++ /dev/null @@ -1,200 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -from string import whitespace # pylint: disable=W0402 - -import re - -from cloudinit import util - -# shortname matches 'sda', 'sda1', 'xvda', 'hda', 'sdb', xvdb, vda, vdd1 -shortname_filter = r"^[x]{0,1}[shv]d[a-z][0-9]*$" -shortname = re.compile(shortname_filter) -ws = re.compile("[%s]+" % (whitespace)) - - -def is_mdname(name): - # return true if this is a metadata service name - if name in ["ami", "root", "swap"]: - return True - # names 'ephemeral0' or 'ephemeral1' - # 'ebs[0-9]' appears when '--block-device-mapping sdf=snap-d4d90bbc' - for enumname in ("ephemeral", "ebs"): - if name.startswith(enumname) and name.find(":") == -1: - return True - return False - - -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"] - defvals = cfg.get("mount_default_fields", defvals) - - # these are our default set of mounts - defmnts = [["ephemeral0", "/mnt", "auto", defvals[3], "0", "2"], - ["swap", "none", "swap", "sw", "0", "0"]] - - cfgmnt = [] - if "mounts" in cfg: - cfgmnt = cfg["mounts"] - - - for i in range(len(cfgmnt)): - # skip something that wasn't a list - if not isinstance(cfgmnt[i], list): - log.warn("Mount option %s not a list, got a %s instead", - (i + 1), util.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 - - # in case the user did not quote a field (likely fs-freq, fs_passno) - # but do not convert None to 'None' (LP: #898365) - for j in range(len(cfgmnt[i])): - if j is None: - continue - else: - cfgmnt[i][j] = str(cfgmnt[i][j]) - - for i in range(len(cfgmnt)): - # fill in values with defaults from defvals above - for j in range(len(defvals)): - if len(cfgmnt[i]) <= j: - cfgmnt[i].append(defvals[j]) - elif cfgmnt[i][j] is None: - cfgmnt[i][j] = defvals[j] - - # if the second entry in the list is 'None' this - # clears all previous entries of that same 'fs_spec' - # (fs_spec is the first field in /etc/fstab, ie, that device) - if cfgmnt[i][1] is None: - for j in range(i): - if cfgmnt[j][0] == cfgmnt[i][0]: - cfgmnt[j][1] = None - - # 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) - continue - if devname.startswith("/"): - defmnt[0] = devname - else: - defmnt[0] = "/dev/%s" % devname - - log.debug("Mapped default device %s to %s", startname, defmnt[0]) - - cfgmnt_has = False - for cfgm in cfgmnt: - if cfgm[0] == defmnt[0]: - cfgmnt_has = True - break - - if cfgmnt_has: - log.debug(("Not including %s, already" - " previously included"), startname) - continue - cfgmnt.append(defmnt) - - # now, each entry in the cfgmnt list has all fstab values - # if the second field is None (not the string, the value) we skip it - actlist = [] - for x in cfgmnt: - if x[1] is None: - log.debug("Skipping non-existent device named %s", x[0]) - else: - actlist.append(x) - - if len(actlist) == 0: - log.debug("No modifications to fstab needed.") - return - - comment = "comment=cloudconfig" - cc_lines = [] - needswap = False - dirs = [] - for line in actlist: - # write 'comment' in the fs_mntops, entry, claiming this - line[3] = "%s,%s" % (line[3], comment) - if line[2] == "swap": - needswap = True - if line[1].startswith("/"): - dirs.append(line[1]) - cc_lines.append('\t'.join(line)) - - fstab_lines = [] - fstab = util.load_file(cloud.paths.join(True, "/etc/fstab")) - for line in fstab.splitlines(): - try: - toks = ws.split(line) - if toks[3].find(comment) != -1: - continue - except: - pass - fstab_lines.append(line) - - fstab_lines.extend(cc_lines) - contents = "%s\n" % ('\n'.join(fstab_lines)) - util.write_file(cloud.paths.join(False, "/etc/fstab"), contents) - - if needswap: - try: - util.subp(("swapon", "-a")) - except: - util.logexc(log, "Activating swap via 'swapon -a' failed") - - for d in dirs: - try: - util.ensure_dir(cloud.paths.join(False, d)) - except: - util.logexc(log, "Failed to make '%s' config-mount", d) - - try: - util.subp(("mount", "-a")) - except: - util.logexc(log, "Activating mounts via 'mount -a' failed") diff --git a/cloudinit/config/phone_home.py b/cloudinit/config/phone_home.py deleted file mode 100644 index a8752527..00000000 --- a/cloudinit/config/phone_home.py +++ /dev/null @@ -1,111 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -from cloudinit import templater -from cloudinit import url_helper as uhelp -from cloudinit import util - -from cloudinit.settings import PER_INSTANCE - -frequency = PER_INSTANCE - -post_list_all = ['pub_key_dsa', 'pub_key_rsa', 'pub_key_ecdsa', - 'instance_id', 'hostname'] - - -# phone_home: -# url: http://my.foo.bar/$INSTANCE/ -# post: all -# tries: 10 -# -# phone_home: -# url: http://my.foo.bar/$INSTANCE_ID/ -# post: [ pub_key_dsa, pub_key_rsa, pub_key_ecdsa, instance_id -# -def handle(name, cfg, cloud, log, args): - if len(args) != 0: - ph_cfg = util.read_conf(args[0]) - else: - if not 'phone_home' in cfg: - return - ph_cfg = cfg['phone_home'] - - if 'url' not in ph_cfg: - log.warn(("Skipping transform named %s, " - "no 'url' found in 'phone_home' configuration"), name) - return - - url = ph_cfg['url'] - post_list = ph_cfg.get('post', 'all') - tries = ph_cfg.get('tries', 10) - try: - tries = int(tries) - except: - tries = 10 - util.logexc(log, ("Configuration entry 'tries'" - " is not an integer, using %s instead"), tries) - - if post_list == "all": - post_list = post_list_all - - all_keys = {} - all_keys['instance_id'] = cloud.get_instance_id() - all_keys['hostname'] = cloud.get_hostname() - - pubkeys = { - 'pub_key_dsa': '/etc/ssh/ssh_host_dsa_key.pub', - 'pub_key_rsa': '/etc/ssh/ssh_host_rsa_key.pub', - 'pub_key_ecdsa': '/etc/ssh/ssh_host_ecdsa_key.pub', - } - - for (n, path) in pubkeys.iteritems(): - try: - all_keys[n] = util.load_file(cloud.paths.join(True, path)) - except: - util.logexc(log, ("%s: failed to open, can not" - " phone home that data"), path) - - submit_keys = {} - for k in post_list: - if k in all_keys: - submit_keys[k] = all_keys[k] - else: - submit_keys[k] = None - log.warn(("Requested key %s from 'post'" - " configuration list not available"), k) - - # Get them read to be posted - real_submit_keys = {} - for (k, v) in submit_keys.iteritems(): - if v is None: - real_submit_keys[k] = 'N/A' - else: - real_submit_keys[k] = str(v) - - # Incase the url is parameterized - url_params = { - 'INSTANCE_ID': all_keys['instance_id'], - } - url = templater.render_string(url, url_params) - try: - uhelp.readurl(url, data=real_submit_keys, retries=tries, sec_between=3) - except: - util.logexc(log, ("Failed to post phone home data to" - " %s in %s tries"), url, tries) diff --git a/cloudinit/config/resizefs.py b/cloudinit/config/resizefs.py deleted file mode 100644 index 1690094a..00000000 --- a/cloudinit/config/resizefs.py +++ /dev/null @@ -1,140 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os -import stat -import time - -from cloudinit import util -from cloudinit.settings import PER_ALWAYS - -frequency = PER_ALWAYS - -resize_fs_prefixes_cmds = [ - ('ext', 'resize2fs'), - ('xfs', 'xfs_growfs'), -] - - -def nodeify_path(devpth, where, log): - try: - st_dev = os.stat(where).st_dev - dev = os.makedev(os.major(st_dev), os.minor(st_dev)) - os.mknod(devpth, 0400 | stat.S_IFBLK, dev) - return st_dev - except: - if util.is_container(): - log.debug("Inside container, ignoring mknod failure in resizefs") - return - log.warn("Failed to make device node to resize %s at %s", - where, devpth) - raise - - -def get_fs_type(st_dev, path, log): - try: - dev_entries = util.find_devs_with(tag='TYPE', oformat='value', - no_cache=True, path=path) - if not dev_entries: - return None - return dev_entries[0].strip() - except util.ProcessExecutionError: - util.logexc(log, ("Failed to get filesystem type" - " of maj=%s, min=%s for path %s"), - os.major(st_dev), os.minor(st_dev), path) - raise - - -def handle(name, cfg, cloud, log, args): - if len(args) != 0: - resize_root = args[0] - else: - resize_root = util.get_cfg_option_str(cfg, "resize_rootfs", True) - - if not util.translate_bool(resize_root): - log.debug("Skipping transform named %s, resizing disabled", name) - return - - # TODO is the directory ok to be used?? - resize_root_d = util.get_cfg_option_str(cfg, "resize_rootfs_tmp", "/run") - resize_root_d = cloud.paths.join(False, resize_root_d) - util.ensure_dir(resize_root_d) - - # TODO: allow what is to be resized to be configurable?? - resize_what = cloud.paths.join(False, "/") - with util.SilentTemporaryFile(prefix="cloudinit.resizefs.", - dir=resize_root_d, delete=True) as tfh: - devpth = tfh.name - - # Delete the file so that mknod will work - # but don't change the file handle to know that its - # removed so that when a later call that recreates - # occurs this temporary file will still benefit from - # auto deletion - tfh.unlink_now() - - st_dev = nodeify_path(devpth, resize_what, log) - fs_type = get_fs_type(st_dev, devpth, log) - if not fs_type: - log.warn("Could not determine filesystem type of %s", resize_what) - return - - resizer = None - fstype_lc = fs_type.lower() - for (pfix, root_cmd) in resize_fs_prefixes_cmds: - if fstype_lc.startswith(pfix): - resizer = root_cmd - break - - if not resizer: - log.warn("Not resizing unknown filesystem type %s for %s", - fs_type, resize_what) - return - - log.debug("Resizing %s (%s) using %s", resize_what, fs_type, resizer) - resize_cmd = [resizer, devpth] - - if resize_root == "noblock": - # Fork to a child that will run - # the resize command - util.fork_cb(do_resize, resize_cmd, log) - # Don't delete the file now in the parent - tfh.delete = False - else: - do_resize(resize_cmd, log) - - action = 'Resized' - if resize_root == "noblock": - action = 'Resizing (via forking)' - log.debug("%s root filesystem (type=%s, maj=%i, min=%i, val=%s)", - action, fs_type, os.major(st_dev), os.minor(st_dev), resize_root) - - -def do_resize(resize_cmd, log): - start = time.time() - try: - util.subp(resize_cmd) - except util.ProcessExecutionError: - util.logexc(log, "Failed to resize filesystem (cmd=%s)", resize_cmd) - raise - tot_time = int(time.time() - start) - log.debug("Resizing took %s seconds", tot_time) - # TODO: Should we add a fsck check after this to make - # sure we didn't corrupt anything? diff --git a/cloudinit/config/rightscale_userdata.py b/cloudinit/config/rightscale_userdata.py deleted file mode 100644 index 8385e281..00000000 --- a/cloudinit/config/rightscale_userdata.py +++ /dev/null @@ -1,102 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -## -## The purpose of this script is to allow cloud-init to consume -## rightscale style userdata. rightscale user data is key-value pairs -## in a url-query-string like format. -## -## for cloud-init support, there will be a key named -## 'CLOUD_INIT_REMOTE_HOOK'. -## -## This cloud-config module will -## - read the blob of data from raw user data, and parse it as key/value -## - for each key that is found, download the content to -## the local instance/scripts directory and set them executable. -## - the files in that directory will be run by the user-scripts module -## Therefore, this must run before that. -## -## - -import os - -from cloudinit import url_helper as uhelp -from cloudinit import util -from cloudinit.settings import PER_INSTANCE - -from urlparse import parse_qs - -frequency = PER_INSTANCE - -MY_NAME = "cc_rightscale_userdata" -MY_HOOKNAME = 'CLOUD_INIT_REMOTE_HOOK' - - -def handle(name, _cfg, cloud, log, _args): - try: - ud = cloud.get_userdata_raw() - except: - log.warn("Failed to get raw userdata in transform %s", name) - return - - try: - mdict = parse_qs(ud) - if not mdict or not MY_HOOKNAME in mdict: - log.debug(("Skipping transform %s, " - "did not find %s in parsed" - " raw userdata"), name, MY_HOOKNAME) - return - except: - util.logexc(log, ("Failed to parse query string %s" - " into a dictionary"), ud) - raise - - wrote_fns = [] - captured_excps = [] - - # These will eventually be then ran by the cc_scripts_user - # TODO: maybe this should just be a new user data handler?? - # Instead of a late transform that acts like a user data handler? - scripts_d = cloud.get_ipath_cur('scripts') - urls = mdict[MY_HOOKNAME] - for (i, url) in enumerate(urls): - fname = os.path.join(scripts_d, "rightscale-%02i" % (i)) - try: - resp = uhelp.readurl(url) - # Ensure its a valid http response (and something gotten) - if resp.ok() and resp.contents: - util.write_file(fname, str(resp), mode=0700) - wrote_fns.append(fname) - except Exception as e: - captured_excps.append(e) - util.logexc(log, "%s failed to read %s and write %s", - MY_NAME, url, fname) - - if wrote_fns: - log.debug("Wrote out rightscale userdata to %s files", len(wrote_fns)) - - if len(wrote_fns) != len(urls): - skipped = len(urls) - len(wrote_fns) - log.debug("%s urls were skipped or failed", skipped) - - if captured_excps: - log.warn("%s failed with exceptions, re-raising the last one", - len(captured_excps)) - raise captured_excps[-1] diff --git a/cloudinit/config/rsyslog.py b/cloudinit/config/rsyslog.py deleted file mode 100644 index f2c1de1e..00000000 --- a/cloudinit/config/rsyslog.py +++ /dev/null @@ -1,102 +0,0 @@ -# vi: ts=4 expandtab syntax=python -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os - -from cloudinit import util - -DEF_FILENAME = "20-cloud-config.conf" -DEF_DIR = "/etc/rsyslog.d" - - -def handle(name, cfg, cloud, log, _args): - # rsyslog: - # - "*.* @@192.158.1.1" - # - content: "*.* @@192.0.2.1:10514" - # - filename: 01-examplecom.conf - # content: | - # *.* @@syslogd.example.com - - # process 'rsyslog' - if not 'rsyslog' in cfg: - log.debug(("Skipping transform named %s," - " no 'rsyslog' key in configuration"), name) - return - - def_dir = cfg.get('rsyslog_dir', DEF_DIR) - def_fname = cfg.get('rsyslog_filename', DEF_FILENAME) - - files = [] - for i, ent in enumerate(cfg['rsyslog']): - if isinstance(ent, dict): - if not "content" in ent: - log.warn("No 'content' entry in config entry %s", i + 1) - continue - content = ent['content'] - filename = ent.get("filename", def_fname) - else: - content = ent - filename = def_fname - - filename = filename.strip() - if not filename: - log.warn("Entry %s has an empty filename", i + 1) - continue - - if not filename.startswith("/"): - filename = os.path.join(def_dir, filename) - - # Truncate filename first time you see it - omode = "ab" - if filename not in files: - omode = "wb" - files.append(filename) - - try: - contents = "%s\n" % (content) - util.write_file(cloud.paths.join(False, filename), - contents, omode=omode) - except Exception: - util.logexc(log, "Failed to write to %s", filename) - - # Attempt to restart syslogd - restarted = False - try: - # If this config module is running at cloud-init time - # (before rsyslog is running) we don't actually have to - # restart syslog. - # - # Upstart actually does what we want here, in that it doesn't - # start a service that wasn't running already on 'restart' - # it will also return failure on the attempt, so 'restarted' - # won't get set. - log.debug("Restarting rsyslog") - util.subp(['service', 'rsyslog', 'restart']) - restarted = True - except Exception: - util.logexc(log, "Failed restarting rsyslog") - - if restarted: - # This only needs to run if we *actually* restarted - # syslog above. - cloud.cycle_logging() - # This should now use rsyslog if - # the logging was setup to use it... - log.debug("%s configured %s files", name, files) diff --git a/cloudinit/config/runcmd.py b/cloudinit/config/runcmd.py deleted file mode 100644 index f121484b..00000000 --- a/cloudinit/config/runcmd.py +++ /dev/null @@ -1,38 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os - -from cloudinit import util - - -def handle(name, cfg, cloud, log, _args): - if "runcmd" not in cfg: - log.debug(("Skipping transform named %s," - " no 'runcmd' key in configuration"), name) - return - - out_fn = os.path.join(cloud.get_ipath('scripts'), "runcmd") - cmd = cfg["runcmd"] - try: - content = util.shellify(cmd) - util.write_file(cloud.paths.join(False, out_fn), content, 0700) - except: - util.logexc(log, "Failed to shellify %s into file %s", cmd, out_fn) diff --git a/cloudinit/config/salt_minion.py b/cloudinit/config/salt_minion.py deleted file mode 100644 index 16f5286d..00000000 --- a/cloudinit/config/salt_minion.py +++ /dev/null @@ -1,60 +0,0 @@ -# vi: ts=4 expandtab -# -# Author: Jeff Bauer -# -# 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 . - -import os - -from cloudinit import util - -# Note: see http://saltstack.org/topics/installation/ - - -def handle(name, cfg, cloud, log, _args): - # If there isn't a salt key in the configuration don't do anything - if 'salt_minion' not in cfg: - log.debug(("Skipping transform named %s," - " no 'salt_minion' key in configuration"), name) - return - - salt_cfg = cfg['salt_minion'] - - # Start by installing the salt package ... - cloud.distro.install_packages(["salt"]) - - # Ensure we can configure files at the right dir - config_dir = salt_cfg.get("config_dir", '/etc/salt') - config_dir = cloud.paths.join(False, config_dir) - util.ensure_dir(config_dir) - - # ... and then update the salt configuration - if 'conf' in salt_cfg: - # Add all sections from the conf object to /etc/salt/minion - minion_config = os.path.join(config_dir, 'minion') - minion_data = util.yaml_dumps(salt_cfg.get('conf')) - util.write_file(minion_config, minion_data) - - # ... copy the key pair if specified - if 'public_key' in salt_cfg and 'private_key' in salt_cfg: - pki_dir = salt_cfg.get('pki_dir', '/etc/salt/pki') - pki_dir = cloud.paths.join(pki_dir) - with util.umask(077): - util.ensure_dir(pki_dir) - pub_name = os.path.join(pki_dir, 'minion.pub') - pem_name = os.path.join(pki_dir, 'minion.pem') - util.write_file(pub_name, salt_cfg['public_key']) - util.write_file(pem_name, salt_cfg['private_key']) - - # Start salt-minion - util.subp(['service', 'salt-minion', 'start'], capture=False) diff --git a/cloudinit/config/scripts_per_boot.py b/cloudinit/config/scripts_per_boot.py deleted file mode 100644 index 364e1d02..00000000 --- a/cloudinit/config/scripts_per_boot.py +++ /dev/null @@ -1,41 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os - -from cloudinit import util - -from cloudinit.settings import PER_ALWAYS - -frequency = PER_ALWAYS - -script_subdir = 'per-boot' - - -def handle(name, _cfg, cloud, log, _args): - # Comes from the following: - # https://forums.aws.amazon.com/thread.jspa?threadID=96918 - runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) - try: - util.runparts(runparts_path) - except: - log.warn("Failed to run transform %s (%s in %s)", - name, script_subdir, runparts_path) - raise diff --git a/cloudinit/config/scripts_per_instance.py b/cloudinit/config/scripts_per_instance.py deleted file mode 100644 index d75ab47d..00000000 --- a/cloudinit/config/scripts_per_instance.py +++ /dev/null @@ -1,41 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os - -from cloudinit import util - -from cloudinit.settings import PER_INSTANCE - -frequency = PER_INSTANCE - -script_subdir = 'per-instance' - - -def handle(name, _cfg, cloud, log, _args): - # Comes from the following: - # https://forums.aws.amazon.com/thread.jspa?threadID=96918 - runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) - try: - util.runparts(runparts_path) - except: - log.warn("Failed to run transform %s (%s in %s)", - name, script_subdir, runparts_path) - raise diff --git a/cloudinit/config/scripts_per_once.py b/cloudinit/config/scripts_per_once.py deleted file mode 100644 index 80f8c325..00000000 --- a/cloudinit/config/scripts_per_once.py +++ /dev/null @@ -1,41 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os - -from cloudinit import util - -from cloudinit.settings import PER_ONCE - -frequency = PER_ONCE - -script_subdir = 'per-once' - - -def handle(name, _cfg, cloud, log, _args): - # Comes from the following: - # https://forums.aws.amazon.com/thread.jspa?threadID=96918 - runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) - try: - util.runparts(runparts_path) - except: - log.warn("Failed to run transform %s (%s in %s)", - name, script_subdir, runparts_path) - raise diff --git a/cloudinit/config/scripts_user.py b/cloudinit/config/scripts_user.py deleted file mode 100644 index f4fe3a2a..00000000 --- a/cloudinit/config/scripts_user.py +++ /dev/null @@ -1,42 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os - -from cloudinit import util - -from cloudinit.settings import PER_INSTANCE - -frequency = PER_INSTANCE - -script_subdir = 'scripts' - - -def handle(name, _cfg, cloud, log, _args): - # This is written to by the user data handlers - # Ie, any custom shell scripts that come down - # go here... - runparts_path = os.path.join(cloud.get_ipath_cur(), script_subdir) - try: - util.runparts(runparts_path) - except: - log.warn("Failed to run transform %s (%s in %s)", - name, script_subdir, runparts_path) - raise diff --git a/cloudinit/config/set_hostname.py b/cloudinit/config/set_hostname.py deleted file mode 100644 index 3ac8a8fa..00000000 --- a/cloudinit/config/set_hostname.py +++ /dev/null @@ -1,35 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -from cloudinit import util - - -def handle(name, cfg, cloud, log, _args): - if util.get_cfg_option_bool(cfg, "preserve_hostname", False): - log.debug(("Configuration option 'preserve_hostname' is set," - " not setting the hostname in transform %s"), name) - return - - (hostname, _fqdn) = util.get_hostname_fqdn(cfg, cloud) - try: - log.debug("Setting hostname to %s", hostname) - cloud.distro.set_hostname(hostname) - except Exception: - util.logexc(log, "Failed to set hostname to %s", hostname) diff --git a/cloudinit/config/set_passwords.py b/cloudinit/config/set_passwords.py deleted file mode 100644 index e7049f22..00000000 --- a/cloudinit/config/set_passwords.py +++ /dev/null @@ -1,151 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import sys - -from cloudinit import util - -from string import letters, digits # pylint: disable=W0402 - -# We are removing certain 'painful' letters/numbers -pw_set = (letters.translate(None, 'loLOI') + - digits.translate(None, '01')) - - -def handle(_name, cfg, cloud, log, args): - if len(args) != 0: - # if run from command line, and give args, wipe the chpasswd['list'] - password = args[0] - if 'chpasswd' in cfg and 'list' in cfg['chpasswd']: - del cfg['chpasswd']['list'] - else: - password = util.get_cfg_option_str(cfg, "password", None) - - expire = True - pw_auth = "no" - change_pwauth = False - plist = None - - if 'chpasswd' in cfg: - chfg = cfg['chpasswd'] - plist = util.get_cfg_option_str(chfg, 'list', plist) - expire = util.get_cfg_option_bool(chfg, 'expire', expire) - - if not plist and password: - user = util.get_cfg_option_str(cfg, "user", "ubuntu") - plist = "%s:%s" % (user, password) - - errors = [] - if plist: - plist_in = [] - randlist = [] - users = [] - for line in plist.splitlines(): - u, p = line.split(':', 1) - if p == "R" or p == "RANDOM": - p = rand_user_password() - randlist.append("%s:%s" % (u, p)) - plist_in.append("%s:%s" % (u, p)) - users.append(u) - - ch_in = '\n'.join(plist_in) - try: - log.debug("Changing password for %s:", users) - util.subp(['chpasswd'], ch_in) - except Exception as e: - errors.append(e) - util.logexc(log, - "Failed to set passwords with chpasswd for %s", users) - - if len(randlist): - blurb = ("Set the following 'random' passwords\n", - '\n'.join(randlist)) - sys.stderr.write("%s\n%s\n" % blurb) - - if expire: - expired_users = [] - for u in users: - try: - util.subp(['passwd', '--expire', u]) - expired_users.append(u) - except Exception as e: - errors.append(e) - util.logexc(log, "Failed to set 'expire' for %s", u) - if expired_users: - log.debug("Expired passwords for: %s users", expired_users) - - change_pwauth = False - pw_auth = None - if 'ssh_pwauth' in cfg: - change_pwauth = True - if util.is_true_str(cfg['ssh_pwauth']): - pw_auth = 'yes' - if util.is_false_str(cfg['ssh_pwauth']): - pw_auth = 'no' - - if change_pwauth: - new_lines = [] - replaced_auth = False - replacement = "PasswordAuthentication %s" % (pw_auth) - - # See http://linux.die.net/man/5/sshd_config - old_lines = util.load_file('/etc/ssh/sshd_config').splitlines() - for i, line in enumerate(old_lines): - if not line.strip() or line.startswith("#"): - new_lines.append(line) - continue - splitup = line.split(None, 1) - if len(splitup) <= 1: - new_lines.append(line) - continue - (cmd, args) = splitup - # Keywords are case-insensitive and arguments are case-sensitive - cmd = cmd.lower().strip() - if cmd == 'passwordauthentication': - log.debug("Replacing auth line %s with %s", i + 1, replacement) - replaced_auth = True - new_lines.append(replacement) - else: - new_lines.append(line) - - if not replaced_auth: - log.debug("Adding new auth line %s", replacement) - replaced_auth = True - new_lines.append(replacement) - - util.write_file(cloud.paths.join(False, '/etc/ssh/sshd_config'), - "\n".join(new_lines)) - - try: - cmd = ['service'] - cmd.append(cloud.distro.get_option('ssh_svcname', 'ssh')) - cmd.append('restart') - util.subp(cmd) - log.debug("Restarted the ssh daemon") - except: - util.logexc(log, "Restarting of the ssh daemon failed") - - if len(errors): - log.debug("%s errors occured, re-raising the last one", len(errors)) - raise errors[-1] - - -def rand_user_password(pwlen=9): - return util.rand_str(pwlen, select_from=pw_set) diff --git a/cloudinit/config/ssh.py b/cloudinit/config/ssh.py deleted file mode 100644 index e5e99560..00000000 --- a/cloudinit/config/ssh.py +++ /dev/null @@ -1,131 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os -import glob - -from cloudinit import util -from cloudinit import ssh_util - -DISABLE_ROOT_OPTS = ( "no-port-forwarding,no-agent-forwarding," -"no-X11-forwarding,command=\"echo \'Please login as the user \\\"$USER\\\" " -"rather than the user \\\"root\\\".\';echo;sleep 10\"") - -key2file = { - "rsa_private": ("/etc/ssh/ssh_host_rsa_key", 0600), - "rsa_public": ("/etc/ssh/ssh_host_rsa_key.pub", 0644), - "dsa_private": ("/etc/ssh/ssh_host_dsa_key", 0600), - "dsa_public": ("/etc/ssh/ssh_host_dsa_key.pub", 0644), - "ecdsa_private": ("/etc/ssh/ssh_host_ecdsa_key", 0600), - "ecdsa_public": ("/etc/ssh/ssh_host_ecdsa_key.pub", 0644), -} - -priv2pub = { - 'rsa_private': 'rsa_public', - 'dsa_private': 'dsa_public', - 'ecdsa_private': 'ecdsa_public', -} - -key_gen_tpl = 'o=$(ssh-keygen -yf "%s") && echo "$o" root@localhost > "%s"' - -generate_keys = ['rsa', 'dsa', 'ecdsa'] - - -def handle(_name, cfg, cloud, log, _args): - - # remove the static keys from the pristine image - if cfg.get("ssh_deletekeys", True): - key_pth = cloud.paths.join(False, "/etc/ssh/", "ssh_host_*key*") - for f in glob.glob(key_pth): - try: - util.del_file(f) - except: - util.logexc(log, "Failed deleting key file %s", f) - - if "ssh_keys" in cfg: - # if there are keys in cloud-config, use them - for (key, val) in cfg["ssh_keys"].iteritems(): - if key in key2file: - tgt_fn = key2file[key][0] - tgt_perms = key2file[key][1] - util.write_file(cloud.paths.join(False, tgt_fn), - val, tgt_perms) - - for (priv, pub) in priv2pub.iteritems(): - if pub in cfg['ssh_keys'] or not priv in cfg['ssh_keys']: - continue - pair = (key2file[priv][0], key2file[pub][0]) - cmd = ['sh', '-xc', key_gen_tpl % pair] - try: - # TODO: Is this guard needed? - with util.SeLinuxGuard("/etc/ssh", recursive=True): - util.subp(cmd, capture=False) - log.debug("Generated a key for %s from %s", pair[0], pair[1]) - except: - util.logexc(log, ("Failed generated a key" - " for %s from %s"), pair[0], pair[1]) - else: - # if not, generate them - genkeys = util.get_cfg_option_list(cfg, - 'ssh_genkeytypes', - generate_keys) - for keytype in genkeys: - keyfile = '/etc/ssh/ssh_host_%s_key' % (keytype) - keyfile = cloud.paths.join(False, keyfile) - util.ensure_dir(os.path.dirname(keyfile)) - if not os.path.exists(keyfile): - cmd = ['ssh-keygen', '-t', keytype, '-N', '', '-f', keyfile] - try: - # TODO: Is this guard needed? - with util.SeLinuxGuard("/etc/ssh", recursive=True): - util.subp(cmd, capture=False) - except: - util.logexc(log, ("Failed generating key type" - " %s to file %s"), keytype, keyfile) - - try: - user = util.get_cfg_option_str(cfg, 'user') - disable_root = util.get_cfg_option_bool(cfg, "disable_root", True) - disable_root_opts = util.get_cfg_option_str(cfg, "disable_root_opts", - DISABLE_ROOT_OPTS) - - keys = cloud.get_public_ssh_keys() or [] - if "ssh_authorized_keys" in cfg: - cfgkeys = cfg["ssh_authorized_keys"] - keys.extend(cfgkeys) - - apply_credentials(keys, user, cloud.paths, - disable_root, disable_root_opts) - except: - util.logexc(log, "Applying ssh credentials failed!") - - -def apply_credentials(keys, user, paths, disable_root, disable_root_opts): - - keys = set(keys) - if user: - ssh_util.setup_user_keys(keys, user, '', paths) - - if disable_root and user: - key_prefix = disable_root_opts.replace('$USER', user) - else: - key_prefix = '' - - ssh_util.setup_user_keys(keys, 'root', key_prefix, paths) diff --git a/cloudinit/config/ssh_import_id.py b/cloudinit/config/ssh_import_id.py deleted file mode 100644 index d57e4665..00000000 --- a/cloudinit/config/ssh_import_id.py +++ /dev/null @@ -1,53 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -from cloudinit import util - -# The ssh-import-id only seems to exist on ubuntu (for now) -# https://launchpad.net/ssh-import-id -distros = ['ubuntu'] - - -def handle(name, cfg, _cloud, log, args): - if len(args) != 0: - user = args[0] - ids = [] - if len(args) > 1: - ids = args[1:] - else: - user = util.get_cfg_option_str(cfg, "user", "ubuntu") - ids = util.get_cfg_option_list(cfg, "ssh_import_id", []) - - if len(ids) == 0: - log.debug("Skipping transform named %s, no ids found to import", name) - return - - if not user: - log.debug("Skipping transform named %s, no user found to import", name) - return - - cmd = ["sudo", "-Hu", user, "ssh-import-id"] + ids - log.debug("Importing ssh ids for user %s.", user) - - try: - util.subp(cmd, capture=False) - except util.ProcessExecutionError as e: - util.logexc(log, "Failed to run command to import %s ssh ids", user) - raise e diff --git a/cloudinit/config/timezone.py b/cloudinit/config/timezone.py deleted file mode 100644 index 747c436c..00000000 --- a/cloudinit/config/timezone.py +++ /dev/null @@ -1,39 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -from cloudinit import util - -from cloudinit.settings import PER_INSTANCE - -frequency = PER_INSTANCE - - -def handle(name, cfg, cloud, log, args): - if len(args) != 0: - timezone = args[0] - else: - timezone = util.get_cfg_option_str(cfg, "timezone", False) - - if not timezone: - log.debug("Skipping transform named %s, no 'timezone' specified", name) - return - - # Let the distro handle settings its timezone - cloud.distro.set_timezone(timezone) diff --git a/cloudinit/config/update_etc_hosts.py b/cloudinit/config/update_etc_hosts.py deleted file mode 100644 index 75615db1..00000000 --- a/cloudinit/config/update_etc_hosts.py +++ /dev/null @@ -1,60 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -from cloudinit import util -from cloudinit import templater - -from cloudinit.settings import PER_ALWAYS - -frequency = PER_ALWAYS - - -def handle(name, cfg, cloud, log, _args): - manage_hosts = util.get_cfg_option_str(cfg, "manage_etc_hosts", False) - if util.translate_bool(manage_hosts, addons=['template']): - (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) - if not hostname: - log.warn(("Option 'manage_etc_hosts' was set," - " but no hostname was found")) - return - - # Render from a template file - distro_n = cloud.distro.name - tpl_fn_name = cloud.get_template_filename("hosts.%s" % (distro_n)) - if not tpl_fn_name: - raise Exception(("No hosts template could be" - " found for distro %s") % (distro_n)) - - out_fn = cloud.paths.join(False, '/etc/hosts') - templater.render_to_file(tpl_fn_name, out_fn, - {'hostname': hostname, 'fqdn': fqdn}) - - elif manage_hosts == "localhost": - (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) - if not hostname: - log.warn(("Option 'manage_etc_hosts' was set," - " but no hostname was found")) - return - - log.debug("Managing localhost in /etc/hosts") - cloud.distro.update_etc_hosts(hostname, fqdn) - else: - log.debug(("Configuration option 'manage_etc_hosts' is not set," - " not managing /etc/hosts in transform %s"), name) diff --git a/cloudinit/config/update_hostname.py b/cloudinit/config/update_hostname.py deleted file mode 100644 index 58444fab..00000000 --- a/cloudinit/config/update_hostname.py +++ /dev/null @@ -1,41 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2011 Canonical Ltd. -# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. -# -# Author: Scott Moser -# Author: Juerg Haefliger -# -# 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 . - -import os - -from cloudinit import util -from cloudinit.settings import PER_ALWAYS - -frequency = PER_ALWAYS - - -def handle(name, cfg, cloud, log, _args): - if util.get_cfg_option_bool(cfg, "preserve_hostname", False): - log.debug(("Configuration option 'preserve_hostname' is set," - " not updating the hostname in transform %s"), name) - return - - (hostname, _fqdn) = util.get_hostname_fqdn(cfg, cloud) - try: - prev_fn = os.path.join(cloud.get_cpath('data'), "previous-hostname") - cloud.distro.update_hostname(hostname, prev_fn) - except Exception: - util.logexc(log, "Failed to set the hostname to %s", hostname) - raise -- cgit v1.2.3 From ec4bdc4fb8d8d3a8f8b4f498eb47eac740485ede Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 20 Jun 2012 17:13:55 -0700 Subject: Massive pylint + pep8 fixups! --- bin/cloud-init | 20 +++++++-------- cloudinit/cloud.py | 3 ++- cloudinit/config/__init__.py | 2 +- cloudinit/config/cc_chef.py | 14 +++++------ cloudinit/config/cc_disable_ec2_metadata.py | 4 +-- cloudinit/config/cc_final_message.py | 8 +++--- cloudinit/config/cc_foo.py | 8 +++--- cloudinit/config/cc_keys_to_console.py | 8 +++--- cloudinit/config/cc_landscape.py | 2 +- cloudinit/config/cc_mcollective.py | 4 +-- cloudinit/config/cc_mounts.py | 13 +++++----- cloudinit/config/cc_phone_home.py | 11 +++++--- cloudinit/config/cc_puppet.py | 2 +- cloudinit/config/cc_resizefs.py | 10 ++++---- cloudinit/config/cc_salt_minion.py | 2 +- cloudinit/config/cc_scripts_per_boot.py | 6 ++--- cloudinit/config/cc_scripts_per_instance.py | 6 ++--- cloudinit/config/cc_scripts_per_once.py | 6 ++--- cloudinit/config/cc_scripts_user.py | 6 ++--- cloudinit/config/cc_set_passwords.py | 4 +-- cloudinit/config/cc_ssh.py | 39 +++++++++++++++-------------- cloudinit/distros/__init__.py | 1 - cloudinit/distros/rhel.py | 14 +++++------ cloudinit/distros/ubuntu.py | 6 ++--- cloudinit/handlers/__init__.py | 8 +++--- cloudinit/helpers.py | 6 ++--- cloudinit/log.py | 2 -- cloudinit/settings.py | 2 +- cloudinit/sources/DataSourceCloudStack.py | 2 +- cloudinit/sources/DataSourceConfigDrive.py | 2 +- cloudinit/sources/DataSourceEc2.py | 6 ++--- cloudinit/sources/DataSourceMAAS.py | 1 + cloudinit/sources/DataSourceNoCloud.py | 2 +- cloudinit/ssh_util.py | 5 ++-- cloudinit/stages.py | 6 ++--- cloudinit/url_helper.py | 14 +++++------ cloudinit/user_data.py | 28 +++++++++------------ cloudinit/util.py | 37 ++++++++++++++------------- 38 files changed, 159 insertions(+), 161 deletions(-) (limited to 'cloudinit/config') diff --git a/bin/cloud-init b/bin/cloud-init index 032d5f39..c1788ef4 100755 --- a/bin/cloud-init +++ b/bin/cloud-init @@ -75,6 +75,7 @@ def welcome(action): sys.stderr.flush() LOG.info(welcome_msg) + def extract_fns(args): # Files are already opened so lets just pass that along # since it would of broke if it couldn't have @@ -329,11 +330,11 @@ def main_single(name, args): def main(): parser = argparse.ArgumentParser() - + # Top level args - parser.add_argument('--version', '-v', action='version', + parser.add_argument('--version', '-v', action='version', version='%(prog)s ' + (version.version_string())) - parser.add_argument('--file', '-f', action='append', + parser.add_argument('--file', '-f', action='append', dest='files', help=('additional yaml configuration' ' files to use'), @@ -345,18 +346,18 @@ def main(): subparsers = parser.add_subparsers() # Each action and its sub-options (if any) - parser_init = subparsers.add_parser('init', + parser_init = subparsers.add_parser('init', help=('initializes cloud-init and' ' performs initial modules')) parser_init.add_argument("--local", '-l', action='store_true', help="start in local mode (default: %(default)s)", default=False) - # This is used so that we can know which action is selected + + # This is used so that we can know which action is selected + # the functor to use to run this subcommand parser_init.set_defaults(action=('init', main_init)) # These settings are used for the 'config' and 'final' stages - parser_mod = subparsers.add_parser('modules', + parser_mod = subparsers.add_parser('modules', help=('activates modules ' 'using a given configuration key')) parser_mod.add_argument("--mode", '-m', action='store', @@ -368,7 +369,7 @@ def main(): # These settings are used when you want to query information # stored in the cloud-init data objects/directories/files - parser_query = subparsers.add_parser('query', + parser_query = subparsers.add_parser('query', help=('query information stored ' 'in cloud-init')) parser_query.add_argument("--name", '-n', action="store", @@ -378,7 +379,7 @@ def main(): parser_query.set_defaults(action=('query', main_query)) # This subcommand allows you to run a single module - parser_single = subparsers.add_parser('single', + parser_single = subparsers.add_parser('single', help=('run a single module ')) parser_single.set_defaults(action=('single', main_single)) parser_single.add_argument("--name", '-n', action="store", @@ -394,10 +395,10 @@ def main(): ' pass to this module')) parser_single.set_defaults(action=('single', main_single)) - args = parser.parse_args() # Setup basic logging to start (until reinitialized) + # iff in debug mode... if args.debug: logging.setupBasicLogging() @@ -407,4 +408,3 @@ def main(): if __name__ == '__main__': sys.exit(main()) - diff --git a/cloudinit/cloud.py b/cloudinit/cloud.py index 90679202..6cdcb76a 100644 --- a/cloudinit/cloud.py +++ b/cloudinit/cloud.py @@ -38,6 +38,7 @@ LOG = logging.getLogger(__name__) # as providing a backwards compatible object that can be maintained # while the stages/other objects can be worked on independently... + class Cloud(object): def __init__(self, datasource, paths, cfg, distro, runners): self.datasource = datasource @@ -71,7 +72,7 @@ class Cloud(object): # The rest of thes are just useful proxies def get_userdata(self): return self.datasource.get_userdata() - + def get_instance_id(self): return self.datasource.get_instance_id() diff --git a/cloudinit/config/__init__.py b/cloudinit/config/__init__.py index 74e2f275..02e32462 100644 --- a/cloudinit/config/__init__.py +++ b/cloudinit/config/__init__.py @@ -25,7 +25,7 @@ from cloudinit import log as logging LOG = logging.getLogger(__name__) -# This prefix is used to make it less +# This prefix is used to make it less # of a change that when importing # we will not find something else with the same # name in the lookup path... diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py index 4e8ef346..74af2a7e 100644 --- a/cloudinit/config/cc_chef.py +++ b/cloudinit/config/cc_chef.py @@ -24,7 +24,7 @@ import os from cloudinit import templater from cloudinit import util -ruby_version_default = "1.8" +RUBY_VERSION_DEFAULT = "1.8" def handle(name, cfg, cloud, log, _args): @@ -38,11 +38,11 @@ def handle(name, cfg, cloud, log, _args): # Ensure the chef directories we use exist c_dirs = [ - '/etc/chef', - '/var/log/chef', - '/var/lib/chef', - '/var/cache/chef', - '/var/backups/chef', + '/etc/chef', + '/var/log/chef', + '/var/lib/chef', + '/var/cache/chef', + '/var/backups/chef', '/var/run/chef', ] for d in c_dirs: @@ -92,7 +92,7 @@ def handle(name, cfg, cloud, log, _args): # this will install and run the chef-client from gems chef_version = util.get_cfg_option_str(chef_cfg, 'version', None) ruby_version = util.get_cfg_option_str(chef_cfg, 'ruby_version', - ruby_version_default) + RUBY_VERSION_DEFAULT) install_chef_from_gems(cloud.distro, ruby_version, chef_version) # and finally, run chef-client log.debug('Running chef-client') diff --git a/cloudinit/config/cc_disable_ec2_metadata.py b/cloudinit/config/cc_disable_ec2_metadata.py index c7d26029..62cca7cc 100644 --- a/cloudinit/config/cc_disable_ec2_metadata.py +++ b/cloudinit/config/cc_disable_ec2_metadata.py @@ -24,13 +24,13 @@ from cloudinit.settings import PER_ALWAYS frequency = PER_ALWAYS -reject_cmd = ['route', 'add', '-host', '169.254.169.254', 'reject'] +REJECT_CMD = ['route', 'add', '-host', '169.254.169.254', 'reject'] def handle(name, cfg, _cloud, log, _args): disabled = util.get_cfg_option_bool(cfg, "disable_ec2_metadata", False) if disabled: - util.subp(reject_cmd) + util.subp(REJECT_CMD) else: log.debug(("Skipping transform named %s," " disabling the ec2 route not enabled"), name) diff --git a/cloudinit/config/cc_final_message.py b/cloudinit/config/cc_final_message.py index c257b6d0..fd59aa1e 100644 --- a/cloudinit/config/cc_final_message.py +++ b/cloudinit/config/cc_final_message.py @@ -28,7 +28,7 @@ from cloudinit.settings import PER_ALWAYS frequency = PER_ALWAYS -final_message_def = ("Cloud-init v. {{version}} finished at {{timestamp}}." +FINAL_MESSAGE_DEF = ("Cloud-init v. {{version}} finished at {{timestamp}}." " Up {{uptime}} seconds.") @@ -39,21 +39,21 @@ def handle(_name, cfg, cloud, log, args): msg_in = args[0] else: msg_in = util.get_cfg_option_str(cfg, "final_message") - + if not msg_in: template_fn = cloud.get_template_filename('final_message') if template_fn: msg_in = util.load_file(template_fn) if not msg_in: - msg_in = final_message_def + msg_in = FINAL_MESSAGE_DEF uptime = util.uptime() ts = util.time_rfc2822() cver = version.version_string() try: subs = { - 'uptime': uptime, + 'uptime': uptime, 'timestamp': ts, 'version': cver, } diff --git a/cloudinit/config/cc_foo.py b/cloudinit/config/cc_foo.py index 99135704..e81e7faa 100644 --- a/cloudinit/config/cc_foo.py +++ b/cloudinit/config/cc_foo.py @@ -30,19 +30,19 @@ from cloudinit.settings import PER_INSTANCE # as well as any datasource provided configuration # c) A cloud object that can be used to access various # datasource and paths for the given distro and data provided -# by the various datasource instance types. +# by the various datasource instance types. # d) A argument list that may or may not be empty to this module. # Typically those are from module configuration where the module # is defined with some extra configuration that will eventually # be translated from yaml into arguments to this module. # 2. A optional 'frequency' that defines how often this module should be ran. -# Typically one of PER_INSTANCE, PER_ALWAYS, PER_ONCE. If not -# provided PER_INSTANCE will be assumed. +# Typically one of PER_INSTANCE, PER_ALWAYS, PER_ONCE. If not +# provided PER_INSTANCE will be assumed. # See settings.py for these constants. # 3. A optional 'distros' array/set/tuple that defines the known distros # this module will work with (if not all of them). This is used to write # a warning out if a module is being ran on a untested distribution for -# informational purposes. If non existent all distros are assumed and +# informational purposes. If non existent all distros are assumed and # no warning occurs. frequency = PER_INSTANCE diff --git a/cloudinit/config/cc_keys_to_console.py b/cloudinit/config/cc_keys_to_console.py index 40758198..a8fb3ba7 100644 --- a/cloudinit/config/cc_keys_to_console.py +++ b/cloudinit/config/cc_keys_to_console.py @@ -26,13 +26,13 @@ from cloudinit import util frequency = PER_INSTANCE # This is a tool that cloud init provides -helper_tool = '/usr/lib/cloud-init/write-ssh-key-fingerprints' +HELPER_TOOL = '/usr/lib/cloud-init/write-ssh-key-fingerprints' def handle(name, cfg, cloud, log, _args): - if not os.path.exists(helper_tool): + if not os.path.exists(HELPER_TOOL): log.warn(("Unable to activate transform %s," - " helper tool not found at %s"), name, helper_tool) + " helper tool not found at %s"), name, HELPER_TOOL) return fp_blacklist = util.get_cfg_option_list(cfg, @@ -42,7 +42,7 @@ def handle(name, cfg, cloud, log, _args): ["ssh-dss"]) try: - cmd = [helper_tool] + cmd = [HELPER_TOOL] cmd.append(','.join(fp_blacklist)) cmd.append(','.join(key_blacklist)) (stdout, _stderr) = util.subp(cmd) diff --git a/cloudinit/config/cc_landscape.py b/cloudinit/config/cc_landscape.py index 29ce41b9..599276a7 100644 --- a/cloudinit/config/cc_landscape.py +++ b/cloudinit/config/cc_landscape.py @@ -62,7 +62,7 @@ def handle(name, cfg, cloud, log, _args): ls_cloudcfg = cfg.get("landscape", {}) if not isinstance(ls_cloudcfg, dict): - raise Exception(("'landscape' key existed in config," + raise Exception(("'landscape' key existed in config," " but not a dictionary type," " is a %s instead"), util.obj_name(ls_cloudcfg)) diff --git a/cloudinit/config/cc_mcollective.py b/cloudinit/config/cc_mcollective.py index 4cec6494..ba5e13ca 100644 --- a/cloudinit/config/cc_mcollective.py +++ b/cloudinit/config/cc_mcollective.py @@ -52,7 +52,7 @@ def handle(name, cfg, cloud, log, _args): # It doesn't contain any sections so just add one temporarily # Use a hash id based off the contents, # just incase of conflicts... (try to not have any...) - # This is so that an error won't occur when reading (and no + # This is so that an error won't occur when reading (and no # sections exist in the file) section_tpl = "[nullsection_%s]" attempts = 0 @@ -85,7 +85,7 @@ def handle(name, cfg, cloud, log, _args): # the previous server.cfg and create our new one old_fn = "%s.old" % (server_cfg_fn) util.rename(server_cfg_fn, old_fn) - # Now we got the whole file, write to disk except the section + # Now we got the whole file, write to disk except the section # we added so that config parser won't error out when trying to read. # Note below, that we've just used ConfigParser because it generally # works. Below, we remove the initial 'nullsection' header. diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py index 700fbc44..ab097c2a 100644 --- a/cloudinit/config/cc_mounts.py +++ b/cloudinit/config/cc_mounts.py @@ -24,10 +24,10 @@ import re from cloudinit import util -# shortname matches 'sda', 'sda1', 'xvda', 'hda', 'sdb', xvdb, vda, vdd1 -shortname_filter = r"^[x]{0,1}[shv]d[a-z][0-9]*$" -shortname = re.compile(shortname_filter) -ws = re.compile("[%s]+" % (whitespace)) +# Shortname matches 'sda', 'sda1', 'xvda', 'hda', 'sdb', xvdb, vda, vdd1 +SHORTNAME_FILTER = r"^[x]{0,1}[shv]d[a-z][0-9]*$" +SHORTNAME = re.compile(SHORTNAME_FILTER) +WS = re.compile("[%s]+" % (whitespace)) def is_mdname(name): @@ -55,7 +55,6 @@ def handle(_name, cfg, cloud, log, _args): if "mounts" in cfg: cfgmnt = cfg["mounts"] - for i in range(len(cfgmnt)): # skip something that wasn't a list if not isinstance(cfgmnt[i], list): @@ -85,7 +84,7 @@ def handle(_name, cfg, cloud, log, _args): cfgmnt[i][0] = renamed log.debug("Mapped metadata name %s to %s", startname, renamed) else: - if shortname.match(startname): + if SHORTNAME.match(startname): renamed = "/dev/%s" % startname log.debug("Mapped shortname name %s to %s", startname, renamed) cfgmnt[i][0] = renamed @@ -171,7 +170,7 @@ def handle(_name, cfg, cloud, log, _args): fstab = util.load_file(cloud.paths.join(True, "/etc/fstab")) for line in fstab.splitlines(): try: - toks = ws.split(line) + toks = WS.split(line) if toks[3].find(comment) != -1: continue except: diff --git a/cloudinit/config/cc_phone_home.py b/cloudinit/config/cc_phone_home.py index a8752527..dcb07b66 100644 --- a/cloudinit/config/cc_phone_home.py +++ b/cloudinit/config/cc_phone_home.py @@ -26,8 +26,13 @@ from cloudinit.settings import PER_INSTANCE frequency = PER_INSTANCE -post_list_all = ['pub_key_dsa', 'pub_key_rsa', 'pub_key_ecdsa', - 'instance_id', 'hostname'] +POST_LIST_ALL = [ + 'pub_key_dsa', + 'pub_key_rsa', + 'pub_key_ecdsa', + 'instance_id', + 'hostname' +] # phone_home: @@ -63,7 +68,7 @@ def handle(name, cfg, cloud, log, args): " is not an integer, using %s instead"), tries) if post_list == "all": - post_list = post_list_all + post_list = POST_LIST_ALL all_keys = {} all_keys['instance_id'] = cloud.get_instance_id() diff --git a/cloudinit/config/cc_puppet.py b/cloudinit/config/cc_puppet.py index 5fb88bf2..5154efba 100644 --- a/cloudinit/config/cc_puppet.py +++ b/cloudinit/config/cc_puppet.py @@ -63,7 +63,7 @@ def handle(name, cfg, cloud, log, _args): util.ensure_dir(pp_ssl_dir, 0771) util.chownbyid(pp_ssl_dir, pwd.getpwnam('puppet').pw_uid, 0) - pp_ssl_certs = cloud.paths.join(False, + pp_ssl_certs = cloud.paths.join(False, '/var/lib/puppet/ssl/certs/') util.ensure_dir(pp_ssl_certs) util.chownbyid(pp_ssl_certs, diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py index 1690094a..c019989e 100644 --- a/cloudinit/config/cc_resizefs.py +++ b/cloudinit/config/cc_resizefs.py @@ -27,7 +27,7 @@ from cloudinit.settings import PER_ALWAYS frequency = PER_ALWAYS -resize_fs_prefixes_cmds = [ +RESIZE_FS_PREFIXES_CMDS = [ ('ext', 'resize2fs'), ('xfs', 'xfs_growfs'), ] @@ -89,16 +89,16 @@ def handle(name, cfg, cloud, log, args): # occurs this temporary file will still benefit from # auto deletion tfh.unlink_now() - + st_dev = nodeify_path(devpth, resize_what, log) fs_type = get_fs_type(st_dev, devpth, log) if not fs_type: log.warn("Could not determine filesystem type of %s", resize_what) return - + resizer = None fstype_lc = fs_type.lower() - for (pfix, root_cmd) in resize_fs_prefixes_cmds: + for (pfix, root_cmd) in RESIZE_FS_PREFIXES_CMDS: if fstype_lc.startswith(pfix): resizer = root_cmd break @@ -112,7 +112,7 @@ def handle(name, cfg, cloud, log, args): resize_cmd = [resizer, devpth] if resize_root == "noblock": - # Fork to a child that will run + # Fork to a child that will run # the resize command util.fork_cb(do_resize, resize_cmd, log) # Don't delete the file now in the parent diff --git a/cloudinit/config/cc_salt_minion.py b/cloudinit/config/cc_salt_minion.py index 16f5286d..986e6db6 100644 --- a/cloudinit/config/cc_salt_minion.py +++ b/cloudinit/config/cc_salt_minion.py @@ -32,7 +32,7 @@ def handle(name, cfg, cloud, log, _args): # Start by installing the salt package ... cloud.distro.install_packages(["salt"]) - + # Ensure we can configure files at the right dir config_dir = salt_cfg.get("config_dir", '/etc/salt') config_dir = cloud.paths.join(False, config_dir) diff --git a/cloudinit/config/cc_scripts_per_boot.py b/cloudinit/config/cc_scripts_per_boot.py index 364e1d02..d3c47442 100644 --- a/cloudinit/config/cc_scripts_per_boot.py +++ b/cloudinit/config/cc_scripts_per_boot.py @@ -26,16 +26,16 @@ from cloudinit.settings import PER_ALWAYS frequency = PER_ALWAYS -script_subdir = 'per-boot' +SCRIPT_SUBDIR = 'per-boot' def handle(name, _cfg, cloud, log, _args): # Comes from the following: # https://forums.aws.amazon.com/thread.jspa?threadID=96918 - runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) + runparts_path = os.path.join(cloud.get_cpath(), 'scripts', SCRIPT_SUBDIR) try: util.runparts(runparts_path) except: log.warn("Failed to run transform %s (%s in %s)", - name, script_subdir, runparts_path) + name, SCRIPT_SUBDIR, runparts_path) raise diff --git a/cloudinit/config/cc_scripts_per_instance.py b/cloudinit/config/cc_scripts_per_instance.py index d75ab47d..8e428ac2 100644 --- a/cloudinit/config/cc_scripts_per_instance.py +++ b/cloudinit/config/cc_scripts_per_instance.py @@ -26,16 +26,16 @@ from cloudinit.settings import PER_INSTANCE frequency = PER_INSTANCE -script_subdir = 'per-instance' +SCRIPT_SUBDIR = 'per-instance' def handle(name, _cfg, cloud, log, _args): # Comes from the following: # https://forums.aws.amazon.com/thread.jspa?threadID=96918 - runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) + runparts_path = os.path.join(cloud.get_cpath(), 'scripts', SCRIPT_SUBDIR) try: util.runparts(runparts_path) except: log.warn("Failed to run transform %s (%s in %s)", - name, script_subdir, runparts_path) + name, SCRIPT_SUBDIR, runparts_path) raise diff --git a/cloudinit/config/cc_scripts_per_once.py b/cloudinit/config/cc_scripts_per_once.py index 80f8c325..e7a29a44 100644 --- a/cloudinit/config/cc_scripts_per_once.py +++ b/cloudinit/config/cc_scripts_per_once.py @@ -26,16 +26,16 @@ from cloudinit.settings import PER_ONCE frequency = PER_ONCE -script_subdir = 'per-once' +SCRIPT_SUBDIR = 'per-once' def handle(name, _cfg, cloud, log, _args): # Comes from the following: # https://forums.aws.amazon.com/thread.jspa?threadID=96918 - runparts_path = os.path.join(cloud.get_cpath(), 'scripts', script_subdir) + runparts_path = os.path.join(cloud.get_cpath(), 'scripts', SCRIPT_SUBDIR) try: util.runparts(runparts_path) except: log.warn("Failed to run transform %s (%s in %s)", - name, script_subdir, runparts_path) + name, SCRIPT_SUBDIR, runparts_path) raise diff --git a/cloudinit/config/cc_scripts_user.py b/cloudinit/config/cc_scripts_user.py index f4fe3a2a..1ff05aae 100644 --- a/cloudinit/config/cc_scripts_user.py +++ b/cloudinit/config/cc_scripts_user.py @@ -26,17 +26,17 @@ from cloudinit.settings import PER_INSTANCE frequency = PER_INSTANCE -script_subdir = 'scripts' +SCRIPT_SUBDIR = 'scripts' def handle(name, _cfg, cloud, log, _args): # This is written to by the user data handlers # Ie, any custom shell scripts that come down # go here... - runparts_path = os.path.join(cloud.get_ipath_cur(), script_subdir) + runparts_path = os.path.join(cloud.get_ipath_cur(), SCRIPT_SUBDIR) try: util.runparts(runparts_path) except: log.warn("Failed to run transform %s (%s in %s)", - name, script_subdir, runparts_path) + name, SCRIPT_SUBDIR, runparts_path) raise diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index e7049f22..ce17f357 100644 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -25,7 +25,7 @@ from cloudinit import util from string import letters, digits # pylint: disable=W0402 # We are removing certain 'painful' letters/numbers -pw_set = (letters.translate(None, 'loLOI') + +PW_SET = (letters.translate(None, 'loLOI') + digits.translate(None, '01')) @@ -148,4 +148,4 @@ def handle(_name, cfg, cloud, log, args): def rand_user_password(pwlen=9): - return util.rand_str(pwlen, select_from=pw_set) + return util.rand_str(pwlen, select_from=PW_SET) diff --git a/cloudinit/config/cc_ssh.py b/cloudinit/config/cc_ssh.py index e5e99560..4019ae90 100644 --- a/cloudinit/config/cc_ssh.py +++ b/cloudinit/config/cc_ssh.py @@ -24,11 +24,11 @@ import glob from cloudinit import util from cloudinit import ssh_util -DISABLE_ROOT_OPTS = ( "no-port-forwarding,no-agent-forwarding," -"no-X11-forwarding,command=\"echo \'Please login as the user \\\"$USER\\\" " +DISABLE_ROOT_OPTS = ("no-port-forwarding,no-agent-forwarding," +"no-X11-forwarding,command=\"echo \'Please login as the user \\\"$USER\\\" " "rather than the user \\\"root\\\".\';echo;sleep 10\"") -key2file = { +KEY_2_FILE = { "rsa_private": ("/etc/ssh/ssh_host_rsa_key", 0600), "rsa_public": ("/etc/ssh/ssh_host_rsa_key.pub", 0644), "dsa_private": ("/etc/ssh/ssh_host_dsa_key", 0600), @@ -37,15 +37,17 @@ key2file = { "ecdsa_public": ("/etc/ssh/ssh_host_ecdsa_key.pub", 0644), } -priv2pub = { - 'rsa_private': 'rsa_public', +PRIV_2_PUB = { + 'rsa_private': 'rsa_public', 'dsa_private': 'dsa_public', 'ecdsa_private': 'ecdsa_public', } -key_gen_tpl = 'o=$(ssh-keygen -yf "%s") && echo "$o" root@localhost > "%s"' +KEY_GEN_TPL = 'o=$(ssh-keygen -yf "%s") && echo "$o" root@localhost > "%s"' -generate_keys = ['rsa', 'dsa', 'ecdsa'] +GENERATE_KEY_NAMES = ['rsa', 'dsa', 'ecdsa'] + +KEY_FILE_TPL = '/etc/ssh/ssh_host_%s_key' def handle(_name, cfg, cloud, log, _args): @@ -58,21 +60,21 @@ def handle(_name, cfg, cloud, log, _args): util.del_file(f) except: util.logexc(log, "Failed deleting key file %s", f) - + if "ssh_keys" in cfg: # if there are keys in cloud-config, use them for (key, val) in cfg["ssh_keys"].iteritems(): - if key in key2file: - tgt_fn = key2file[key][0] - tgt_perms = key2file[key][1] + if key in KEY_2_FILE: + tgt_fn = KEY_2_FILE[key][0] + tgt_perms = KEY_2_FILE[key][1] util.write_file(cloud.paths.join(False, tgt_fn), val, tgt_perms) - for (priv, pub) in priv2pub.iteritems(): + for (priv, pub) in PRIV_2_PUB.iteritems(): if pub in cfg['ssh_keys'] or not priv in cfg['ssh_keys']: continue - pair = (key2file[priv][0], key2file[pub][0]) - cmd = ['sh', '-xc', key_gen_tpl % pair] + pair = (KEY_2_FILE[priv][0], KEY_2_FILE[pub][0]) + cmd = ['sh', '-xc', KEY_GEN_TPL % pair] try: # TODO: Is this guard needed? with util.SeLinuxGuard("/etc/ssh", recursive=True): @@ -84,12 +86,11 @@ def handle(_name, cfg, cloud, log, _args): else: # if not, generate them genkeys = util.get_cfg_option_list(cfg, - 'ssh_genkeytypes', - generate_keys) + 'ssh_genkeytypes', + GENERATE_KEY_NAMES) for keytype in genkeys: - keyfile = '/etc/ssh/ssh_host_%s_key' % (keytype) - keyfile = cloud.paths.join(False, keyfile) - util.ensure_dir(os.path.dirname(keyfile)) + keyfile = cloud.paths.join(False, KEY_FILE_TPL % (keytype)) + util.ensure_dir(os.path.dirname(keyfile)) if not os.path.exists(keyfile): cmd = ['ssh-keygen', '-t', keytype, '-N', '', '-f', keyfile] try: diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 45dd85ec..25a60c52 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -157,4 +157,3 @@ def fetch(distro_name, mods=(__name__, )): % (distro_name)) distro_cls = getattr(mod, 'Distro') return distro_cls - diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index b67ae5b8..5cbefa6e 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -35,7 +35,7 @@ class Distro(distros.Distro): def __init__(self, name, cfg, paths): distros.Distro.__init__(self, name, cfg, paths) - + def install_packages(self, pkglist): self.package_command('install', pkglist) @@ -210,12 +210,12 @@ class Distro(distros.Distro): def package_command(self, command, args=None): cmd = ['yum'] # If enabled, then yum will be tolerant of errors on the command line - # with regard to packages. - # For example: if you request to install foo, bar and baz and baz is + # with regard to packages. + # For example: if you request to install foo, bar and baz and baz is # installed; yum won't error out complaining that baz is already - # installed. + # installed. cmd.append("-t") - # Determines whether or not yum prompts for confirmation + # Determines whether or not yum prompts for confirmation # of critical actions. We don't want to prompt... cmd.append("-y") cmd.append(command) @@ -223,8 +223,8 @@ class Distro(distros.Distro): cmd.extend(args) # Allow the output of this to flow outwards (ie not be captured) util.subp(cmd, capture=False) - - + + # This is a util function to translate a ubuntu /etc/network/interfaces 'blob' # to a rhel equiv. that can then be written to /etc/sysconfig/network-scripts/ # TODO remove when we have python-netcf active... diff --git a/cloudinit/distros/ubuntu.py b/cloudinit/distros/ubuntu.py index 5a1b572e..fd7b7b8d 100644 --- a/cloudinit/distros/ubuntu.py +++ b/cloudinit/distros/ubuntu.py @@ -36,11 +36,11 @@ class Distro(distros.Distro): def __init__(self, name, cfg, paths): distros.Distro.__init__(self, name, cfg, paths) - # This will be used to restrict certain + # This will be used to restrict certain # calls from repeatly happening (when they # should only happen say once per instance...) self._runner = helpers.Runners(paths) - + def install_packages(self, pkglist): self._update_package_sources() self.package_command('install', pkglist) @@ -131,4 +131,4 @@ class Distro(distros.Distro): def _update_package_sources(self): self._runner.run("update-sources", self.package_command, - ["update"], freq=PER_INSTANCE) \ No newline at end of file + ["update"], freq=PER_INSTANCE) diff --git a/cloudinit/handlers/__init__.py b/cloudinit/handlers/__init__.py index c6f2119c..d52b1cba 100644 --- a/cloudinit/handlers/__init__.py +++ b/cloudinit/handlers/__init__.py @@ -104,7 +104,7 @@ def run_part(mod, data, ctype, filename, payload, frequency): except: mod_ver = 1 try: - LOG.debug("Calling handler %s (%s, %s, %s) with frequency %s", + LOG.debug("Calling handler %s (%s, %s, %s) with frequency %s", mod, ctype, filename, mod_ver, frequency) if mod_ver >= 2: # Treat as v. 2 which does get a frequency @@ -114,7 +114,7 @@ def run_part(mod, data, ctype, filename, payload, frequency): mod.handle_part(data, ctype, filename, payload) except: util.logexc(LOG, ("Failed calling handler %s (%s, %s, %s)" - " with frequency %s"), + " with frequency %s"), mod, ctype, filename, mod_ver, frequency) @@ -178,7 +178,7 @@ def walker_callback(pdata, ctype, filename, payload): payload, pdata['frequency']) -# Callback is a function that will be called with +# Callback is a function that will be called with # (data, content_type, filename, payload) def walk(msg, callback, data): partnum = 0 @@ -226,5 +226,3 @@ def type_from_starts_with(payload, default=None): if payload_lc.startswith(text): return INCLUSION_TYPES_MAP[text] return default - - diff --git a/cloudinit/helpers.py b/cloudinit/helpers.py index 45633e0f..4447d1ee 100644 --- a/cloudinit/helpers.py +++ b/cloudinit/helpers.py @@ -84,7 +84,7 @@ class FileSemaphores(object): try: util.del_dir(self.sem_path) except (IOError, OSError): - util.logexc(LOG, "Failed deleting semaphore directory %s", + util.logexc(LOG, "Failed deleting semaphore directory %s", self.sem_path) def _acquire(self, name, freq): @@ -212,7 +212,7 @@ class Paths(object): self.cfgs = path_cfgs # Populate all the initial paths self.cloud_dir = self.join(False, - path_cfgs.get('cloud_dir', + path_cfgs.get('cloud_dir', '/var/lib/cloud')) self.instance_link = os.path.join(self.cloud_dir, 'instance') self.boot_finished = os.path.join(self.instance_link, "boot-finished") @@ -237,7 +237,7 @@ class Paths(object): # Set when a datasource becomes active self.datasource = ds - # joins the paths but also appends a read + # joins the paths but also appends a read # or write root if available def join(self, read_only, *paths): if read_only: diff --git a/cloudinit/log.py b/cloudinit/log.py index 478946f8..fc1428a2 100644 --- a/cloudinit/log.py +++ b/cloudinit/log.py @@ -20,7 +20,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - import logging import logging.handlers import logging.config @@ -53,7 +52,6 @@ def setupBasicLogging(): root.setLevel(DEBUG) - def setupLogging(cfg=None): # See if the config provides any logging conf... if not cfg: diff --git a/cloudinit/settings.py b/cloudinit/settings.py index 8a1eaeb3..fac9b862 100644 --- a/cloudinit/settings.py +++ b/cloudinit/settings.py @@ -47,7 +47,7 @@ CFG_BUILTIN = { 'paths': { 'cloud_dir': '/var/lib/cloud', 'templates_dir': '/etc/cloud/templates/', - }, + }, 'distro': 'ubuntu', }, } diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py index b1817654..83c577e6 100644 --- a/cloudinit/sources/DataSourceCloudStack.py +++ b/cloudinit/sources/DataSourceCloudStack.py @@ -121,7 +121,7 @@ class DataSourceCloudStack(sources.DataSource): None, self.metadata_address) self.metadata = boto_utils.get_instance_metadata(self.api_ver, self.metadata_address) - LOG.debug("Crawl of metadata service took %s seconds", + LOG.debug("Crawl of metadata service took %s seconds", int(time.time() - start_time)) return True except Exception: diff --git a/cloudinit/sources/DataSourceConfigDrive.py b/cloudinit/sources/DataSourceConfigDrive.py index 5da1ffea..9905dad4 100644 --- a/cloudinit/sources/DataSourceConfigDrive.py +++ b/cloudinit/sources/DataSourceConfigDrive.py @@ -36,7 +36,7 @@ CFG_DRIVE_FILES = [ "meta.js", ] DEFAULT_METADATA = { - "instance-id": DEFAULT_IID, + "instance-id": DEFAULT_IID, "dsmode": DEFAULT_MODE, } CFG_DRIVE_DEV_ENV = 'CLOUD_INIT_CONFIG_DRIVE_DEVICE' diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py index 55447102..0598dfa2 100644 --- a/cloudinit/sources/DataSourceEc2.py +++ b/cloudinit/sources/DataSourceEc2.py @@ -38,7 +38,7 @@ DEF_MD_URL = "http://169.254.169.254" DEF_MD_VERSION = '2009-04-04' # Default metadata urls that will be used if none are provided -# They will be checked for 'resolveability' and some of the +# They will be checked for 'resolveability' and some of the # following may be discarded if they do not resolve DEF_MD_URLS = [DEF_MD_URL, "http://instance-data:8773"] @@ -69,7 +69,7 @@ class DataSourceEc2(sources.DataSource): None, self.metadata_address) self.metadata = boto_utils.get_instance_metadata(self.api_ver, self.metadata_address) - LOG.debug("Crawl of metadata service took %s seconds", + LOG.debug("Crawl of metadata service took %s seconds", int(time.time() - start_time)) return True except Exception: @@ -201,7 +201,7 @@ class DataSourceEc2(sources.DataSource): return None # Example: - # 'block-device-mapping': + # 'block-device-mapping': # {'ami': '/dev/sda1', # 'ephemeral0': '/dev/sdb', # 'root': '/dev/sda1'} diff --git a/cloudinit/sources/DataSourceMAAS.py b/cloudinit/sources/DataSourceMAAS.py index bb8fbac1..104e7a54 100644 --- a/cloudinit/sources/DataSourceMAAS.py +++ b/cloudinit/sources/DataSourceMAAS.py @@ -251,6 +251,7 @@ datasources = [ (DataSourceMAAS, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)), ] + # Return a list of data sources that match this set of dependencies def get_datasource_list(depends): return sources.list_from_depends(depends, datasources) diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py index 2b016d1c..8499a97c 100644 --- a/cloudinit/sources/DataSourceNoCloud.py +++ b/cloudinit/sources/DataSourceNoCloud.py @@ -154,7 +154,7 @@ class DataSourceNoCloud(sources.DataSource): (self.dsmode in ("local", seeded_interfaces))): LOG.info("Updating network interfaces from %s", self) self.distro.apply_network(md['network-interfaces']) - + if md['dsmode'] == self.dsmode: self.seed = ",".join(found) self.metadata = md diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py index fef3d40f..45dd5535 100644 --- a/cloudinit/ssh_util.py +++ b/cloudinit/ssh_util.py @@ -103,10 +103,10 @@ class AuthKeyLineParser(object): elif curc == '"': quoted = not quoted i = i + 1 - + options = ent[0:i] options_lst = [] - + # Now use a csv parser to pull the options # out of the above string that we just found an endpoint for. # @@ -211,7 +211,6 @@ def update_authorized_keys(fname, keys): def setup_user_keys(keys, user, key_prefix, paths): - # Make sure the users .ssh dir is setup accordingly pwent = pwd.getpwnam(user) ssh_dir = os.path.join(pwent.pw_dir, '.ssh') diff --git a/cloudinit/stages.py b/cloudinit/stages.py index ae6e2de5..84a965c2 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -287,7 +287,7 @@ class Init(object): def cloudify(self): # Form the needed options to cloudify our members - return cloud.Cloud(self.datasource, + return cloud.Cloud(self.datasource, self.paths, self.cfg, self.distro, helpers.Runners(self.paths)) @@ -318,7 +318,7 @@ class Init(object): def consume(self, frequency=PER_INSTANCE): cdir = self.paths.get_cpath("handlers") idir = self.paths.get_ipath("handlers") - + # Add the path to the plugins dir to the top of our list for import # instance dir should be read before cloud-dir if cdir and cdir not in sys.path: @@ -417,7 +417,7 @@ class Modules(object): except: util.logexc(LOG, ("Failed loading of datasource" " config object from %s"), self.datasource) - + if self.base_cfg: t_cfgs.append(self.base_cfg) diff --git a/cloudinit/url_helper.py b/cloudinit/url_helper.py index 1c583eba..223278ce 100644 --- a/cloudinit/url_helper.py +++ b/cloudinit/url_helper.py @@ -47,11 +47,11 @@ class UrlResponse(object): @property def contents(self): return self._contents - + @property def headers(self): return self._headers - + def __str__(self): if not self.contents: return '' @@ -66,7 +66,7 @@ class UrlResponse(object): return True else: return False - + def readurl(url, data=None, timeout=None, retries=0, sec_between=1, headers=None): @@ -89,8 +89,8 @@ def readurl(url, data=None, timeout=None, excepts = [] LOG.info(("Attempting to open '%s' with %s attempts" - " (%s retries, timeout=%s) to be performed"), - url, attempts, retries, timeout) + " (%s retries, timeout=%s) to be performed"), + url, attempts, retries, timeout) open_args = {} if timeout is not None: open_args['timeout'] = int(timeout) @@ -112,7 +112,7 @@ def readurl(url, data=None, timeout=None, excepts.append(e) except urllib2.URLError as e: # This can be a message string or - # another exception instance + # another exception instance # (socket.error for remote URLs, OSError for local URLs). if (isinstance(e.reason, (OSError)) and e.reason.errno == errno.ENOENT): @@ -128,7 +128,7 @@ def readurl(url, data=None, timeout=None, # Didn't work out LOG.warn("Failed reading from %s after %s attempts", url, attempts) - + # It must of errored at least once for code # to get here so re-raise the last error LOG.debug("%s errors occured, re-raising the last one", len(excepts)) diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py index b7902d44..4babb8e5 100644 --- a/cloudinit/user_data.py +++ b/cloudinit/user_data.py @@ -65,33 +65,33 @@ class UserDataProcessor(object): # multipart/* are just containers if part.get_content_maintype() == 'multipart': continue - + ctype = None ctype_orig = part.get_content_type() payload = part.get_payload(decode=True) - + if not ctype_orig: ctype_orig = UNDEF_TYPE - + if ctype_orig in TYPE_NEEDED: ctype = handlers.type_from_starts_with(payload) - + if ctype is None: ctype = ctype_orig - + if ctype in INCLUDE_TYPES: self._do_include(payload, append_msg) continue - + if ctype in ARCHIVE_TYPES: self._explode_archive(payload, append_msg) continue - + if 'Content-Type' in base_msg: base_msg.replace_header('Content-Type', ctype) else: base_msg['Content-Type'] = ctype - + self._attach_part(append_msg, part) def _get_include_once_filename(self, entry): @@ -108,8 +108,8 @@ class UserDataProcessor(object): lc_line = line.lower() if lc_line.startswith("#include-once"): line = line[len("#include-once"):].lstrip() - # Every following include will now - # not be refetched.... but will be + # Every following include will now + # not be refetched.... but will be # re-read from a local urlcache (if it worked) include_once_on = True elif lc_line.startswith("#include"): @@ -190,10 +190,10 @@ class UserDataProcessor(object): """ if ATTACHMENT_FIELD not in outer_msg: outer_msg[ATTACHMENT_FIELD] = '0' - + if new_count is not None: outer_msg.replace_header(ATTACHMENT_FIELD, str(new_count)) - + fetched_count = 0 try: fetched_count = int(outer_msg.get(ATTACHMENT_FIELD)) @@ -234,7 +234,3 @@ def convert_string(raw_data, headers=None): msg = MIMEBase(maintype, subtype, *headers) msg.set_payload(data) return msg - - - - diff --git a/cloudinit/util.py b/cloudinit/util.py index 91d20a76..56c01fab 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -35,7 +35,7 @@ import pwd import random import shutil import socket -import string # pylint: disable=W0402 +import string # pylint: disable=W0402 import subprocess import sys import tempfile @@ -153,13 +153,15 @@ def SilentTemporaryFile(**kwargs): # file to unlink has been unlinked elsewhere.. LOG.debug("Created temporary file %s", fh.name) fh.unlink = del_file - # Add a new method that will unlink + + # Add a new method that will unlink # right 'now' but still lets the exit # method attempt to remove it (which will # not throw due to our del file being quiet # about files that are not there) def unlink_now(): fh.unlink(fh.name) + setattr(fh, 'unlink_now', unlink_now) return fh @@ -199,7 +201,7 @@ def is_false_str(val, addons=None): def translate_bool(val, addons=None): if not val: - # This handles empty lists and false and + # This handles empty lists and false and # other things that python believes are false return False # If its already a boolean skip @@ -214,7 +216,6 @@ def rand_str(strlen=32, select_from=None): return "".join([random.choice(select_from) for _x in range(0, strlen)]) - def read_conf(fname): try: return load_yaml(load_file(fname), default={}) @@ -275,7 +276,7 @@ def is_ipv4(instr): def merge_base_cfg(cfgfile, cfg_builtin=None): syscfg = read_conf_with_confd(cfgfile) - + kern_contents = read_cc_from_cmdline() kerncfg = {} if kern_contents: @@ -575,7 +576,7 @@ def load_yaml(blob, default=None, allowed=(dict,)): try: blob = str(blob) LOG.debug(("Attempting to load yaml from string " - "of length %s with allowed root types %s"), + "of length %s with allowed root types %s"), len(blob), allowed) converted = yaml.load(blob) if not isinstance(converted, allowed): @@ -625,7 +626,7 @@ def read_conf_d(confd): # remove anything not ending in '.cfg' confs = [f for f in confs if f.endswith(".cfg")] - + # remove anything not a file confs = [f for f in confs if os.path.isfile(os.path.join(confd, f))] @@ -726,9 +727,9 @@ def get_fqdn_from_hosts(hostname, filename="/etc/hosts"): """ For each host a single line should be present with the following information: - - IP_address canonical_hostname [aliases...] - + + IP_address canonical_hostname [aliases...] + Fields of the entry are separated by any number of blanks and/or tab characters. Text from a "#" character until the end of the line is a comment, and is ignored. Host names may contain only alphanumeric @@ -747,7 +748,7 @@ def get_fqdn_from_hosts(hostname, filename="/etc/hosts"): if not line: continue - # If there there is less than 3 entries + # If there there is less than 3 entries # (IP_address, canonical_hostname, alias) # then ignore this line toks = line.split() @@ -829,7 +830,7 @@ def close_stdin(): os.dup2(fp.fileno(), sys.stdin.fileno()) -def find_devs_with(criteria=None, oformat='device', +def find_devs_with(criteria=None, oformat='device', tag=None, no_cache=False, path=None): """ find devices matching given criteria (via blkid) @@ -841,23 +842,23 @@ def find_devs_with(criteria=None, oformat='device', blk_id_cmd = ['blkid'] options = [] if criteria: - # Search for block devices with tokens named NAME that + # Search for block devices with tokens named NAME that # have the value 'value' and display any devices which are found. # Common values for NAME include TYPE, LABEL, and UUID. # If there are no devices specified on the command line, - # all block devices will be searched; otherwise, + # all block devices will be searched; otherwise, # only search the devices specified by the user. options.append("-t%s" % (criteria)) if tag: # For each (specified) device, show only the tags that match tag. options.append("-s%s" % (tag)) if no_cache: - # If you want to start with a clean cache - # (i.e. don't report devices previously scanned + # If you want to start with a clean cache + # (i.e. don't report devices previously scanned # but not necessarily available at this time), specify /dev/null. options.extend(["-c", "/dev/null"]) if oformat: - # Display blkid's output using the specified format. + # Display blkid's output using the specified format. # The format parameter may be: # full, value, list, device, udev, export options.append('-o%s' % (oformat)) @@ -1104,7 +1105,7 @@ def mounts(): (dev, mp, fstype, opts, _freq, _passno) = mpline.split() except: continue - # If the name of the mount point contains spaces these + # If the name of the mount point contains spaces these # can be escaped as '\040', so undo that.. mp = mp.replace("\\040", " ") mounted[dev] = { -- cgit v1.2.3 From 95e0fa29af3656c1011c41ab0f35dc4e9317269c Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 20 Jun 2012 23:40:00 -0700 Subject: 1. Add an importer function that will search for a given module in a set of search module 'prefixes' that also has a potential set of required attributes. 2. Use this new importer to find the distro class, the userdata handler modules, the config modules and the datasource modules, if none can be found error out accordingly. --- cloudinit/config/__init__.py | 4 ---- cloudinit/distros/__init__.py | 23 ++++++++++------------ cloudinit/handlers/__init__.py | 8 -------- cloudinit/importer.py | 44 +++++++++++++++++++++++++++++++++--------- cloudinit/sources/__init__.py | 34 +++++++++++--------------------- cloudinit/stages.py | 22 +++++++++++++++++---- 6 files changed, 74 insertions(+), 61 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/__init__.py b/cloudinit/config/__init__.py index 02e32462..ab13045f 100644 --- a/cloudinit/config/__init__.py +++ b/cloudinit/config/__init__.py @@ -51,10 +51,6 @@ def fixup_module(mod, def_freq=PER_INSTANCE): freq = mod.frequency if freq and freq not in FREQUENCIES: LOG.warn("Module %s has an unknown frequency %s", mod, freq) - if not hasattr(mod, 'handle'): - def empty_handle(_name, _cfg, _cloud, _log, _args): - pass - setattr(mod, 'handle', empty_handle) if not hasattr(mod, 'distros'): setattr(mod, 'distros', None) return mod diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 25a60c52..e0ef6ee0 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -144,16 +144,13 @@ class Distro(object): return False -def fetch(distro_name, mods=(__name__, )): - mod = None - for m in mods: - mod_name = "%s.%s" % (m, distro_name) - try: - mod = importer.import_module(mod_name) - except ImportError: - pass - if not mod: - raise RuntimeError("No distribution found for distro %s" - % (distro_name)) - distro_cls = getattr(mod, 'Distro') - return distro_cls +def fetch(name): + locs = importer.find_module(name, + ['', __name__], + ['Distro']) + if not locs: + raise ImportError("No distribution found for distro %s" + % (name)) + mod = importer.import_module(locs[0]) + cls = getattr(mod, 'Distro') + return cls diff --git a/cloudinit/handlers/__init__.py b/cloudinit/handlers/__init__.py index d52b1cba..0f7432e5 100644 --- a/cloudinit/handlers/__init__.py +++ b/cloudinit/handlers/__init__.py @@ -202,20 +202,12 @@ def walk(msg, callback, data): def fixup_handler(mod, def_freq=PER_INSTANCE): if not hasattr(mod, "handler_version"): setattr(mod, "handler_version", 1) - if not hasattr(mod, 'list_types'): - def empty_types(): - return [] - setattr(mod, 'list_types', empty_types) if not hasattr(mod, 'frequency'): setattr(mod, 'frequency', def_freq) else: freq = mod.frequency if freq and freq not in FREQUENCIES: LOG.warn("Handler %s has an unknown frequency %s", mod, freq) - if not hasattr(mod, 'handle_part'): - def empty_handler(_data, _ctype, _filename, _payload): - pass - setattr(mod, 'handle_part', empty_handler) return mod diff --git a/cloudinit/importer.py b/cloudinit/importer.py index a36b87bc..71cf2726 100644 --- a/cloudinit/importer.py +++ b/cloudinit/importer.py @@ -23,17 +23,43 @@ import sys from cloudinit import log as logging -from cloudinit import util LOG = logging.getLogger(__name__) -# Simple wrapper that allows us to add more logging in... def import_module(module_name): - try: - LOG.debug("Attempting to import module %s", module_name) - __import__(module_name) - return sys.modules[module_name] - except: - util.logexc(LOG, 'Failed at importing %s', module_name) - raise + __import__(module_name) + return sys.modules[module_name] + + +def find_module(base_name, search_paths, required_attrs=None): + found_places = [] + if not required_attrs: + required_attrs = [] + real_paths = [] + for path in search_paths: + real_path = [] + if path: + real_path.extend(path.split(".")) + real_path.append(base_name) + full_path = '.'.join(real_path) + real_paths.append(full_path) + LOG.debug("Looking for modules %s that have attributes %s", + real_paths, required_attrs) + for full_path in real_paths: + mod = None + try: + mod = import_module(full_path) + except ImportError: + pass + if not mod: + continue + found_attrs = 0 + for attr in required_attrs: + if hasattr(mod, attr): + found_attrs += 1 + if found_attrs == len(required_attrs): + found_places.append(full_path) + LOG.debug("Found %s with attributes %s in %s", base_name, + required_attrs, found_places) + return found_places diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py index 8ab7cf54..42e924b0 100644 --- a/cloudinit/sources/__init__.py +++ b/cloudinit/sources/__init__.py @@ -191,31 +191,19 @@ def list_sources(cfg_list, depends, pkg_list): LOG.info(("Looking for for data source in: %s," " via packages %s that matches dependencies %s"), cfg_list, pkg_list, depends) - for ds_coll in cfg_list: - ds_name = str(ds_coll) + for ds_name in cfg_list: if not ds_name.startswith(DS_PREFIX): ds_name = '%s%s' % (DS_PREFIX, ds_name) - for pkg in pkg_list: - pkg_name = [] - if pkg: - # Any package name given, this affects - # the lookup path - pkg_name.append(str(pkg)) - pkg_name.append(ds_name) - try: - mod = importer.import_module(".".join(pkg_name)) - except ImportError: - continue - lister = getattr(mod, "get_datasource_list", None) - if not lister: - continue - cls_matches = lister(depends) - if not cls_matches: - continue - src_list.extend(cls_matches) - LOG.debug(("Found a match" - " in %s with matches %s"), mod, cls_matches) - break + m_locs = importer.find_module(ds_name, + pkg_list, + ['get_datasource_list']) + for m_loc in m_locs: + mod = importer.import_module(m_loc) + lister = getattr(mod, "get_datasource_list") + matches = lister(depends) + if matches: + src_list.extend(matches) + break return src_list diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 84a965c2..1997301a 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -205,7 +205,7 @@ class Init(object): # Any config provided??? pkg_list = self.cfg.get('datasource_pkg_list') or [] # Add the defaults at the end - for n in [util.obj_name(sources), '']: + for n in ['', util.obj_name(sources)]: if n not in pkg_list: pkg_list.append(n) cfg_list = self.cfg.get('datasource_list') or [] @@ -334,9 +334,17 @@ class Init(object): # Add handlers in cdir potential_handlers = util.find_modules(cdir) - for (fname, modname) in potential_handlers.iteritems(): + for (fname, mod_name) in potential_handlers.iteritems(): try: - mod = handlers.fixup_handler(importer.import_module(modname)) + mod_locs = importer.find_module(mod_name, [''], + ['list_types', + 'handle_part']) + if not mod_locs: + LOG.warn(("Could not find a valid user-data handler" + " named %s in file %s"), mod_name, fname) + continue + mod = importer.import_module(mod_locs[0]) + mod = handlers.fixup_handler(mod) types = c_handlers.register(mod) LOG.debug("Added handler for %s from %s", types, fname) except: @@ -482,7 +490,13 @@ class Modules(object): " has an unknown frequency %s"), raw_name, freq) # Reset it so when ran it will get set to a known value freq = None - mod = config.fixup_module(importer.import_module(mod_name)) + mod_locs = importer.find_module(mod_name, + ['', util.obj_name(config)], + ['handle']) + if not mod_locs: + LOG.warn("Could not find module named %s", mod_name) + continue + mod = config.fixup_module(importer.import_module(mod_locs[0])) mostly_mods.append([mod, raw_name, freq, run_args]) return mostly_mods -- cgit v1.2.3 From 64f1347cb8f2580339d539b7772e7872ed81cae9 Mon Sep 17 00:00:00 2001 From: harlowja Date: Thu, 21 Jun 2012 08:56:36 -0700 Subject: Move to a write location and when writing, also write to the write location, instead of the read-only one --- cloudinit/config/cc_mcollective.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_mcollective.py b/cloudinit/config/cc_mcollective.py index ba5e13ca..3fec6729 100644 --- a/cloudinit/config/cc_mcollective.py +++ b/cloudinit/config/cc_mcollective.py @@ -83,7 +83,7 @@ def handle(name, cfg, cloud, log, _args): mcollective_config.set(cfg_name, o, v) # We got all our config as wanted we'll rename # the previous server.cfg and create our new one - old_fn = "%s.old" % (server_cfg_fn) + old_fn = cloud.paths.join(False, '/etc/mcollective/server.cfg.old') util.rename(server_cfg_fn, old_fn) # Now we got the whole file, write to disk except the section # we added so that config parser won't error out when trying to read. @@ -91,7 +91,8 @@ def handle(name, cfg, cloud, log, _args): # works. Below, we remove the initial 'nullsection' header. contents = mcollective_config.stringify() contents = contents.replace("%s\n" % (section_head), "") - util.write_file(server_cfg_fn, contents, mode=0644) + server_cfg_rw = cloud.paths.join(False, '/etc/mcollective/server.cfg') + util.write_file(server_cfg_rw, contents, mode=0644) # Start mcollective util.subp(['service', 'mcollective', 'start'], capture=False) -- cgit v1.2.3 From 0c83ff5cb81b6de3028cf90e7dd6aba565682dbf Mon Sep 17 00:00:00 2001 From: harlowja Date: Thu, 21 Jun 2012 09:12:16 -0700 Subject: 1. Renames for debug message from 'transform' to 'module' 2. Fixing up more cloud.path.joins found to use the right ro/rw filename --- cloudinit/cloud.py | 4 ++-- cloudinit/config/cc_apt_pipelining.py | 6 ++++-- cloudinit/config/cc_bootcmd.py | 4 ++-- cloudinit/config/cc_byobu.py | 2 +- cloudinit/config/cc_ca_certs.py | 2 +- cloudinit/config/cc_chef.py | 2 +- cloudinit/config/cc_disable_ec2_metadata.py | 2 +- cloudinit/config/cc_foo.py | 2 +- cloudinit/config/cc_keys_to_console.py | 2 +- cloudinit/config/cc_landscape.py | 11 ++++++++--- cloudinit/config/cc_locale.py | 2 +- cloudinit/config/cc_mcollective.py | 2 +- cloudinit/config/cc_mounts.py | 3 ++- cloudinit/config/cc_phone_home.py | 2 +- cloudinit/config/cc_puppet.py | 12 +++++++----- cloudinit/config/cc_resizefs.py | 2 +- cloudinit/config/cc_rightscale_userdata.py | 6 +++--- cloudinit/config/cc_rsyslog.py | 2 +- cloudinit/config/cc_runcmd.py | 2 +- cloudinit/config/cc_salt_minion.py | 10 +++++----- cloudinit/config/cc_scripts_per_boot.py | 2 +- cloudinit/config/cc_scripts_per_instance.py | 2 +- cloudinit/config/cc_scripts_per_once.py | 2 +- cloudinit/config/cc_scripts_user.py | 2 +- cloudinit/config/cc_set_hostname.py | 2 +- cloudinit/config/cc_set_passwords.py | 4 +++- cloudinit/config/cc_ssh_import_id.py | 4 ++-- cloudinit/config/cc_timezone.py | 2 +- cloudinit/config/cc_update_etc_hosts.py | 2 +- cloudinit/config/cc_update_hostname.py | 2 +- cloudinit/stages.py | 2 +- 31 files changed, 59 insertions(+), 47 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/cloud.py b/cloudinit/cloud.py index 6cdcb76a..22d9167e 100644 --- a/cloudinit/cloud.py +++ b/cloudinit/cloud.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) # This class is the high level wrapper that provides # access to cloud-init objects without exposing the stage objects -# to handler and or transform manipulation. It allows for cloud +# to handler and or module manipulation. It allows for cloud # init to restrict what those types of user facing code may see # and or adjust (which helps avoid code messing with each other) # @@ -47,7 +47,7 @@ class Cloud(object): self._cfg = cfg self._runners = runners - # If a transform manipulates logging or logging services + # If a 'user' manipulates logging or logging services # it is typically useful to cause the logging to be # setup again. def cycle_logging(self): diff --git a/cloudinit/config/cc_apt_pipelining.py b/cloudinit/config/cc_apt_pipelining.py index f460becb..3426099e 100644 --- a/cloudinit/config/cc_apt_pipelining.py +++ b/cloudinit/config/cc_apt_pipelining.py @@ -25,6 +25,9 @@ distros = ['ubuntu', 'debian'] DEFAULT_FILE = "/etc/apt/apt.conf.d/90cloud-init-pipelining" +APT_PIPE_TPL = ("//Written by cloud-init per 'apt_pipelining'\n" + 'Acquire::http::Pipeline-Depth "%s";\n') + # Acquire::http::Pipeline-Depth can be a value # from 0 to 5 indicating how many outstanding requests APT should send. # A value of zero MUST be specified if the remote host does not properly linger @@ -49,8 +52,7 @@ def handle(_name, cfg, cloud, log, _args): def write_apt_snippet(cloud, setting, log, f_name): """ Writes f_name with apt pipeline depth 'setting' """ - file_contents = ("//Written by cloud-init per 'apt_pipelining'\n" - 'Acquire::http::Pipeline-Depth "%s";\n') % (setting) + file_contents = APT_PIPE_TPL % (setting) util.write_file(cloud.paths.join(False, f_name), file_contents) diff --git a/cloudinit/config/cc_bootcmd.py b/cloudinit/config/cc_bootcmd.py index 635e3a1f..89ccf3f1 100644 --- a/cloudinit/config/cc_bootcmd.py +++ b/cloudinit/config/cc_bootcmd.py @@ -30,7 +30,7 @@ frequency = PER_ALWAYS def handle(name, cfg, cloud, log, _args): if "bootcmd" not in cfg: - log.debug(("Skipping transform named %s," + log.debug(("Skipping module named %s," " no 'bootcmd' key in configuration"), name) return @@ -52,5 +52,5 @@ def handle(name, cfg, cloud, log, _args): util.subp(cmd, env=env, capture=False) except: util.logexc(log, - ("Failed to run bootcmd transform %s"), name) + ("Failed to run bootcmd module %s"), name) raise diff --git a/cloudinit/config/cc_byobu.py b/cloudinit/config/cc_byobu.py index 741aa934..38586174 100644 --- a/cloudinit/config/cc_byobu.py +++ b/cloudinit/config/cc_byobu.py @@ -30,7 +30,7 @@ def handle(name, cfg, _cloud, log, args): value = util.get_cfg_option_str(cfg, "byobu_by_default", "") if not value: - log.debug("Skipping transform named %s, no 'byobu' values found", name) + log.debug("Skipping module named %s, no 'byobu' values found", name) return if value == "user" or value == "system": diff --git a/cloudinit/config/cc_ca_certs.py b/cloudinit/config/cc_ca_certs.py index 56c41561..3221a587 100644 --- a/cloudinit/config/cc_ca_certs.py +++ b/cloudinit/config/cc_ca_certs.py @@ -75,7 +75,7 @@ def handle(name, cfg, cloud, log, _args): """ # If there isn't a ca-certs section in the configuration don't do anything if "ca-certs" not in cfg: - log.debug(("Skipping transform named %s," + log.debug(("Skipping module named %s," " no 'ca-certs' key in configuration"), name) return diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py index 74af2a7e..d8bd85f8 100644 --- a/cloudinit/config/cc_chef.py +++ b/cloudinit/config/cc_chef.py @@ -31,7 +31,7 @@ def handle(name, cfg, cloud, log, _args): # If there isn't a chef key in the configuration don't do anything if 'chef' not in cfg: - log.debug(("Skipping transform named %s," + log.debug(("Skipping module named %s," " no 'chef' key in configuration"), name) return chef_cfg = cfg['chef'] diff --git a/cloudinit/config/cc_disable_ec2_metadata.py b/cloudinit/config/cc_disable_ec2_metadata.py index 62cca7cc..a7c6a75b 100644 --- a/cloudinit/config/cc_disable_ec2_metadata.py +++ b/cloudinit/config/cc_disable_ec2_metadata.py @@ -32,5 +32,5 @@ def handle(name, cfg, _cloud, log, _args): if disabled: util.subp(REJECT_CMD) else: - log.debug(("Skipping transform named %s," + log.debug(("Skipping module named %s," " disabling the ec2 route not enabled"), name) diff --git a/cloudinit/config/cc_foo.py b/cloudinit/config/cc_foo.py index e81e7faa..95aab4dd 100644 --- a/cloudinit/config/cc_foo.py +++ b/cloudinit/config/cc_foo.py @@ -49,4 +49,4 @@ frequency = PER_INSTANCE def handle(name, _cfg, _cloud, log, _args): - log.debug("Hi from transform %s", name) + log.debug("Hi from module %s", name) diff --git a/cloudinit/config/cc_keys_to_console.py b/cloudinit/config/cc_keys_to_console.py index a8fb3ba7..d4c877f7 100644 --- a/cloudinit/config/cc_keys_to_console.py +++ b/cloudinit/config/cc_keys_to_console.py @@ -31,7 +31,7 @@ HELPER_TOOL = '/usr/lib/cloud-init/write-ssh-key-fingerprints' def handle(name, cfg, cloud, log, _args): if not os.path.exists(HELPER_TOOL): - log.warn(("Unable to activate transform %s," + log.warn(("Unable to activate module %s," " helper tool not found at %s"), name, HELPER_TOOL) return diff --git a/cloudinit/config/cc_landscape.py b/cloudinit/config/cc_landscape.py index 599276a7..d45c9203 100644 --- a/cloudinit/config/cc_landscape.py +++ b/cloudinit/config/cc_landscape.py @@ -56,7 +56,7 @@ def handle(name, cfg, cloud, log, _args): """ if not ConfigObj: log.warn(("'ConfigObj' support not available," - " running transform %s disabled"), name) + " running module %s disabled"), name) return ls_cloudcfg = cfg.get("landscape", {}) @@ -66,9 +66,14 @@ def handle(name, cfg, cloud, log, _args): " but not a dictionary type," " is a %s instead"), util.obj_name(ls_cloudcfg)) - lsc_client_fn = cloud.paths.join(True, LSC_CLIENT_CFG_FILE) - merged = merge_together([LSC_BUILTIN_CFG, lsc_client_fn, ls_cloudcfg]) + merge_data = [ + LSC_BUILTIN_CFG, + cloud.paths.join(True, LSC_CLIENT_CFG_FILE), + ls_cloudcfg, + ] + merged = merge_together(merge_data) + lsc_client_fn = cloud.paths.join(False, LSC_CLIENT_CFG_FILE) lsc_dir = cloud.paths.join(False, os.path.dirname(lsc_client_fn)) if not os.path.isdir(lsc_dir): util.ensure_dir(lsc_dir) diff --git a/cloudinit/config/cc_locale.py b/cloudinit/config/cc_locale.py index 7f273123..3fb4c5d9 100644 --- a/cloudinit/config/cc_locale.py +++ b/cloudinit/config/cc_locale.py @@ -49,7 +49,7 @@ def handle(name, cfg, cloud, log, args): "/etc/default/locale") if not locale: - log.debug(("Skipping transform named %s, " + log.debug(("Skipping module named %s, " "no 'locale' configuration found"), name) return diff --git a/cloudinit/config/cc_mcollective.py b/cloudinit/config/cc_mcollective.py index 3fec6729..36a4cade 100644 --- a/cloudinit/config/cc_mcollective.py +++ b/cloudinit/config/cc_mcollective.py @@ -32,7 +32,7 @@ def handle(name, cfg, cloud, log, _args): # If there isn't a mcollective key in the configuration don't do anything if 'mcollective' not in cfg: - log.debug(("Skipping transform named %s, " + log.debug(("Skipping module named %s, " "no 'mcollective' key in configuration"), name) return diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py index ab097c2a..d3dcf7af 100644 --- a/cloudinit/config/cc_mounts.py +++ b/cloudinit/config/cc_mounts.py @@ -188,8 +188,9 @@ def handle(_name, cfg, cloud, log, _args): util.logexc(log, "Activating swap via 'swapon -a' failed") for d in dirs: + real_dir = cloud.paths.join(False, d) try: - util.ensure_dir(cloud.paths.join(False, d)) + util.ensure_dir(real_dir) except: util.logexc(log, "Failed to make '%s' config-mount", d) diff --git a/cloudinit/config/cc_phone_home.py b/cloudinit/config/cc_phone_home.py index dcb07b66..d929eb64 100644 --- a/cloudinit/config/cc_phone_home.py +++ b/cloudinit/config/cc_phone_home.py @@ -53,7 +53,7 @@ def handle(name, cfg, cloud, log, args): ph_cfg = cfg['phone_home'] if 'url' not in ph_cfg: - log.warn(("Skipping transform named %s, " + log.warn(("Skipping module named %s, " "no 'url' found in 'phone_home' configuration"), name) return diff --git a/cloudinit/config/cc_puppet.py b/cloudinit/config/cc_puppet.py index 5154efba..467c1496 100644 --- a/cloudinit/config/cc_puppet.py +++ b/cloudinit/config/cc_puppet.py @@ -31,7 +31,7 @@ from cloudinit import util def handle(name, cfg, cloud, log, _args): # If there isn't a puppet key in the configuration don't do anything if 'puppet' not in cfg: - log.debug(("Skipping transform named %s," + log.debug(("Skipping module named %s," " no 'puppet' configuration found"), name) return @@ -43,7 +43,7 @@ def handle(name, cfg, cloud, log, _args): # ... and then update the puppet configuration if 'conf' in puppet_cfg: # Add all sections from the conf object to puppet.conf - puppet_conf_fn = cloud.paths.join(False, '/etc/puppet/puppet.conf') + puppet_conf_fn = cloud.paths.join(True, '/etc/puppet/puppet.conf') contents = util.load_file(puppet_conf_fn) # Create object for reading puppet.conf values puppet_config = helpers.DefaultingConfigParser() @@ -89,9 +89,11 @@ def handle(name, cfg, cloud, log, _args): puppet_config.set(cfg_name, o, v) # We got all our config as wanted we'll rename # the previous puppet.conf and create our new one - puppet_conf_old_fn = "%s.old" % (puppet_conf_fn) - util.rename(puppet_conf_fn, puppet_conf_old_fn) - util.write_file(puppet_conf_fn, puppet_config.stringify()) + conf_old_fn = cloud.paths.join(False, + '/etc/puppet/puppet.conf.old') + util.rename(puppet_conf_fn, conf_old_fn) + puppet_conf_rw = cloud.paths.join(False, '/etc/puppet/puppet.conf') + util.write_file(puppet_conf_rw, puppet_config.stringify()) # Set puppet to automatically start if os.path.exists('/etc/default/puppet'): diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py index c019989e..7e1428e9 100644 --- a/cloudinit/config/cc_resizefs.py +++ b/cloudinit/config/cc_resizefs.py @@ -69,7 +69,7 @@ def handle(name, cfg, cloud, log, args): resize_root = util.get_cfg_option_str(cfg, "resize_rootfs", True) if not util.translate_bool(resize_root): - log.debug("Skipping transform named %s, resizing disabled", name) + log.debug("Skipping module named %s, resizing disabled", name) return # TODO is the directory ok to be used?? diff --git a/cloudinit/config/cc_rightscale_userdata.py b/cloudinit/config/cc_rightscale_userdata.py index 8385e281..7a134569 100644 --- a/cloudinit/config/cc_rightscale_userdata.py +++ b/cloudinit/config/cc_rightscale_userdata.py @@ -53,13 +53,13 @@ def handle(name, _cfg, cloud, log, _args): try: ud = cloud.get_userdata_raw() except: - log.warn("Failed to get raw userdata in transform %s", name) + log.warn("Failed to get raw userdata in module %s", name) return try: mdict = parse_qs(ud) if not mdict or not MY_HOOKNAME in mdict: - log.debug(("Skipping transform %s, " + log.debug(("Skipping module %s, " "did not find %s in parsed" " raw userdata"), name, MY_HOOKNAME) return @@ -73,7 +73,7 @@ def handle(name, _cfg, cloud, log, _args): # These will eventually be then ran by the cc_scripts_user # TODO: maybe this should just be a new user data handler?? - # Instead of a late transform that acts like a user data handler? + # Instead of a late module that acts like a user data handler? scripts_d = cloud.get_ipath_cur('scripts') urls = mdict[MY_HOOKNAME] for (i, url) in enumerate(urls): diff --git a/cloudinit/config/cc_rsyslog.py b/cloudinit/config/cc_rsyslog.py index f2c1de1e..78327526 100644 --- a/cloudinit/config/cc_rsyslog.py +++ b/cloudinit/config/cc_rsyslog.py @@ -36,7 +36,7 @@ def handle(name, cfg, cloud, log, _args): # process 'rsyslog' if not 'rsyslog' in cfg: - log.debug(("Skipping transform named %s," + log.debug(("Skipping module named %s," " no 'rsyslog' key in configuration"), name) return diff --git a/cloudinit/config/cc_runcmd.py b/cloudinit/config/cc_runcmd.py index f121484b..65064cfb 100644 --- a/cloudinit/config/cc_runcmd.py +++ b/cloudinit/config/cc_runcmd.py @@ -25,7 +25,7 @@ from cloudinit import util def handle(name, cfg, cloud, log, _args): if "runcmd" not in cfg: - log.debug(("Skipping transform named %s," + log.debug(("Skipping module named %s," " no 'runcmd' key in configuration"), name) return diff --git a/cloudinit/config/cc_salt_minion.py b/cloudinit/config/cc_salt_minion.py index 986e6db6..ff90d07a 100644 --- a/cloudinit/config/cc_salt_minion.py +++ b/cloudinit/config/cc_salt_minion.py @@ -24,7 +24,7 @@ from cloudinit import util def handle(name, cfg, cloud, log, _args): # If there isn't a salt key in the configuration don't do anything if 'salt_minion' not in cfg: - log.debug(("Skipping transform named %s," + log.debug(("Skipping module named %s," " no 'salt_minion' key in configuration"), name) return @@ -34,8 +34,8 @@ def handle(name, cfg, cloud, log, _args): cloud.distro.install_packages(["salt"]) # Ensure we can configure files at the right dir - config_dir = salt_cfg.get("config_dir", '/etc/salt') - config_dir = cloud.paths.join(False, config_dir) + config_dir = cloud.paths.join(False, salt_cfg.get("config_dir", + '/etc/salt')) util.ensure_dir(config_dir) # ... and then update the salt configuration @@ -47,8 +47,8 @@ def handle(name, cfg, cloud, log, _args): # ... copy the key pair if specified if 'public_key' in salt_cfg and 'private_key' in salt_cfg: - pki_dir = salt_cfg.get('pki_dir', '/etc/salt/pki') - pki_dir = cloud.paths.join(pki_dir) + pki_dir = cloud.paths.join(False, salt_cfg.get('pki_dir', + '/etc/salt/pki')) with util.umask(077): util.ensure_dir(pki_dir) pub_name = os.path.join(pki_dir, 'minion.pub') diff --git a/cloudinit/config/cc_scripts_per_boot.py b/cloudinit/config/cc_scripts_per_boot.py index d3c47442..42b987eb 100644 --- a/cloudinit/config/cc_scripts_per_boot.py +++ b/cloudinit/config/cc_scripts_per_boot.py @@ -36,6 +36,6 @@ def handle(name, _cfg, cloud, log, _args): try: util.runparts(runparts_path) except: - log.warn("Failed to run transform %s (%s in %s)", + log.warn("Failed to run module %s (%s in %s)", name, SCRIPT_SUBDIR, runparts_path) raise diff --git a/cloudinit/config/cc_scripts_per_instance.py b/cloudinit/config/cc_scripts_per_instance.py index 8e428ac2..b5d71c13 100644 --- a/cloudinit/config/cc_scripts_per_instance.py +++ b/cloudinit/config/cc_scripts_per_instance.py @@ -36,6 +36,6 @@ def handle(name, _cfg, cloud, log, _args): try: util.runparts(runparts_path) except: - log.warn("Failed to run transform %s (%s in %s)", + log.warn("Failed to run module %s (%s in %s)", name, SCRIPT_SUBDIR, runparts_path) raise diff --git a/cloudinit/config/cc_scripts_per_once.py b/cloudinit/config/cc_scripts_per_once.py index e7a29a44..d77d36d5 100644 --- a/cloudinit/config/cc_scripts_per_once.py +++ b/cloudinit/config/cc_scripts_per_once.py @@ -36,6 +36,6 @@ def handle(name, _cfg, cloud, log, _args): try: util.runparts(runparts_path) except: - log.warn("Failed to run transform %s (%s in %s)", + log.warn("Failed to run module %s (%s in %s)", name, SCRIPT_SUBDIR, runparts_path) raise diff --git a/cloudinit/config/cc_scripts_user.py b/cloudinit/config/cc_scripts_user.py index 1ff05aae..5c53014f 100644 --- a/cloudinit/config/cc_scripts_user.py +++ b/cloudinit/config/cc_scripts_user.py @@ -37,6 +37,6 @@ def handle(name, _cfg, cloud, log, _args): try: util.runparts(runparts_path) except: - log.warn("Failed to run transform %s (%s in %s)", + log.warn("Failed to run module %s (%s in %s)", name, SCRIPT_SUBDIR, runparts_path) raise diff --git a/cloudinit/config/cc_set_hostname.py b/cloudinit/config/cc_set_hostname.py index 3ac8a8fa..b0f27ebf 100644 --- a/cloudinit/config/cc_set_hostname.py +++ b/cloudinit/config/cc_set_hostname.py @@ -24,7 +24,7 @@ from cloudinit import util def handle(name, cfg, cloud, log, _args): if util.get_cfg_option_bool(cfg, "preserve_hostname", False): log.debug(("Configuration option 'preserve_hostname' is set," - " not setting the hostname in transform %s"), name) + " not setting the hostname in module %s"), name) return (hostname, _fqdn) = util.get_hostname_fqdn(cfg, cloud) diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index ce17f357..eb68ddfe 100644 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -106,7 +106,9 @@ def handle(_name, cfg, cloud, log, args): replacement = "PasswordAuthentication %s" % (pw_auth) # See http://linux.die.net/man/5/sshd_config - old_lines = util.load_file('/etc/ssh/sshd_config').splitlines() + conf_fn = cloud.paths.join(True, '/etc/ssh/sshd_config') + # Todo: use the common ssh_util function for this parsing... + old_lines = util.load_file(conf_fn).splitlines() for i, line in enumerate(old_lines): if not line.strip() or line.startswith("#"): new_lines.append(line) diff --git a/cloudinit/config/cc_ssh_import_id.py b/cloudinit/config/cc_ssh_import_id.py index d57e4665..c58b28ec 100644 --- a/cloudinit/config/cc_ssh_import_id.py +++ b/cloudinit/config/cc_ssh_import_id.py @@ -36,11 +36,11 @@ def handle(name, cfg, _cloud, log, args): ids = util.get_cfg_option_list(cfg, "ssh_import_id", []) if len(ids) == 0: - log.debug("Skipping transform named %s, no ids found to import", name) + log.debug("Skipping module named %s, no ids found to import", name) return if not user: - log.debug("Skipping transform named %s, no user found to import", name) + log.debug("Skipping module named %s, no user found to import", name) return cmd = ["sudo", "-Hu", user, "ssh-import-id"] + ids diff --git a/cloudinit/config/cc_timezone.py b/cloudinit/config/cc_timezone.py index 747c436c..b9eb85b2 100644 --- a/cloudinit/config/cc_timezone.py +++ b/cloudinit/config/cc_timezone.py @@ -32,7 +32,7 @@ def handle(name, cfg, cloud, log, args): timezone = util.get_cfg_option_str(cfg, "timezone", False) if not timezone: - log.debug("Skipping transform named %s, no 'timezone' specified", name) + log.debug("Skipping module named %s, no 'timezone' specified", name) return # Let the distro handle settings its timezone diff --git a/cloudinit/config/cc_update_etc_hosts.py b/cloudinit/config/cc_update_etc_hosts.py index 75615db1..6820ac4f 100644 --- a/cloudinit/config/cc_update_etc_hosts.py +++ b/cloudinit/config/cc_update_etc_hosts.py @@ -57,4 +57,4 @@ def handle(name, cfg, cloud, log, _args): cloud.distro.update_etc_hosts(hostname, fqdn) else: log.debug(("Configuration option 'manage_etc_hosts' is not set," - " not managing /etc/hosts in transform %s"), name) + " not managing /etc/hosts in module %s"), name) diff --git a/cloudinit/config/cc_update_hostname.py b/cloudinit/config/cc_update_hostname.py index 58444fab..b84a1a06 100644 --- a/cloudinit/config/cc_update_hostname.py +++ b/cloudinit/config/cc_update_hostname.py @@ -29,7 +29,7 @@ frequency = PER_ALWAYS def handle(name, cfg, cloud, log, _args): if util.get_cfg_option_bool(cfg, "preserve_hostname", False): log.debug(("Configuration option 'preserve_hostname' is set," - " not updating the hostname in transform %s"), name) + " not updating the hostname in module %s"), name) return (hostname, _fqdn) = util.get_hostname_fqdn(cfg, cloud) diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 1997301a..9f28c2e8 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -518,7 +518,7 @@ class Modules(object): " but not on %s distro. It may or may not work" " correctly."), name, worked_distros, d_name) # Deep copy the config so that modules can't alter it - # Use the transforms logger and not our own + # Use the configs logger and not our own func_args = [name, copy.deepcopy(self.cfg), cc, config.LOG, args] # Mark it as having started running -- cgit v1.2.3 From 081d0cb81213f69a51cbcce9063d2e0792bb5e34 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 21 Jun 2012 12:30:32 -0700 Subject: Simplify sshd config loading to make it use one set of functions/objects/classes --- cloudinit/config/cc_set_passwords.py | 45 ++++++++++++++------------------- cloudinit/ssh_util.py | 49 +++++++++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 32 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index eb68ddfe..5b72224b 100644 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -20,6 +20,7 @@ import sys +from cloudinit import ssh_util from cloudinit import util from string import letters, digits # pylint: disable=W0402 @@ -101,39 +102,31 @@ def handle(_name, cfg, cloud, log, args): pw_auth = 'no' if change_pwauth: - new_lines = [] replaced_auth = False - replacement = "PasswordAuthentication %s" % (pw_auth) - - # See http://linux.die.net/man/5/sshd_config - conf_fn = cloud.paths.join(True, '/etc/ssh/sshd_config') - # Todo: use the common ssh_util function for this parsing... - old_lines = util.load_file(conf_fn).splitlines() - for i, line in enumerate(old_lines): - if not line.strip() or line.startswith("#"): - new_lines.append(line) - continue - splitup = line.split(None, 1) - if len(splitup) <= 1: - new_lines.append(line) - continue - (cmd, args) = splitup + + # See: man sshd_config + conf_fn = cloud.paths.join(True, ssh_util.DEF_SSHD_CFG) + old_lines = ssh_util.parse_ssh_config(conf_fn) + new_lines = [] + i = 0 + for (i, line) in enumerate(old_lines): # Keywords are case-insensitive and arguments are case-sensitive - cmd = cmd.lower().strip() - if cmd == 'passwordauthentication': - log.debug("Replacing auth line %s with %s", i + 1, replacement) + if line.key == 'passwordauthentication': + log.debug("Replacing auth line %s with %s", i + 1, pw_auth) replaced_auth = True - new_lines.append(replacement) - else: - new_lines.append(line) + line.value = pw_auth + new_lines.append(line) if not replaced_auth: - log.debug("Adding new auth line %s", replacement) + log.debug("Adding new auth line %s", i + 1) replaced_auth = True - new_lines.append(replacement) + new_lines.append(ssh_util.SshdConfigLine('', + 'PasswordAuthentication', + pw_auth)) - util.write_file(cloud.paths.join(False, '/etc/ssh/sshd_config'), - "\n".join(new_lines)) + lines = [str(e) for e in new_lines] + ssh_rw_fn = cloud.paths.join(False, ssh_util.DEF_SSHD_CFG) + util.write_file(ssh_rw_fn, "\n".join(lines)) try: cmd = ['service'] diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py index 45dd5535..fc8b9b3d 100644 --- a/cloudinit/ssh_util.py +++ b/cloudinit/ssh_util.py @@ -29,6 +29,8 @@ from cloudinit import log as logging from cloudinit import util LOG = logging.getLogger(__name__) + +# See: man sshd_config DEF_SSHD_CFG = "/etc/ssh/sshd_config" @@ -233,7 +235,7 @@ def setup_user_keys(keys, user, key_prefix, paths): # The following tokens are defined: %% is replaced by a literal # '%', %h is replaced by the home directory of the user being # authenticated and %u is replaced by the username of that user. - ssh_cfg = parse_ssh_config(sshd_conf_fn) + ssh_cfg = parse_ssh_config_map(sshd_conf_fn) akeys = ssh_cfg.get("authorizedkeysfile", '') akeys = akeys.strip() if not akeys: @@ -258,19 +260,54 @@ def setup_user_keys(keys, user, key_prefix, paths): util.chownbyid(authorized_keys, pwent.pw_uid, pwent.pw_gid) +class SshdConfigLine(object): + def __init__(self, line, k=None, v=None): + self.line = line + self._key = k + self.value = v + + @property + def key(self): + if self._key is None: + return None + # Keywords are case-insensitive + return self._key.lower() + + def __str__(self): + if self._key is None: + return str(self.line) + else: + v = str(self._key) + if self.value: + v += " " + str(self.value) + return v + + def parse_ssh_config(fname): + # See: man sshd_config # The file contains keyword-argument pairs, one per line. # Lines starting with '#' and empty lines are interpreted as comments. # Note: key-words are case-insensitive and arguments are case-sensitive - ret = {} + lines = [] if not os.path.isfile(fname): - return ret + return lines for line in util.load_file(fname).splitlines(): line = line.strip() if not line or line.startswith("#"): + lines.append(SshdConfigLine(line)) continue (key, val) = line.split(None, 1) - key = key.strip().lower() - if key: - ret[key] = val + lines.append(SshdConfigLine(line, key, val)) + return lines + + +def parse_ssh_config_map(fname): + lines = parse_ssh_config(fname) + if not lines: + return {} + ret = {} + for line in lines: + if not line.key: + continue + ret[line.key] = line.value return ret -- cgit v1.2.3 From 421556807fe27e6443c67410634f8a05ab84fc37 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 21 Jun 2012 17:36:09 -0700 Subject: Testing is easier if we just pass the paths object, instead of the full cloud object here. --- cloudinit/config/cc_ca_certs.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_ca_certs.py b/cloudinit/config/cc_ca_certs.py index 3221a587..266a2d84 100644 --- a/cloudinit/config/cc_ca_certs.py +++ b/cloudinit/config/cc_ca_certs.py @@ -33,7 +33,7 @@ def update_ca_certs(): util.subp(["update-ca-certificates"]) -def add_ca_certs(cloud, certs): +def add_ca_certs(paths, certs): """ Adds certificates to the system. To actually apply the new certificates you must also call L{update_ca_certs}. @@ -44,21 +44,21 @@ def add_ca_certs(cloud, certs): # First ensure they are strings... cert_file_contents = "\n".join([str(c) for c in certs]) cert_file_fullpath = os.path.join(CA_CERT_PATH, CA_CERT_FILENAME) - cert_file_fullpath = cloud.paths.join(False, cert_file_fullpath) + cert_file_fullpath = paths.join(False, cert_file_fullpath) util.write_file(cert_file_fullpath, cert_file_contents, mode=0644) # Append cert filename to CA_CERT_CONFIG file. - util.write_file(cloud.paths.join(False, CA_CERT_CONFIG), + util.write_file(paths.join(False, CA_CERT_CONFIG), "\n%s" % CA_CERT_FILENAME, omode="ab") -def remove_default_ca_certs(cloud): +def remove_default_ca_certs(paths): """ Removes all default trusted CA certificates from the system. To actually apply the change you must also call L{update_ca_certs}. """ - util.delete_dir_contents(cloud.paths.join(False, CA_CERT_PATH)) - util.delete_dir_contents(cloud.paths.join(False, CA_CERT_SYSTEM_PATH)) - util.write_file(cloud.paths.join(False, CA_CERT_CONFIG), "", mode=0644) + util.delete_dir_contents(paths.join(False, CA_CERT_PATH)) + util.delete_dir_contents(paths.join(False, CA_CERT_SYSTEM_PATH)) + util.write_file(paths.join(False, CA_CERT_CONFIG), "", mode=0644) debconf_sel = "ca-certificates ca-certificates/trust_new_crts select no" util.subp(('debconf-set-selections', '-'), debconf_sel) @@ -85,14 +85,14 @@ def handle(name, cfg, cloud, log, _args): # default trusted CA certs first. if ca_cert_cfg.get("remove-defaults", False): log.debug("Removing default certificates") - remove_default_ca_certs(cloud) + remove_default_ca_certs(cloud.paths) # If we are given any new trusted CA certs to add, add them. if "trusted" in ca_cert_cfg: trusted_certs = util.get_cfg_option_list(ca_cert_cfg, "trusted") if trusted_certs: log.debug("Adding %d certificates" % len(trusted_certs)) - add_ca_certs(cloud, trusted_certs) + add_ca_certs(cloud.paths, trusted_certs) # Update the system with the new cert configuration. log.debug("Updating certificates") -- cgit v1.2.3 From c49507a221464ce0f9747d4371f8e3d1d1b30abd Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 21 Jun 2012 19:46:16 -0700 Subject: Updated so that the locale that is being written out currently in 'cc_locale' now will be done by the distro classes (since its not the same for rhel and ubuntu). Remove the template also since it will just be created by the ubuntu distro class (its just one line). --- cloudinit/config/cc_locale.py | 25 ++----------------------- cloudinit/distros/__init__.py | 4 ++++ cloudinit/distros/rhel.py | 31 +++++++++++++++++++++++++++++++ cloudinit/distros/ubuntu.py | 11 +++++++++++ templates/default-locale.tmpl | 1 - 5 files changed, 48 insertions(+), 24 deletions(-) delete mode 100644 templates/default-locale.tmpl (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_locale.py b/cloudinit/config/cc_locale.py index 3fb4c5d9..6feaae9d 100644 --- a/cloudinit/config/cc_locale.py +++ b/cloudinit/config/cc_locale.py @@ -18,41 +18,20 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os - -from cloudinit import templater from cloudinit import util -def apply_locale(locale, cfgfile, cloud, log): - # TODO this command might not work on RH... - if os.path.exists('/usr/sbin/locale-gen'): - util.subp(['locale-gen', locale], capture=False) - if os.path.exists('/usr/sbin/update-locale'): - util.subp(['update-locale', locale], capture=False) - if not cfgfile: - return - template_fn = cloud.get_template_filename('default-locale') - if not template_fn: - log.warn("No template filename found to write to %s", cfgfile) - else: - templater.render_to_file(template_fn, cfgfile, {'locale': locale}) - - def handle(name, cfg, cloud, log, args): if len(args) != 0: locale = args[0] else: locale = util.get_cfg_option_str(cfg, "locale", cloud.get_locale()) - locale_cfgfile = util.get_cfg_option_str(cfg, "locale_configfile", - "/etc/default/locale") - if not locale: log.debug(("Skipping module named %s, " "no 'locale' configuration found"), name) return log.debug("Setting locale to %s", locale) - - apply_locale(locale, locale_cfgfile, cloud, log) + locale_cfgfile = util.get_cfg_option_str(cfg, "locale_configfile") + cloud.distro.apply_locale(locale, locale_cfgfile) diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index f9a97da7..c324ddf6 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -82,6 +82,10 @@ class Distro(object): return self._interface_action('up') return False + @abc.abstractmethod + def apply_locale(self, locale, out_fn=None): + raise NotImplementedError() + @abc.abstractmethod def set_timezone(self, tz): raise NotImplementedError() diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index 5cbefa6e..df63d559 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -30,6 +30,10 @@ LOG = logging.getLogger(__name__) NETWORK_FN_TPL = '/etc/sysconfig/network-scripts/ifcfg-%s' +# See: http://tiny.cc/6r99fw +# For what alot of these files that are being written +# are and the format of them + class Distro(distros.Distro): @@ -83,6 +87,33 @@ class Distro(distros.Distro): LOG.debug("Setting hostname to %s", hostname) util.subp(['hostname', hostname]) + def apply_locale(self, locale, out_fn=None): + if not out_fn: + out_fn = self._paths.join(False, '/etc/sysconfig/i18n') + ro_fn = self._paths.join(True, '/etc/sysconfig/i18n') + # Update the 'LANG' if it exists instead of appending + old_contents = self._read_conf(ro_fn) + adjusted = False + new_contents = [] + for entry in old_contents: + if not entry: + continue + if len(entry) == 1: + new_contents.append(entry[0]) + continue + (cmd, args) = entry + cmd_c = cmd.strip().lower() + if cmd_c == 'lang': + args = "%s" % (locale) + adjusted = True + new_contents.append("=".join([cmd, args])) + # Guess not found, append it + if not adjusted: + new_contents.append("# Added by cloud-init") + new_contents.append('LANG="%s"' % (locale)) + contents = "\n".join(new_contents) + util.write_file(out_fn, contents, 0644) + def _write_hostname(self, hostname, out_fn): old_contents = [] if os.path.isfile(out_fn): diff --git a/cloudinit/distros/ubuntu.py b/cloudinit/distros/ubuntu.py index 15af2e7f..e8b95374 100644 --- a/cloudinit/distros/ubuntu.py +++ b/cloudinit/distros/ubuntu.py @@ -41,6 +41,17 @@ class Distro(distros.Distro): # should only happen say once per instance...) self._runner = helpers.Runners(paths) + def apply_locale(self, locale, out_fn=None): + if not out_fn: + out_fn = self._paths.join(False, '/etc/default/locale') + util.subp(['locale-gen', locale], capture=False) + util.subp(['update-locale', locale], capture=False) + contents = [ + "# Created by cloud-init", + 'LANG="%s"' % (locale), + ] + util.write_file(out_fn, "\n".join(contents)) + def install_packages(self, pkglist): self._update_package_sources() self.package_command('install', pkglist) diff --git a/templates/default-locale.tmpl b/templates/default-locale.tmpl deleted file mode 100644 index 5ee7e454..00000000 --- a/templates/default-locale.tmpl +++ /dev/null @@ -1 +0,0 @@ -LANG="{{locale}}" -- cgit v1.2.3 From 23cf7e35bf9aa1cffc9d1bb2f20d362b57110723 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 21 Jun 2012 20:28:21 -0700 Subject: Use RuntimeError subclass instead of top level Exception --- cloudinit/config/cc_landscape.py | 8 ++++---- cloudinit/config/cc_update_etc_hosts.py | 4 ++-- cloudinit/distros/rhel.py | 4 ++-- cloudinit/distros/ubuntu.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_landscape.py b/cloudinit/config/cc_landscape.py index d45c9203..99a958b0 100644 --- a/cloudinit/config/cc_landscape.py +++ b/cloudinit/config/cc_landscape.py @@ -61,10 +61,10 @@ def handle(name, cfg, cloud, log, _args): ls_cloudcfg = cfg.get("landscape", {}) - if not isinstance(ls_cloudcfg, dict): - raise Exception(("'landscape' key existed in config," - " but not a dictionary type," - " is a %s instead"), util.obj_name(ls_cloudcfg)) + if not isinstance(ls_cloudcfg, (dict)): + raise RuntimeError(("'landscape' key existed in config," + " but not a dictionary type," + " is a %s instead"), util.obj_name(ls_cloudcfg)) merge_data = [ LSC_BUILTIN_CFG, diff --git a/cloudinit/config/cc_update_etc_hosts.py b/cloudinit/config/cc_update_etc_hosts.py index 6820ac4f..c148b12e 100644 --- a/cloudinit/config/cc_update_etc_hosts.py +++ b/cloudinit/config/cc_update_etc_hosts.py @@ -39,8 +39,8 @@ def handle(name, cfg, cloud, log, _args): distro_n = cloud.distro.name tpl_fn_name = cloud.get_template_filename("hosts.%s" % (distro_n)) if not tpl_fn_name: - raise Exception(("No hosts template could be" - " found for distro %s") % (distro_n)) + raise RuntimeError(("No hosts template could be" + " found for distro %s") % (distro_n)) out_fn = cloud.paths.join(False, '/etc/hosts') templater.render_to_file(tpl_fn_name, out_fn, diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index df63d559..dff76075 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -208,8 +208,8 @@ class Distro(distros.Distro): def set_timezone(self, tz): tz_file = os.path.join("/usr/share/zoneinfo", tz) if not os.path.isfile(tz_file): - raise Exception(("Invalid timezone %s," - " no file found at %s") % (tz, tz_file)) + raise RuntimeError(("Invalid timezone %s," + " no file found at %s") % (tz, tz_file)) # Adjust the sysconfig clock zone setting read_fn = self._paths.join(True, "/etc/sysconfig/clock") old_contents = self._read_conf(read_fn) diff --git a/cloudinit/distros/ubuntu.py b/cloudinit/distros/ubuntu.py index e8b95374..b23945d8 100644 --- a/cloudinit/distros/ubuntu.py +++ b/cloudinit/distros/ubuntu.py @@ -121,8 +121,8 @@ class Distro(distros.Distro): def set_timezone(self, tz): tz_file = os.path.join("/usr/share/zoneinfo", tz) if not os.path.isfile(tz_file): - raise Exception(("Invalid timezone %s," - " no file found at %s") % (tz, tz_file)) + raise RuntimeError(("Invalid timezone %s," + " no file found at %s") % (tz, tz_file)) tz_lines = [ "# Created by cloud-init", str(tz), -- cgit v1.2.3 From 3ca68402b5d9fbfeceebe39c92501dc773a335f4 Mon Sep 17 00:00:00 2001 From: harlowja Date: Fri, 22 Jun 2012 08:48:18 -0700 Subject: 1. Use configobj instead of configparser a. This allows us to not have to add a fake section in b. It will also preserver the files initial comments 2. Adjust how the new sections are being added and values are being written due to this change --- cloudinit/config/cc_mcollective.py | 65 +++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 36 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_mcollective.py b/cloudinit/config/cc_mcollective.py index 36a4cade..2acdbc6f 100644 --- a/cloudinit/config/cc_mcollective.py +++ b/cloudinit/config/cc_mcollective.py @@ -21,7 +21,10 @@ from StringIO import StringIO -from cloudinit import helpers +# Used since this can maintain comments +# and doesn't need a top level section +from configobj import ConfigObj + from cloudinit import util PUBCERT_FILE = "/etc/mcollective/ssl/server-public.pem" @@ -43,54 +46,44 @@ def handle(name, cfg, cloud, log, _args): # ... and then update the mcollective configuration if 'conf' in mcollective_cfg: - # Create object for reading server.cfg values - mcollective_config = helpers.DefaultingConfigParser() - # Read server.cfg values from original file in order to be able to mix - # the rest up + # Read server.cfg values from the + # original file in order to be able to mix the rest up server_cfg_fn = cloud.paths.join(True, '/etc/mcollective/server.cfg') - old_contents = util.load_file(server_cfg_fn) - # It doesn't contain any sections so just add one temporarily - # Use a hash id based off the contents, - # just incase of conflicts... (try to not have any...) - # This is so that an error won't occur when reading (and no - # sections exist in the file) - section_tpl = "[nullsection_%s]" - attempts = 0 - section_head = section_tpl % (attempts) - while old_contents.find(section_head) != -1: - attempts += 1 - section_head = section_tpl % (attempts) - sectioned_contents = "%s\n%s" % (section_head, old_contents) - mcollective_config.readfp(StringIO(sectioned_contents), - filename=server_cfg_fn) + mcollective_config = ConfigObj(server_cfg_fn) + # See: http://tiny.cc/jh9agw for (cfg_name, cfg) in mcollective_cfg['conf'].iteritems(): if cfg_name == 'public-cert': pubcert_fn = cloud.paths.join(True, PUBCERT_FILE) util.write_file(pubcert_fn, cfg, mode=0644) - mcollective_config.set(cfg_name, - 'plugin.ssl_server_public', pubcert_fn) - mcollective_config.set(cfg_name, 'securityprovider', 'ssl') + mcollective_config['plugin.ssl_server_public'] = pubcert_fn + mcollective_config['securityprovider'] = 'ssl' elif cfg_name == 'private-cert': pricert_fn = cloud.paths.join(True, PRICERT_FILE) util.write_file(pricert_fn, cfg, mode=0600) - mcollective_config.set(cfg_name, - 'plugin.ssl_server_private', pricert_fn) - mcollective_config.set(cfg_name, 'securityprovider', 'ssl') + mcollective_config['plugin.ssl_server_private'] = pricert_fn + mcollective_config['securityprovider'] = 'ssl' else: - # Iterate throug the config items, we'll use ConfigParser.set - # to overwrite or create new items as needed - for (o, v) in cfg.iteritems(): - mcollective_config.set(cfg_name, o, v) + if isinstance(cfg, (basestring, str)): + # Just set it in the 'main' section + mcollective_config[cfg_name] = cfg + elif isinstance(cfg, (dict)): + # Iterate throug the config items, create a section + # if it is needed and then add/or create items as needed + if cfg_name not in mcollective_config.sections: + mcollective_config[cfg_name] = {} + for (o, v) in cfg.iteritems(): + mcollective_config[cfg_name][o] = v + else: + # Otherwise just try to convert it to a string + mcollective_config[cfg_name] = str(cfg) # We got all our config as wanted we'll rename # the previous server.cfg and create our new one old_fn = cloud.paths.join(False, '/etc/mcollective/server.cfg.old') util.rename(server_cfg_fn, old_fn) - # Now we got the whole file, write to disk except the section - # we added so that config parser won't error out when trying to read. - # Note below, that we've just used ConfigParser because it generally - # works. Below, we remove the initial 'nullsection' header. - contents = mcollective_config.stringify() - contents = contents.replace("%s\n" % (section_head), "") + # Now we got the whole file, write to disk... + contents = StringIO() + mcollective_config.write(contents) + contents = contents.getvalue() server_cfg_rw = cloud.paths.join(False, '/etc/mcollective/server.cfg') util.write_file(server_cfg_rw, contents, mode=0644) -- cgit v1.2.3 From 5ea5f2c931b32ad1776b0dc108009affceea5389 Mon Sep 17 00:00:00 2001 From: harlowja Date: Fri, 22 Jun 2012 08:50:19 -0700 Subject: Configobj should be a requirement now, and not optional, so there is no need to check the import for errors. --- cloudinit/config/cc_landscape.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_landscape.py b/cloudinit/config/cc_landscape.py index 99a958b0..906a6ff7 100644 --- a/cloudinit/config/cc_landscape.py +++ b/cloudinit/config/cc_landscape.py @@ -22,10 +22,7 @@ import os from StringIO import StringIO -try: - from configobj import ConfigObj -except ImportError: - ConfigObj = None +from configobj import ConfigObj from cloudinit import util @@ -48,16 +45,12 @@ LSC_BUILTIN_CFG = { } -def handle(name, cfg, cloud, log, _args): +def handle(_name, cfg, cloud, log, _args): """ Basically turn a top level 'landscape' entry with a 'client' dict and render it to ConfigObj format under '[client]' section in /etc/landscape/client.conf """ - if not ConfigObj: - log.warn(("'ConfigObj' support not available," - " running module %s disabled"), name) - return ls_cloudcfg = cfg.get("landscape", {}) -- cgit v1.2.3 From ba79c2254c67bdf7545cc5c5433bbf16b92092f5 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 22 Jun 2012 12:55:45 -0700 Subject: Add in debian, which should also work --- cloudinit/config/cc_ca_certs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_ca_certs.py b/cloudinit/config/cc_ca_certs.py index 266a2d84..b4f9482a 100644 --- a/cloudinit/config/cc_ca_certs.py +++ b/cloudinit/config/cc_ca_certs.py @@ -23,7 +23,7 @@ CA_CERT_FILENAME = "cloud-init-ca-certs.crt" CA_CERT_CONFIG = "/etc/ca-certificates.conf" CA_CERT_SYSTEM_PATH = "/etc/ssl/certs/" -distros = ['ubuntu'] +distros = ['ubuntu', 'debian'] def update_ca_certs(): -- cgit v1.2.3 From 73e59752a8d776b12d03a5e9dd9d5f8b9823f66c Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 22 Jun 2012 20:30:08 -0700 Subject: Disable capturing of these subp calls --- cloudinit/config/cc_chef.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py index d8bd85f8..d682398a 100644 --- a/cloudinit/config/cc_chef.py +++ b/cloudinit/config/cc_chef.py @@ -96,7 +96,8 @@ def handle(name, cfg, cloud, log, _args): install_chef_from_gems(cloud.distro, ruby_version, chef_version) # and finally, run chef-client log.debug('Running chef-client') - util.subp(['/usr/bin/chef-client', '-d', '-i', '1800', '-s', '20']) + util.subp(['/usr/bin/chef-client', + '-d', '-i', '1800', '-s', '20'], capture=False) elif install_type == 'packages': # this will install and run the chef-client from packages cloud.distro.install_packages(('chef',)) @@ -121,8 +122,8 @@ def install_chef_from_gems(ruby_version, chef_version, distro): if chef_version: util.subp(['/usr/bin/gem', 'install', 'chef', '-v %s' % chef_version, '--no-ri', - '--no-rdoc', '--bindir', '/usr/bin', '-q']) + '--no-rdoc', '--bindir', '/usr/bin', '-q'], capture=False) else: util.subp(['/usr/bin/gem', 'install', 'chef', '--no-ri', '--no-rdoc', '--bindir', - '/usr/bin', '-q']) + '/usr/bin', '-q'], capture=False) -- cgit v1.2.3 From ae092a1b559ece1964885535b75daa9f0b6a9a24 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 22 Jun 2012 20:49:30 -0700 Subject: Fix ma english --- cloudinit/config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/__init__.py b/cloudinit/config/__init__.py index ab13045f..69a8cc68 100644 --- a/cloudinit/config/__init__.py +++ b/cloudinit/config/__init__.py @@ -26,7 +26,7 @@ from cloudinit import log as logging LOG = logging.getLogger(__name__) # This prefix is used to make it less -# of a change that when importing +# of a chance that when importing # we will not find something else with the same # name in the lookup path... MOD_PREFIX = "cc_" -- cgit v1.2.3 From aff1783ff4c6b5afdbf42d474f3ab270b64b3d14 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 22 Jun 2012 20:53:35 -0700 Subject: Use a module level constant for the filename --- cloudinit/config/cc_apt_update_upgrade.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_apt_update_upgrade.py b/cloudinit/config/cc_apt_update_upgrade.py index f5b4b58f..6719da52 100644 --- a/cloudinit/config/cc_apt_update_upgrade.py +++ b/cloudinit/config/cc_apt_update_upgrade.py @@ -27,6 +27,7 @@ from cloudinit import util distros = ['ubuntu', 'debian'] PROXY_TPL = "Acquire::HTTP::Proxy \"%s\";\n" +PROXY_FN = "/etc/apt/apt.conf.d/95cloud-init-proxy" def handle(_name, cfg, cloud, log, _args): @@ -48,7 +49,7 @@ def handle(_name, cfg, cloud, log, _args): # Set up any apt proxy proxy = cfg.get("apt_proxy", None) - proxy_filename = "/etc/apt/apt.conf.d/95cloud-init-proxy" + proxy_filename = PROXY_FN if proxy: try: # See man 'apt.conf' -- cgit v1.2.3 From 1fefd65f888d7b245c8a221c3c2168fea5423259 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 22 Jun 2012 20:58:50 -0700 Subject: Turn off capturing --- cloudinit/config/cc_byobu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_byobu.py b/cloudinit/config/cc_byobu.py index 38586174..4e2e06bb 100644 --- a/cloudinit/config/cc_byobu.py +++ b/cloudinit/config/cc_byobu.py @@ -68,4 +68,4 @@ def handle(name, cfg, _cloud, log, args): log.debug("Setting byobu to %s", value) - util.subp(cmd) + util.subp(cmd, capture=False) -- cgit v1.2.3 From f445c1ab201a9416734888c9398ab8e0ec134dd0 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 22 Jun 2012 20:59:23 -0700 Subject: Turn off capturing --- cloudinit/config/cc_ca_certs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_ca_certs.py b/cloudinit/config/cc_ca_certs.py index b4f9482a..dc046bda 100644 --- a/cloudinit/config/cc_ca_certs.py +++ b/cloudinit/config/cc_ca_certs.py @@ -30,7 +30,7 @@ def update_ca_certs(): """ Updates the CA certificate cache on the current machine. """ - util.subp(["update-ca-certificates"]) + util.subp(["update-ca-certificates"], capture=False) def add_ca_certs(paths, certs): -- cgit v1.2.3 From 1551b681a3377d46bd1e6fff1f2894bf9e7a9b54 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 22 Jun 2012 21:01:57 -0700 Subject: Turn off capturing --- cloudinit/config/cc_disable_ec2_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_disable_ec2_metadata.py b/cloudinit/config/cc_disable_ec2_metadata.py index a7c6a75b..8fb15e43 100644 --- a/cloudinit/config/cc_disable_ec2_metadata.py +++ b/cloudinit/config/cc_disable_ec2_metadata.py @@ -30,7 +30,7 @@ REJECT_CMD = ['route', 'add', '-host', '169.254.169.254', 'reject'] def handle(name, cfg, _cloud, log, _args): disabled = util.get_cfg_option_bool(cfg, "disable_ec2_metadata", False) if disabled: - util.subp(REJECT_CMD) + util.subp(REJECT_CMD, subp=False) else: log.debug(("Skipping module named %s," " disabling the ec2 route not enabled"), name) -- cgit v1.2.3 From f8122ecf95aa347969829a217b3894e07158f994 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 22 Jun 2012 21:02:04 -0700 Subject: Turn off capturing --- cloudinit/config/cc_disable_ec2_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_disable_ec2_metadata.py b/cloudinit/config/cc_disable_ec2_metadata.py index 8fb15e43..3fd2c20f 100644 --- a/cloudinit/config/cc_disable_ec2_metadata.py +++ b/cloudinit/config/cc_disable_ec2_metadata.py @@ -30,7 +30,7 @@ REJECT_CMD = ['route', 'add', '-host', '169.254.169.254', 'reject'] def handle(name, cfg, _cloud, log, _args): disabled = util.get_cfg_option_bool(cfg, "disable_ec2_metadata", False) if disabled: - util.subp(REJECT_CMD, subp=False) + util.subp(REJECT_CMD, capture=False) else: log.debug(("Skipping module named %s," " disabling the ec2 route not enabled"), name) -- cgit v1.2.3 From 85d5753d549e2d6ff69bbcc946e37d8fdac44868 Mon Sep 17 00:00:00 2001 From: harlowja Date: Fri, 22 Jun 2012 22:04:37 -0700 Subject: 1. Adjust the logging of phone home module when its not enabled 2. Fix pylint warning in userdata about unused variable --- cloudinit/config/cc_phone_home.py | 4 +++- cloudinit/user_data.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_phone_home.py b/cloudinit/config/cc_phone_home.py index d929eb64..ae1349eb 100644 --- a/cloudinit/config/cc_phone_home.py +++ b/cloudinit/config/cc_phone_home.py @@ -49,6 +49,8 @@ def handle(name, cfg, cloud, log, args): ph_cfg = util.read_conf(args[0]) else: if not 'phone_home' in cfg: + log.debug(("Skipping module named %s, " + "no 'phone_home' configuration found"), name) return ph_cfg = cfg['phone_home'] @@ -59,7 +61,7 @@ def handle(name, cfg, cloud, log, args): url = ph_cfg['url'] post_list = ph_cfg.get('post', 'all') - tries = ph_cfg.get('tries', 10) + tries = ph_cfg.get('tries') try: tries = int(tries) except: diff --git a/cloudinit/user_data.py b/cloudinit/user_data.py index 91841bb8..0842594d 100644 --- a/cloudinit/user_data.py +++ b/cloudinit/user_data.py @@ -204,7 +204,7 @@ class UserDataProcessor(object): outer_msg.replace_header(ATTACHMENT_FIELD, str(fetched_count)) return fetched_count - def _part_filename(self, unnamed_part, count): + def _part_filename(self, _unnamed_part, count): return PART_FN_TPL % (count + 1) def _attach_part(self, outer_msg, part): -- cgit v1.2.3 From f8413af9168adc0ad7c730b9adea9eba67949ba5 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 22 Jun 2012 23:26:50 -0700 Subject: 1. Move the getkeybyid function back here but add some slight adjustments a. Instead of executing a bash string, write out a temporary file and then just execute '/bin/sh' on that file with the right arguments instead. 2. Rename util.SilentTemporaryFile to util.ExtendedTemporaryFile and update the usages of the previous name accordingly, this better reflects what this temp file is. 3. More teenie pep8 line length fixings --- cloudinit/config/cc_apt_update_upgrade.py | 27 ++++++++++++++++++++++++++- cloudinit/config/cc_bootcmd.py | 3 +-- cloudinit/config/cc_chef.py | 2 +- cloudinit/config/cc_resizefs.py | 4 ++-- cloudinit/util.py | 22 +--------------------- 5 files changed, 31 insertions(+), 27 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_apt_update_upgrade.py b/cloudinit/config/cc_apt_update_upgrade.py index 6719da52..8ecd9c94 100644 --- a/cloudinit/config/cc_apt_update_upgrade.py +++ b/cloudinit/config/cc_apt_update_upgrade.py @@ -29,6 +29,21 @@ distros = ['ubuntu', 'debian'] PROXY_TPL = "Acquire::HTTP::Proxy \"%s\";\n" PROXY_FN = "/etc/apt/apt.conf.d/95cloud-init-proxy" +# A temporary shell program to get a given gpg key +# from a given keyserver +EXPORT_GPG_KEYID = """ + k=${1} ks=${2}; + exec 2>/dev/null + [ -n "$k" ] || exit 1; + armour=$(gpg --list-keys --armour "${k}") + if [ -z "${armour}" ]; then + gpg --keyserver ${ks} --recv $k >/dev/null && + armour=$(gpg --export --armour "${k}") && + gpg --batch --yes --delete-keys "${k}" + fi + [ -n "${armour}" ] && echo "${armour}" +""" + def handle(_name, cfg, cloud, log, _args): update = util.get_cfg_option_bool(cfg, 'apt_update', False) @@ -106,6 +121,16 @@ def handle(_name, cfg, cloud, log, _args): raise errors[-1] +# get gpg keyid from keyserver +def getkeybyid(keyid, keyserver): + with util.ExtendedTemporaryFile(suffix='.sh') as fh: + fh.write(EXPORT_GPG_KEYID) + fh.flush() + cmd = ['/bin/sh', fh.name, keyid, keyserver] + (stdout, _stderr) = util.subp(cmd) + return stdout.strip() + + def mirror2lists_fileprefix(mirror): string = mirror # take of http:// or ftp:// @@ -181,7 +206,7 @@ def add_sources(cloud, srclist, template_params=None): if 'keyserver' in ent: ks = ent['keyserver'] try: - ent['key'] = util.getkeybyid(ent['keyid'], ks) + ent['key'] = getkeybyid(ent['keyid'], ks) except: errorlist.append([source, "failed to get key from %s" % ks]) continue diff --git a/cloudinit/config/cc_bootcmd.py b/cloudinit/config/cc_bootcmd.py index 89ccf3f1..bae1ea54 100644 --- a/cloudinit/config/cc_bootcmd.py +++ b/cloudinit/config/cc_bootcmd.py @@ -19,7 +19,6 @@ # along with this program. If not, see . import os -import tempfile from cloudinit import util from cloudinit.settings import PER_ALWAYS @@ -34,7 +33,7 @@ def handle(name, cfg, cloud, log, _args): " no 'bootcmd' key in configuration"), name) return - with tempfile.NamedTemporaryFile(suffix=".sh") as tmpf: + with util.ExtendedTemporaryFile(suffix=".sh") as tmpf: try: content = util.shellify(cfg["bootcmd"]) tmpf.write(content) diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py index d682398a..6f568261 100644 --- a/cloudinit/config/cc_chef.py +++ b/cloudinit/config/cc_chef.py @@ -96,7 +96,7 @@ def handle(name, cfg, cloud, log, _args): install_chef_from_gems(cloud.distro, ruby_version, chef_version) # and finally, run chef-client log.debug('Running chef-client') - util.subp(['/usr/bin/chef-client', + util.subp(['/usr/bin/chef-client', '-d', '-i', '1800', '-s', '20'], capture=False) elif install_type == 'packages': # this will install and run the chef-client from packages diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py index 7e1428e9..69cd8872 100644 --- a/cloudinit/config/cc_resizefs.py +++ b/cloudinit/config/cc_resizefs.py @@ -79,8 +79,8 @@ def handle(name, cfg, cloud, log, args): # TODO: allow what is to be resized to be configurable?? resize_what = cloud.paths.join(False, "/") - with util.SilentTemporaryFile(prefix="cloudinit.resizefs.", - dir=resize_root_d, delete=True) as tfh: + with util.ExtendedTemporaryFile(prefix="cloudinit.resizefs.", + dir=resize_root_d, delete=True) as tfh: devpth = tfh.name # Delete the file so that mknod will work diff --git a/cloudinit/util.py b/cloudinit/util.py index baa3def1..6cdf9ff3 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -157,7 +157,7 @@ class MountFailedError(Exception): pass -def SilentTemporaryFile(**kwargs): +def ExtendedTemporaryFile(**kwargs): fh = tempfile.NamedTemporaryFile(**kwargs) # Replace its unlink with a quiet version # that does not raise errors when the @@ -517,26 +517,6 @@ def del_dir(path): shutil.rmtree(path) -# get gpg keyid from keyserver -def getkeybyid(keyid, keyserver): - # TODO fix this... - shcmd = """ - k=${1} ks=${2}; - exec 2>/dev/null - [ -n "$k" ] || exit 1; - armour=$(gpg --list-keys --armour "${k}") - if [ -z "${armour}" ]; then - gpg --keyserver ${ks} --recv $k >/dev/null && - armour=$(gpg --export --armour "${k}") && - gpg --batch --yes --delete-keys "${k}" - fi - [ -n "${armour}" ] && echo "${armour}" - """ - args = ['sh', '-c', shcmd, "export-gpg-keyid", keyid, keyserver] - (stdout, _stderr) = subp(args) - return stdout - - def runparts(dirp, skip_no_exist=True): if skip_no_exist and not os.path.isdir(dirp): return -- cgit v1.2.3 From fdbd341aae7a41d3985e544fe40375087056b1f9 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 22 Jun 2012 23:36:08 -0700 Subject: Use os.path.join instead of custom string path formation --- cloudinit/config/cc_apt_update_upgrade.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_apt_update_upgrade.py b/cloudinit/config/cc_apt_update_upgrade.py index 8ecd9c94..42b6f3e9 100644 --- a/cloudinit/config/cc_apt_update_upgrade.py +++ b/cloudinit/config/cc_apt_update_upgrade.py @@ -133,7 +133,7 @@ def getkeybyid(keyid, keyserver): def mirror2lists_fileprefix(mirror): string = mirror - # take of http:// or ftp:// + # take off http:// or ftp:// if string.endswith("/"): string = string[0:-1] pos = string.find("://") @@ -144,8 +144,8 @@ def mirror2lists_fileprefix(mirror): def rename_apt_lists(omirror, new_mirror, lists_d="/var/lib/apt/lists"): - oprefix = "%s/%s" % (lists_d, mirror2lists_fileprefix(omirror)) - nprefix = "%s/%s" % (lists_d, mirror2lists_fileprefix(new_mirror)) + oprefix = os.path.join(lists_d, mirror2lists_fileprefix(omirror)) + nprefix = os.path.join(lists_d, mirror2lists_fileprefix(new_mirror)) if oprefix == nprefix: return olen = len(oprefix) -- cgit v1.2.3 From aa9189e89ec101fba8ca36919a7c800f8b28d5c0 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Sun, 1 Jul 2012 00:19:49 -0700 Subject: Add a multi log function that can write to stderr, console and a log debug, useful in certain cases --- bin/cloud-init | 4 +--- cloudinit/config/cc_final_message.py | 6 +----- cloudinit/config/cc_keys_to_console.py | 6 +++--- cloudinit/util.py | 11 +++++++++++ 4 files changed, 16 insertions(+), 11 deletions(-) (limited to 'cloudinit/config') diff --git a/bin/cloud-init b/bin/cloud-init index 22901e15..fce4fe8c 100755 --- a/bin/cloud-init +++ b/bin/cloud-init @@ -86,9 +86,7 @@ def welcome(action): 'timestamp': util.time_rfc2822(), 'action': action, } - welcome_msg = "%s" % (templater.render_string(msg, tpl_params)) - sys.stderr.write("%s\n" % (welcome_msg)) - LOG.debug(welcome_msg) + util.multi_log("%s\n" % (templater.render_string(msg, tpl_params))) def extract_fns(args): diff --git a/cloudinit/config/cc_final_message.py b/cloudinit/config/cc_final_message.py index fd59aa1e..711e7b5b 100644 --- a/cloudinit/config/cc_final_message.py +++ b/cloudinit/config/cc_final_message.py @@ -18,8 +18,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import sys - from cloudinit import templater from cloudinit import util from cloudinit import version @@ -57,9 +55,7 @@ def handle(_name, cfg, cloud, log, args): 'timestamp': ts, 'version': cver, } - # Use stdout, stderr or the logger?? - content = templater.render_string(msg_in, subs) - sys.stderr.write("%s\n" % (content)) + util.multi_log("%s\n" % (templater.render_string(msg_in, subs))) except Exception: util.logexc(log, "Failed to render final message template") diff --git a/cloudinit/config/cc_keys_to_console.py b/cloudinit/config/cc_keys_to_console.py index d4c877f7..da7d5219 100644 --- a/cloudinit/config/cc_keys_to_console.py +++ b/cloudinit/config/cc_keys_to_console.py @@ -29,7 +29,7 @@ frequency = PER_INSTANCE HELPER_TOOL = '/usr/lib/cloud-init/write-ssh-key-fingerprints' -def handle(name, cfg, cloud, log, _args): +def handle(name, cfg, _cloud, log, _args): if not os.path.exists(HELPER_TOOL): log.warn(("Unable to activate module %s," " helper tool not found at %s"), name, HELPER_TOOL) @@ -46,7 +46,7 @@ def handle(name, cfg, cloud, log, _args): cmd.append(','.join(fp_blacklist)) cmd.append(','.join(key_blacklist)) (stdout, _stderr) = util.subp(cmd) - util.write_file(cloud.paths.join(False, '/dev/console'), stdout) + util.multi_log("%s\n" % (stdout.strip()), stderr=False) except: - log.warn("Writing keys to /dev/console failed!") + log.warn("Writing keys to the system console failed!") raise diff --git a/cloudinit/util.py b/cloudinit/util.py index 0c592656..e6219d66 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -271,6 +271,17 @@ def find_modules(root_dir): return entries +def multi_log(text, console=True, stderr=True, log=None): + if stderr: + sys.stderr.write(text) + if console: + with open('/dev/console', 'wb') as wfh: + wfh.write(text) + wfh.flush() + if log: + log.debug(text) + + def is_ipv4(instr): """ determine if input string is a ipv4 address. return boolean""" toks = instr.split('.') -- cgit v1.2.3 From 25712b40dfbe197c1ad8bdc783a2235e3c87c7de Mon Sep 17 00:00:00 2001 From: harlowja Date: Sun, 1 Jul 2012 12:08:08 -0700 Subject: 1. Rename util functions to is_true and is_false 2. Move the config loading functions to where they are used (in stages) 3. Adjust cc_set_passwords to use the is_true and is_false renamed functions 4. Adjust the init stage to have a _read_base_config function used to load the base 'initial' configuration from the following locations a. Kernel cmdline b. Conf.d location (+ the cloud.cfg location) c. Built-in configuration --- cloudinit/config/cc_set_passwords.py | 4 +-- cloudinit/stages.py | 27 +++++++++++------ cloudinit/util.py | 56 +++++++++++------------------------- 3 files changed, 37 insertions(+), 50 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py index 5b72224b..ab266741 100644 --- a/cloudinit/config/cc_set_passwords.py +++ b/cloudinit/config/cc_set_passwords.py @@ -96,9 +96,9 @@ def handle(_name, cfg, cloud, log, args): pw_auth = None if 'ssh_pwauth' in cfg: change_pwauth = True - if util.is_true_str(cfg['ssh_pwauth']): + if util.is_true(cfg['ssh_pwauth']): pw_auth = 'yes' - if util.is_false_str(cfg['ssh_pwauth']): + if util.is_false(cfg['ssh_pwauth']): pw_auth = 'no' if change_pwauth: diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 2f175934..79663b27 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -26,7 +26,7 @@ import copy import os import sys -from cloudinit.settings import (PER_INSTANCE, FREQUENCIES) +from cloudinit.settings import (PER_INSTANCE, FREQUENCIES, CLOUD_CONFIG) from cloudinit import handlers @@ -146,16 +146,27 @@ class Init(object): self._cfg = self._read_cfg(extra_fns) # LOG.debug("Loaded 'init' config %s", self._cfg) + def _read_base_cfg(self): + base_cfgs = [] + default_cfg = util.get_builtin_cfg() + kern_contents = util.read_cc_from_cmdline() + # Kernel/cmdline parameters override system config + if kern_contents: + base_cfgs.append(util.load_yaml(kern_contents, default={})) + # Anything in your conf.d location?? + if os.path.isfile(CLOUD_CONFIG): + base_cfgs.append(util.read_conf_with_confd(CLOUD_CONFIG)) + # And finally the default gets to play + if default_cfg: + base_cfgs.append(default_cfg) + return util.mergemanydict(base_cfgs) + def _read_cfg(self, extra_fns): - try: - base_conf = util.get_base_cfg(builtin=util.get_builtin_cfg()) - except Exception: - base_conf = util.get_builtin_cfg() - no_cfg_pths = helpers.Paths({}, self.datasource) - merger = helpers.ConfigMerger(paths=no_cfg_pths, + no_cfg_paths = helpers.Paths({}, self.datasource) + merger = helpers.ConfigMerger(paths=no_cfg_paths, datasource=self.datasource, additional_fns=extra_fns, - base_cfg=base_conf) + base_cfg=self._read_base_cfg()) return merger.cfg def _restore_from_cache(self): diff --git a/cloudinit/util.py b/cloudinit/util.py index 0c592656..f07d22e7 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -50,7 +50,7 @@ from cloudinit import importer from cloudinit import log as logging from cloudinit import url_helper as uhelp -from cloudinit.settings import (CFG_BUILTIN, CLOUD_CONFIG) +from cloudinit.settings import (CFG_BUILTIN) LOG = logging.getLogger(__name__) @@ -192,7 +192,9 @@ def fork_cb(child_cb, *args): fid, obj_name(child_cb)) -def is_true_str(val, addons=None): +def is_true(val, addons=None): + if isinstance(val, (bool)): + return val is True check_set = ['true', '1', 'on', 'yes'] if addons: check_set = check_set + addons @@ -201,7 +203,9 @@ def is_true_str(val, addons=None): return False -def is_false_str(val, addons=None): +def is_false(val, addons=None): + if isinstance(val, (bool)): + return val is False check_set = ['off', '0', 'no', 'false'] if addons: check_set = check_set + addons @@ -218,7 +222,7 @@ def translate_bool(val, addons=None): # If its already a boolean skip if isinstance(val, (bool)): return val - return is_true_str(val, addons) + return is_true(val, addons) def rand_str(strlen=32, select_from=None): @@ -285,29 +289,6 @@ def is_ipv4(instr): return (len(toks) == 4) -def merge_base_cfg(cfgfile, cfg_builtin=None): - syscfg = read_conf_with_confd(cfgfile) - - kern_contents = read_cc_from_cmdline() - kerncfg = {} - if kern_contents: - kerncfg = load_yaml(kern_contents, default={}) - - # Kernel parameters override system config - if kerncfg: - combined = mergedict(kerncfg, syscfg) - else: - combined = syscfg - - if cfg_builtin: - # Combined over-ride anything builtin - fin = mergedict(combined, cfg_builtin) - else: - fin = combined - - return fin - - def get_cfg_option_bool(yobj, key, default=False): if key not in yobj: return default @@ -622,15 +603,17 @@ def read_seeded(base="", ext="", timeout=5, retries=10, file_retries=0): def read_conf_d(confd): - # get reverse sorted list (later trumps newer) + # Get reverse sorted list (later trumps newer) confs = sorted(os.listdir(confd), reverse=True) - # remove anything not ending in '.cfg' + # Remove anything not ending in '.cfg' confs = [f for f in confs if f.endswith(".cfg")] - # remove anything not a file - confs = [f for f in confs if os.path.isfile(os.path.join(confd, f))] + # Remove anything not a file + confs = [f for f in confs + if os.path.isfile(os.path.join(confd, f))] + # Load them all so that they can be merged cfgs = [] for fn in confs: cfgs.append(read_conf(os.path.join(confd, fn))) @@ -658,7 +641,8 @@ def read_conf_with_confd(cfgfile): return cfg # Conf.d settings override input configuration - return mergedict(read_conf_d(confd), cfg) + confd_cfg = read_conf_d(confd) + return mergedict(confd_cfg, cfg) def read_cc_from_cmdline(cmdline=None): @@ -1076,14 +1060,6 @@ def ensure_dir(path, mode=None): chmod(path, mode) -def get_base_cfg(cfg_path=None, builtin=None): - if not cfg_path: - cfg_path = CLOUD_CONFIG - if not builtin: - builtin = get_builtin_cfg() - return merge_base_cfg(cfg_path, builtin) - - @contextlib.contextmanager def unmounter(umount): try: -- cgit v1.2.3 From 879946c26f005f5ae5c2bbdd537beb295d7f4773 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Mon, 2 Jul 2012 10:56:22 -0700 Subject: 1. Move the welcome message template string to a constant at the top of the module 2. Fix the usage of multi_log to log to only one of the places (for now) 3. Update comment about multi-log and why write_file isn't used in this case --- bin/cloud-init | 11 ++++++++--- cloudinit/config/cc_final_message.py | 3 ++- cloudinit/config/cc_keys_to_console.py | 3 ++- cloudinit/util.py | 7 +++++-- 4 files changed, 17 insertions(+), 7 deletions(-) (limited to 'cloudinit/config') diff --git a/bin/cloud-init b/bin/cloud-init index fce4fe8c..c7863db1 100755 --- a/bin/cloud-init +++ b/bin/cloud-init @@ -45,6 +45,10 @@ from cloudinit.settings import (PER_INSTANCE, PER_ALWAYS, PER_ONCE, CLOUD_CONFIG) +# Pretty little welcome message template +WELCOME_MSG_TPL = ("Cloud-init v. {{version}} running '{{action}}' at " + "{{timestamp}}. Up {{uptime}} seconds.") + # Module section template MOD_SECTION_TPL = "cloud_%s_modules" @@ -56,6 +60,7 @@ QUERY_DATA_TYPES = [ ] # Frequency shortname to full name +# (so users don't have to remember the full name...) FREQ_SHORT_NAMES = { 'instance': PER_INSTANCE, 'always': PER_ALWAYS, @@ -78,15 +83,15 @@ def print_exc(msg=''): def welcome(action): - msg = ("Cloud-init v. {{version}} running '{{action}}' at " - "{{timestamp}}. Up {{uptime}} seconds.") tpl_params = { 'version': version.version_string(), 'uptime': util.uptime(), 'timestamp': util.time_rfc2822(), 'action': action, } - util.multi_log("%s\n" % (templater.render_string(msg, tpl_params))) + tpl_msg = templater.render_string(WELCOME_MSG_TPL, tpl_params) + util.multi_log("%s\n" % (tpl_msg), + console=False, stderr=True) def extract_fns(args): diff --git a/cloudinit/config/cc_final_message.py b/cloudinit/config/cc_final_message.py index 711e7b5b..b1caca47 100644 --- a/cloudinit/config/cc_final_message.py +++ b/cloudinit/config/cc_final_message.py @@ -55,7 +55,8 @@ def handle(_name, cfg, cloud, log, args): 'timestamp': ts, 'version': cver, } - util.multi_log("%s\n" % (templater.render_string(msg_in, subs))) + util.multi_log("%s\n" % (templater.render_string(msg_in, subs)), + console=False, stderr=True) except Exception: util.logexc(log, "Failed to render final message template") diff --git a/cloudinit/config/cc_keys_to_console.py b/cloudinit/config/cc_keys_to_console.py index da7d5219..ed7af690 100644 --- a/cloudinit/config/cc_keys_to_console.py +++ b/cloudinit/config/cc_keys_to_console.py @@ -46,7 +46,8 @@ def handle(name, cfg, _cloud, log, _args): cmd.append(','.join(fp_blacklist)) cmd.append(','.join(key_blacklist)) (stdout, _stderr) = util.subp(cmd) - util.multi_log("%s\n" % (stdout.strip()), stderr=False) + util.multi_log("%s\n" % (stdout.strip()), + stderr=False, console=True) except: log.warn("Writing keys to the system console failed!") raise diff --git a/cloudinit/util.py b/cloudinit/util.py index 6b23a0ee..4c29432b 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -275,15 +275,18 @@ def find_modules(root_dir): return entries -def multi_log(text, console=True, stderr=True, log=None): +def multi_log(text, console=True, stderr=True, + log=None, log_level=logging.DEBUG): if stderr: sys.stderr.write(text) if console: + # Don't use the write_file since + # this might be 'sensitive' info (not debug worthy?) with open('/dev/console', 'wb') as wfh: wfh.write(text) wfh.flush() if log: - log.debug(text) + log.log(log_level, text) def is_ipv4(instr): -- cgit v1.2.3 From b3975ef46bbee1c713b963f3897fce9c5d3cbe94 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 6 Jul 2012 10:47:19 -0700 Subject: Updated so that if no mirror is found, the module stops running. --- cloudinit/config/cc_apt_update_upgrade.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'cloudinit/config') diff --git a/cloudinit/config/cc_apt_update_upgrade.py b/cloudinit/config/cc_apt_update_upgrade.py index 42b6f3e9..5c5e510c 100644 --- a/cloudinit/config/cc_apt_update_upgrade.py +++ b/cloudinit/config/cc_apt_update_upgrade.py @@ -45,13 +45,16 @@ EXPORT_GPG_KEYID = """ """ -def handle(_name, cfg, cloud, log, _args): +def handle(name, cfg, cloud, log, _args): update = util.get_cfg_option_bool(cfg, 'apt_update', False) upgrade = util.get_cfg_option_bool(cfg, 'apt_upgrade', False) release = get_release() - mirror = find_apt_mirror(cloud, cfg) + if not mirror: + log.debug(("Skipping module named %s," + " no package 'mirror' located"), name) + return log.debug("Selected mirror at: %s" % mirror) -- cgit v1.2.3