summaryrefslogtreecommitdiff
path: root/cloudinit/CloudConfig
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/CloudConfig')
-rw-r--r--cloudinit/CloudConfig/__init__.py158
-rw-r--r--cloudinit/CloudConfig/cc_apt_update_upgrade.py24
-rw-r--r--cloudinit/CloudConfig/cc_final_message.py55
-rw-r--r--cloudinit/CloudConfig/cc_keys_to_console.py31
-rw-r--r--cloudinit/CloudConfig/cc_locale.py43
-rw-r--r--cloudinit/CloudConfig/cc_phone_home.py99
-rw-r--r--cloudinit/CloudConfig/cc_resizefs.py54
-rw-r--r--cloudinit/CloudConfig/cc_rightscale_userdata.py73
-rw-r--r--cloudinit/CloudConfig/cc_rsyslog.py99
-rw-r--r--cloudinit/CloudConfig/cc_runcmd.py2
-rw-r--r--cloudinit/CloudConfig/cc_scripts_per_boot.py30
-rw-r--r--cloudinit/CloudConfig/cc_scripts_per_instance.py30
-rw-r--r--cloudinit/CloudConfig/cc_scripts_per_once.py30
-rw-r--r--cloudinit/CloudConfig/cc_scripts_user.py30
-rw-r--r--cloudinit/CloudConfig/cc_set_hostname.py38
-rw-r--r--cloudinit/CloudConfig/cc_ssh.py15
-rw-r--r--cloudinit/CloudConfig/cc_ssh_import_id.py2
-rw-r--r--cloudinit/CloudConfig/cc_update_hostname.py95
-rw-r--r--cloudinit/CloudConfig/cc_updates_check.py49
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
-