diff options
Diffstat (limited to 'cloudinit/CloudConfig')
19 files changed, 882 insertions, 75 deletions
diff --git a/cloudinit/CloudConfig/__init__.py b/cloudinit/CloudConfig/__init__.py index 0a91059a..22ad63a6 100644 --- a/cloudinit/CloudConfig/__init__.py +++ b/cloudinit/CloudConfig/__init__.py @@ -21,16 +21,22 @@ import cloudinit import cloudinit.util as util import sys import traceback +import os +import subprocess per_instance="once-per-instance" per_always="always" +per_once="once" class CloudConfig(): cfgfile = None cfg = None - def __init__(self,cfgfile): - self.cloud = cloudinit.CloudInit() + def __init__(self,cfgfile, cloud=None): + if cloud == None: + self.cloud = cloudinit.CloudInit() + else: + self.cloud = cloud self.cfg = self.get_config_obj(cfgfile) self.cloud.get_data_source() @@ -38,6 +44,7 @@ class CloudConfig(): try: cfg = util.read_conf(cfgfile) except: + # TODO: this 'log' could/should be passed in cloudinit.log.critical("Failed loading of cloud config '%s'. Continuing with empty config\n" % cfgfile) cloudinit.log.debug(traceback.format_exc() + "\n") cfg = None @@ -58,3 +65,150 @@ class CloudConfig(): except: raise +# reads a cloudconfig module list, returns +# a 2 dimensional array suitable to pass to run_cc_modules +def read_cc_modules(cfg,name): + if name not in cfg: return([]) + module_list = [] + # create 'module_list', an array of arrays + # where array[0] = config + # array[1] = freq + # array[2:] = arguemnts + for item in cfg[name]: + if isinstance(item,str): + module_list.append((item,)) + elif isinstance(item,list): + module_list.append(item) + else: + raise TypeError("failed to read '%s' item in config") + return(module_list) + +def run_cc_modules(cc,module_list,log): + failures = [] + for cfg_mod in module_list: + name = cfg_mod[0] + freq = None + run_args = [ ] + if len(cfg_mod) > 1: + freq = cfg_mod[1] + if len(cfg_mod) > 2: + run_args = cfg_mod[2:] + + try: + log.debug("handling %s with freq=%s and args=%s" % + (name, freq, run_args )) + cc.handle(name, run_args, freq=freq) + except: + log.warn(traceback.format_exc()) + log.error("config handling of %s, %s, %s failed\n" % + (name,freq,run_args)) + failures.append(name) + + return(failures) + +# always returns well formated values +# cfg is expected to have an entry 'output' in it, which is a dictionary +# that includes entries for 'init', 'config', 'final' or 'all' +# init: /var/log/cloud.out +# config: [ ">> /var/log/cloud-config.out", /var/log/cloud-config.err ] +# final: +# output: "| logger -p" +# error: "> /dev/null" +# this returns the specific 'mode' entry, cleanly formatted, with value +# None if if none is given +def get_output_cfg(cfg, mode="init"): + ret = [ None, None ] + if not 'output' in cfg: return ret + + outcfg = cfg['output'] + if mode in outcfg: + modecfg = outcfg[mode] + else: + if 'all' not in outcfg: return ret + # if there is a 'all' item in the output list + # then it applies to all users of this (init, config, final) + modecfg = outcfg['all'] + + # if value is a string, it specifies stdout + if isinstance(modecfg,str): + ret = [ modecfg, None ] + + # if its a list, then we expect (stdout, stderr) + if isinstance(modecfg,list): + if len(modecfg) > 0: ret[0] = modecfg[0] + if len(modecfg) > 1: + ret[1] = modecfg[1] + + # if it is a dictionary, expect 'out' and 'error' + # items, which indicate out and error + if isinstance(modecfg, dict): + if 'output' in modecfg: + ret[0] = modecfg['output'] + if 'error' in modecfg: + ret[1] = modecfg['error'] + + # if err's entry == "&1", then make it same as stdout + # as in shell syntax of "echo foo >/dev/null 2>&1" + if ret[1] == "&1": ret[1] = ret[0] + + swlist = [ ">>", ">", "|" ] + for i in range(len(ret)): + if not ret[i]: continue + val = ret[i].lstrip() + found = False + for s in swlist: + if val.startswith(s): + val = "%s %s" % (s,val[len(s):].strip()) + found = True + break + if not found: + # default behavior is append + val = "%s %s" % ( ">>", val.strip()) + ret[i] = val + + return(ret) + + +# redirect_output(outfmt, errfmt, orig_out, orig_err) +# replace orig_out and orig_err with filehandles specified in outfmt or errfmt +# fmt can be: +# > FILEPATH +# >> FILEPATH +# | program [ arg1 [ arg2 [ ... ] ] ] +# +# with a '|', arguments are passed to shell, so one level of +# shell escape is required. +def redirect_output(outfmt,errfmt, o_out=sys.stdout, o_err=sys.stderr): + if outfmt: + (mode, arg) = outfmt.split(" ",1) + if mode == ">" or mode == ">>": + owith = "ab" + if mode == ">": owith = "wb" + new_fp = open(arg, owith) + elif mode == "|": + proc = subprocess.Popen(arg, shell=True, stdin=subprocess.PIPE) + new_fp = proc.stdin + else: + raise TypeError("invalid type for outfmt: %s" % outfmt) + + if o_out: + os.dup2(new_fp.fileno(), o_out.fileno()) + if errfmt == outfmt: + os.dup2(new_fp.fileno(), o_err.fileno()) + return + + if errfmt: + (mode, arg) = errfmt.split(" ",1) + if mode == ">" or mode == ">>": + owith = "ab" + if mode == ">": owith = "wb" + new_fp = open(arg, owith) + elif mode == "|": + proc = subprocess.Popen(arg, shell=True, stdin=subprocess.PIPE) + new_fp = proc.stdin + else: + raise TypeError("invalid type for outfmt: %s" % outfmt) + + if o_err: + os.dup2(new_fp.fileno(), o_err.fileno()) + return diff --git a/cloudinit/CloudConfig/cc_apt_update_upgrade.py b/cloudinit/CloudConfig/cc_apt_update_upgrade.py index 396c5e09..e918e8c8 100644 --- a/cloudinit/CloudConfig/cc_apt_update_upgrade.py +++ b/cloudinit/CloudConfig/cc_apt_update_upgrade.py @@ -25,20 +25,23 @@ 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() + if cfg.has_key("apt_mirror"): + mirror = cfg["apt_mirror"] + else: + mirror = cloud.get_mirror() + if not util.get_cfg_option_bool(cfg, \ 'apt_preserve_sources_list', False): - if cfg.has_key("apt_mirror"): - mirror = cfg["apt_mirror"] - else: - mirror = cloud.get_mirror() - generate_sources_list(mirror) + generate_sources_list(release, mirror) old_mir = util.get_cfg_option_str(cfg,'apt_old_mirror', \ "archive.ubuntu.com/ubuntu") rename_apt_lists(old_mir, mirror) # process 'apt_sources' if cfg.has_key('apt_sources'): - errors = add_sources(cfg['apt_sources']) + errors = add_sources(cfg['apt_sources'], + { 'MIRROR' : mirror, 'RELEASE' : release } ) for e in errors: log.warn("Source Error: %s\n" % ':'.join(e)) @@ -96,17 +99,18 @@ def rename_apt_lists(omirror,new_mirror,lists_d="/var/lib/apt/lists"): for file in glob.glob("%s_*" % oprefix): os.rename(file,"%s%s" % (nprefix, file[olen:])) -def generate_sources_list(mirror): +def get_release(): stdout, stderr = subprocess.Popen(['lsb_release', '-cs'], stdout=subprocess.PIPE).communicate() - codename = stdout.strip() + return(stdout.strip()) +def generate_sources_list(codename, mirror): util.render_to_file('sources.list', '/etc/apt/sources.list', \ { 'mirror' : mirror, 'codename' : codename }) # srclist is a list of dictionaries, # each entry must have: 'source' # may have: key, ( keyid and keyserver) -def add_sources(srclist): +def add_sources(srclist, searchList={ }): elst = [] for ent in srclist: @@ -121,6 +125,8 @@ def add_sources(srclist): elst.append([source, "add-apt-repository failed"]) continue + source = util.render_string(source, searchList) + if not ent.has_key('filename'): ent['filename']='cloud_config_sources.list' diff --git a/cloudinit/CloudConfig/cc_final_message.py b/cloudinit/CloudConfig/cc_final_message.py new file mode 100644 index 00000000..4d72e409 --- /dev/null +++ b/cloudinit/CloudConfig/cc_final_message.py @@ -0,0 +1,55 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +from cloudinit.CloudConfig import per_always +import sys +from cloudinit import util, boot_finished +import time + +frequency = per_always + +final_message = "cloud-init boot finished at $TIMESTAMP. Up $UPTIME seconds" + +def handle(name,cfg,cloud,log,args): + if len(args) != 0: + msg_in = args[0] + else: + msg_in = util.get_cfg_option_str(cfg,"final_message",final_message) + + try: + uptimef=open("/proc/uptime") + uptime=uptimef.read().split(" ")[0] + uptimef.close() + except IOError as e: + log.warn("unable to open /proc/uptime\n") + uptime = "na" + + + try: + ts = time.strftime("%a, %d %b %Y %H:%M:%S %z",time.gmtime()) + except: + ts = "na" + + try: + subs = { 'UPTIME' : uptime, 'TIMESTAMP' : ts } + sys.stdout.write(util.render_string(msg_in, subs)) + except Exception as e: + log.warn("failed to render string to stdout: %s" % e) + + fp = open(boot_finished, "wb") + fp.write(uptime + "\n") + fp.close() diff --git a/cloudinit/CloudConfig/cc_keys_to_console.py b/cloudinit/CloudConfig/cc_keys_to_console.py new file mode 100644 index 00000000..47227b76 --- /dev/null +++ b/cloudinit/CloudConfig/cc_keys_to_console.py @@ -0,0 +1,31 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +from cloudinit.CloudConfig import per_instance +import subprocess + +frequency = per_instance + +def handle(name,cfg,cloud,log,args): + write_ssh_prog='/usr/lib/cloud-init/write-ssh-key-fingerprints' + try: + confp = open('/dev/console',"wb") + subprocess.call(write_ssh_prog,stdout=confp) + confp.close() + except: + log.warn("writing keys to console value") + raise diff --git a/cloudinit/CloudConfig/cc_locale.py b/cloudinit/CloudConfig/cc_locale.py new file mode 100644 index 00000000..c164b5ba --- /dev/null +++ b/cloudinit/CloudConfig/cc_locale.py @@ -0,0 +1,43 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import cloudinit.util as util +import subprocess +import traceback + +def apply_locale(locale): + subprocess.Popen(['locale-gen', locale]).communicate() + subprocess.Popen(['update-locale', locale]).communicate() + + util.render_to_file('default-locale', '/etc/default/locale', \ + { '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()) + + if not locale: return + + log.debug("setting locale to %s" % locale) + + try: + apply_locale(locale) + except Exception, e: + log.debug(traceback.format_exc(e)) + raise Exception("failed to apply locale %s" % locale) diff --git a/cloudinit/CloudConfig/cc_phone_home.py b/cloudinit/CloudConfig/cc_phone_home.py new file mode 100644 index 00000000..ee463757 --- /dev/null +++ b/cloudinit/CloudConfig/cc_phone_home.py @@ -0,0 +1,99 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +from cloudinit.CloudConfig import per_instance +import cloudinit.util as util +from time import sleep + +frequency = per_instance +post_list_all = [ 'pub_key_dsa', 'pub_key_rsa', '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, instance_id +# +def handle(name,cfg,cloud,log,args): + if len(args) != 0: + ph_cfg = util.readconf(args[0]) + else: + if not 'phone_home' in cfg: return + ph_cfg = cfg['phone_home'] + + if 'url' not in ph_cfg: + log.warn("no 'url' token in phone_home") + return + + url = ph_cfg['url'] + post_list = ph_cfg.get('post', 'all') + tries = ph_cfg.get('tries',10) + try: + tries = int(tries) + except: + log.warn("tries is not an integer. using 10") + tries = 10 + + 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', + } + + for n, path in pubkeys.iteritems(): + try: + fp = open(path, "rb") + all_keys[n] = fp.read() + all_keys[n] + fp.close() + except: + log.warn("%s: failed to open in phone_home" % path) + + submit_keys = { } + for k in post_list: + if k in all_keys: + submit_keys[k] = all_keys[k] + else: + submit_keys[k] = "N/A" + log.warn("requested key %s from 'post' list not available") + + url = util.render_string(url, { 'INSTANCE_ID' : all_keys['instance_id'] }) + + last_e = None + for i in range(0,tries): + try: + util.readurl(url, submit_keys) + log.debug("succeeded submit to %s on try %i" % (url, i+1)) + return + except Exception, e: + log.debug("failed to post to %s on try %i" % (url, i+1)) + last_e = e + sleep(3) + + log.warn("failed to post to %s in %i tries" % (url, tries)) + if last_e: raise(last_e) + + return diff --git a/cloudinit/CloudConfig/cc_resizefs.py b/cloudinit/CloudConfig/cc_resizefs.py new file mode 100644 index 00000000..11a10005 --- /dev/null +++ b/cloudinit/CloudConfig/cc_resizefs.py @@ -0,0 +1,54 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import cloudinit.util as util +import subprocess +import traceback + +def handle(name,cfg,cloud,log,args): + if len(args) != 0: + resize_root = False + if str(value).lower() in [ 'true', '1', 'on', 'yes']: + resize_root = True + else: + resize_root = util.get_cfg_option_bool(cfg,"resize_rootfs",False) + + if not resize_root: return + + log.debug("resizing root filesystem on first boot") + + cmd = ['blkid', '-sTYPE', '-ovalue', '/dev/root'] + try: + (fstype,err) = util.subp(cmd) + except Exception, e: + log.warn("Failed to get filesystem type via %s" % cmd) + raise + + if fstype.startswith("ext"): + resize_cmd = [ 'resize2fs', '/dev/root' ] + elif fstype == "xfs": + resize_cmd = [ 'xfs_growfs', '/dev/root' ] + else: + log.debug("not resizing unknown filesystem %s" % fstype) + return + + try: + (out,err) = util.subp(resize_cmd) + except Exception, e: + log.warn("Failed to resize filesystem (%s,%s)" % cmd) + raise + diff --git a/cloudinit/CloudConfig/cc_rightscale_userdata.py b/cloudinit/CloudConfig/cc_rightscale_userdata.py new file mode 100644 index 00000000..a90e6d18 --- /dev/null +++ b/cloudinit/CloudConfig/cc_rightscale_userdata.py @@ -0,0 +1,73 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +## +## 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 cloudinit.util as util +from cloudinit.CloudConfig import per_once, per_always, per_instance +from cloudinit import get_ipath_cur +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 %s" % my_name) + return + + try: + mdict = parse_qs(cloud.get_userdata_raw()) + if not my_hookname in mdict: return + except: + log.warn("failed to urlparse.parse_qa(userdata_raw())") + raise + + scripts_d = get_ipath_cur('scripts') + i = 0 + errors = [ ] + first_e = None + for url in mdict[my_hookname]: + fname = "%s/rightscale-%02i" % (scripts_d,i) + i = i +1 + try: + content = util.readurl(url) + util.write_file(fname, content, mode=0700) + except Exception, e: + if not first_e: first_e = None + log.warn("%s failed to read %s: %s" % (my_name, url, e)) + + if first_e: + raise(e) diff --git a/cloudinit/CloudConfig/cc_rsyslog.py b/cloudinit/CloudConfig/cc_rsyslog.py new file mode 100644 index 00000000..3320dbb2 --- /dev/null +++ b/cloudinit/CloudConfig/cc_rsyslog.py @@ -0,0 +1,99 @@ +# vi: ts=4 expandtab syntax=python +# +# Copyright (C) 2009-2010 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import cloudinit +import logging +import cloudinit.util as util +import subprocess +import traceback + +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: return + + def_dir = cfg.get('rsyslog_dir', DEF_DIR) + def_fname = cfg.get('rsyslog_filename', DEF_FILENAME) + + entries = cfg['rsyslog'] + + files = [ ] + elst = [ ] + for ent in cfg['rsyslog']: + if isinstance(ent,dict): + if not "content" in ent: + elst.append((ent, "no 'content' entry")) + continue + content = ent['content'] + filename = ent.get("filename", def_fname) + else: + content = ent + filename = def_fname + + if not filename.startswith("/"): + filename = "%s/%s" % (def_dir,filename) + + omode = "ab" + # truncate filename first time you see it + if filename not in files: + omode = "wb" + files.append(filename) + + try: + util.write_file(filename, content + "\n", omode=omode) + except Exception, e: + log.debug(traceback.format_exc(e)) + elst.append((content, "failed to write to %s" % filename)) + + # need 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") + p = util.subp(['service', 'rsyslog', 'restart']) + restarted = True + + except Exception, e: + elst.append(("restart", str(e))) + + if restarted: + # this only needs to run if we *actually* restarted + # syslog above. + cloudinit.logging_set_from_cfg_file() + log = logging.getLogger() + log.debug("rsyslog configured %s" % files) + + for e in elst: + log.warn("rsyslog error: %s\n" % ':'.join(e)) + + return diff --git a/cloudinit/CloudConfig/cc_runcmd.py b/cloudinit/CloudConfig/cc_runcmd.py index 97d21900..afa7a441 100644 --- a/cloudinit/CloudConfig/cc_runcmd.py +++ b/cloudinit/CloudConfig/cc_runcmd.py @@ -21,7 +21,7 @@ import cloudinit.util as util def handle(name,cfg,cloud,log,args): if not cfg.has_key("runcmd"): return - outfile="%s/runcmd" % cloudinit.user_scripts_dir + outfile="%s/runcmd" % cloud.get_ipath('scripts') content="#!/bin/sh\n" escaped="%s%s%s%s" % ( "'", '\\', "'", "'" ) diff --git a/cloudinit/CloudConfig/cc_scripts_per_boot.py b/cloudinit/CloudConfig/cc_scripts_per_boot.py new file mode 100644 index 00000000..4e407fb7 --- /dev/null +++ b/cloudinit/CloudConfig/cc_scripts_per_boot.py @@ -0,0 +1,30 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import cloudinit.util as util +from cloudinit.CloudConfig import per_once, per_always, per_instance +from cloudinit import get_cpath, get_ipath_cur + +frequency = per_always +runparts_path = "%s/%s" % (get_cpath(), "scripts/per-boot") + +def handle(name,cfg,cloud,log,args): + try: + util.runparts(runparts_path) + except: + log.warn("failed to run-parts in %s" % runparts_path) + raise diff --git a/cloudinit/CloudConfig/cc_scripts_per_instance.py b/cloudinit/CloudConfig/cc_scripts_per_instance.py new file mode 100644 index 00000000..22b41185 --- /dev/null +++ b/cloudinit/CloudConfig/cc_scripts_per_instance.py @@ -0,0 +1,30 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import cloudinit.util as util +from cloudinit.CloudConfig import per_once, per_always, per_instance +from cloudinit import get_cpath, get_ipath_cur + +frequency = per_instance +runparts_path = "%s/%s" % (get_cpath(), "scripts/per-instance") + +def handle(name,cfg,cloud,log,args): + try: + util.runparts(runparts_path) + except: + log.warn("failed to run-parts in %s" % runparts_path) + raise diff --git a/cloudinit/CloudConfig/cc_scripts_per_once.py b/cloudinit/CloudConfig/cc_scripts_per_once.py new file mode 100644 index 00000000..9d752325 --- /dev/null +++ b/cloudinit/CloudConfig/cc_scripts_per_once.py @@ -0,0 +1,30 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import cloudinit.util as util +from cloudinit.CloudConfig import per_once, per_always, per_instance +from cloudinit import get_cpath, get_ipath_cur + +frequency = per_once +runparts_path = "%s/%s" % (get_cpath(), "scripts/per-once") + +def handle(name,cfg,cloud,log,args): + try: + util.runparts(runparts_path) + except: + log.warn("failed to run-parts in %s" % runparts_path) + raise diff --git a/cloudinit/CloudConfig/cc_scripts_user.py b/cloudinit/CloudConfig/cc_scripts_user.py new file mode 100644 index 00000000..bafecd23 --- /dev/null +++ b/cloudinit/CloudConfig/cc_scripts_user.py @@ -0,0 +1,30 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import cloudinit.util as util +from cloudinit.CloudConfig import per_once, per_always, per_instance +from cloudinit import get_cpath, get_ipath_cur + +frequency = per_instance +runparts_path = "%s/%s" % (get_ipath_cur(), "scripts") + +def handle(name,cfg,cloud,log,args): + try: + util.runparts(runparts_path) + except: + log.warn("failed to run-parts in %s" % runparts_path) + raise diff --git a/cloudinit/CloudConfig/cc_set_hostname.py b/cloudinit/CloudConfig/cc_set_hostname.py new file mode 100644 index 00000000..34621e97 --- /dev/null +++ b/cloudinit/CloudConfig/cc_set_hostname.py @@ -0,0 +1,38 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import cloudinit.util as util +import subprocess + +def handle(name,cfg,cloud,log,args): + if util.get_cfg_option_bool(cfg,"preserve_hostname",False): + log.debug("preserve_hostname is set. not setting hostname") + return(True) + + try: + hostname = util.get_cfg_option_str(cfg,"hostname",cloud.get_hostname()) + set_hostname(hostname, log) + except Exception, e: + util.logexc(log) + log.warn("failed to set hostname\n") + + return(True) + +def set_hostname(hostname, log): + subprocess.Popen(['hostname', hostname]).communicate() + util.write_file("/etc/hostname","%s\n" % hostname, 0644) + log.debug("populated /etc/hostname with %s on first boot", hostname) diff --git a/cloudinit/CloudConfig/cc_ssh.py b/cloudinit/CloudConfig/cc_ssh.py index 07527906..7b9ba5ab 100644 --- a/cloudinit/CloudConfig/cc_ssh.py +++ b/cloudinit/CloudConfig/cc_ssh.py @@ -60,18 +60,7 @@ def handle(name,cfg,cloud,log,args): send_ssh_keys_to_console() def send_ssh_keys_to_console(): - send_keys_sh = """ - { - echo - echo "#############################################################" - echo "-----BEGIN SSH HOST KEY FINGERPRINTS-----" - ssh-keygen -l -f /etc/ssh/ssh_host_rsa_key.pub - ssh-keygen -l -f /etc/ssh/ssh_host_dsa_key.pub - echo "-----END SSH HOST KEY FINGERPRINTS-----" - echo "#############################################################" - } | logger -p user.info -s -t "ec2" - """ - subprocess.call(('sh', '-c', send_keys_sh)) + subprocess.call(('/usr/lib/cloud-init/write-ssh-key-fingerprints',)) def apply_credentials(keys, user, disable_root): keys = set(keys) @@ -79,7 +68,7 @@ def apply_credentials(keys, user, disable_root): setup_user_keys(keys, user, '') if disable_root: - key_prefix = 'command="echo \'Please login as the %s user rather than root user.\';echo;sleep 10" ' % user + key_prefix = 'command="echo \'Please login as the user \\\"%s\\\" rather than the user \\\"root\\\".\';echo;sleep 10" ' % user else: key_prefix = '' diff --git a/cloudinit/CloudConfig/cc_ssh_import_id.py b/cloudinit/CloudConfig/cc_ssh_import_id.py index dd4d3184..bf1314be 100644 --- a/cloudinit/CloudConfig/cc_ssh_import_id.py +++ b/cloudinit/CloudConfig/cc_ssh_import_id.py @@ -31,7 +31,7 @@ def handle(name,cfg,cloud,log,args): if len(ids) == 0: return - cmd = [ "sudo", "-Hu", user, "ssh-import-lp-id" ] + ids + cmd = [ "sudo", "-Hu", user, "ssh-import-id" ] + ids log.debug("importing ssh ids. cmd = %s" % cmd) diff --git a/cloudinit/CloudConfig/cc_update_hostname.py b/cloudinit/CloudConfig/cc_update_hostname.py new file mode 100644 index 00000000..3663c0ab --- /dev/null +++ b/cloudinit/CloudConfig/cc_update_hostname.py @@ -0,0 +1,95 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2011 Canonical Ltd. +# +# Author: Scott Moser <scott.moser@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import cloudinit.util as util +import subprocess +import errno +from cloudinit.CloudConfig 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("preserve_hostname is set. not updating hostname") + return + + try: + hostname = util.get_cfg_option_str(cfg,"hostname",cloud.get_hostname()) + prev ="%s/%s" % (cloud.get_cpath('datadir'),"previous-hostname") + update_hostname(hostname, prev, log) + except Exception, e: + log.warn("failed to set hostname\n") + raise + +# read hostname from a 'hostname' file +# allow for comments and stripping line endings. +# if file doesn't exist, or no contents, return default +def read_hostname(filename, default=None): + try: + fp = open(filename,"r") + lines = fp.readlines() + fp.close() + for line in lines: + hpos = line.find("#") + if hpos != -1: + line = line[0:hpos] + line = line.rstrip() + if line: + return line + except IOError, e: + if e.errno != errno.ENOENT: raise + return default + +def update_hostname(hostname, prev_file, log): + etc_file = "/etc/hostname" + + hostname_prev = None + hostname_in_etc = None + + try: + hostname_prev = read_hostname(prev_file) + except Exception, e: + log.warn("Failed to open %s: %s" % (prev_file, e)) + + try: + hostname_in_etc = read_hostname(etc_file) + except: + log.warn("Failed to open %s" % etc_file) + + update_files = [] + if not hostname_prev or hostname_prev != hostname: + update_files.append(prev_file) + + if (not hostname_in_etc or + (hostname_in_etc == hostname_prev and hostname_in_etc != hostname)): + update_files.append(etc_file) + + try: + for fname in update_files: + util.write_file(fname,"%s\n" % hostname, 0644) + log.debug("wrote %s to %s" % (hostname,fname)) + except: + log.warn("failed to write hostname to %s" % fname) + + if hostname_in_etc and hostname_prev and hostname_in_etc != hostname_prev: + log.debug("%s differs from %s. assuming user maintained" % + (prev_file,etc_file)) + + if etc_file in update_files: + log.debug("setting hostname to %s" % hostname) + subprocess.Popen(['hostname', hostname]).communicate() + diff --git a/cloudinit/CloudConfig/cc_updates_check.py b/cloudinit/CloudConfig/cc_updates_check.py deleted file mode 100644 index aaa2e6b0..00000000 --- a/cloudinit/CloudConfig/cc_updates_check.py +++ /dev/null @@ -1,49 +0,0 @@ -# vi: ts=4 expandtab -# -# Copyright (C) 2009-2010 Canonical Ltd. -# -# Author: Scott Moser <scott.moser@canonical.com> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 3, as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -import cloudinit.util as util -import cloudinit -import os -import time - -cronpre = "/etc/cron.d/cloudinit" - -def handle(name,cfg,cloud,log,args): - if not util.get_cfg_option_bool(cfg, 'updates-check', True): - return - build_info = "/etc/cloud/build.info" - if not os.path.isfile(build_info): - log.warn("no %s file" % build_info) - - avail="%s/%s" % ( cloudinit.datadir, "available.build" ) - cmd=( "uec-query-builds", "--system-suite", "--config", "%s" % build_info, - "--output", "%s" % avail, "is-update-available" ) - try: - util.subp(cmd) - except: - log.warn("failed to execute uec-query-build for updates check") - - # add a cron entry for this hour and this minute every day - try: - cron=open("%s-%s" % (cronpre, "updates") ,"w") - cron.write("%s root %s\n" % \ - (time.strftime("%M %H * * *"),' '.join(cmd))) - cron.close() - except: - log.warn("failed to enable cron update system check") - raise - |
