summaryrefslogtreecommitdiff
path: root/cloudinit/CloudConfig.py
diff options
context:
space:
mode:
authorScott Moser <smoser@ubuntu.com>2010-02-03 18:09:48 -0500
committerScott Moser <smoser@ubuntu.com>2010-02-03 18:09:48 -0500
commitaed1c1e9fda1e78d19305e90f554d0dcb5388cd7 (patch)
tree7f0dd15537201ea49217ba6ec3040ede9b0426f4 /cloudinit/CloudConfig.py
parentff522791cb2b58e83da4efea1be76757f05a1f1f (diff)
downloadvyos-cloud-init-aed1c1e9fda1e78d19305e90f554d0dcb5388cd7.tar.gz
vyos-cloud-init-aed1c1e9fda1e78d19305e90f554d0dcb5388cd7.zip
globally remove ec2init and rename to cloudinit
Diffstat (limited to 'cloudinit/CloudConfig.py')
-rw-r--r--cloudinit/CloudConfig.py407
1 files changed, 407 insertions, 0 deletions
diff --git a/cloudinit/CloudConfig.py b/cloudinit/CloudConfig.py
new file mode 100644
index 00000000..4a498866
--- /dev/null
+++ b/cloudinit/CloudConfig.py
@@ -0,0 +1,407 @@
+#
+# Common code for the EC2 configuration files in Ubuntu
+# Copyright (C) 2008-2010 Canonical Ltd.
+#
+# Author: Chuck Short <chuck.short@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 yaml
+import re
+import cloudinit
+import cloudinit.util as util
+import subprocess
+import os
+import glob
+import sys
+
+per_instance="once-per-instance"
+
+class CloudConfig():
+ cfgfile = None
+ handlers = { }
+ cfg = None
+
+ def __init__(self,cfgfile):
+ self.cloud = cloudinit.EC2Init()
+ self.cfg = self.get_config_obj(cfgfile)
+ self.cloud.get_data_source()
+ self.add_handler('apt-update-upgrade', self.h_apt_update_upgrade)
+ self.add_handler('config-ssh')
+ self.add_handler('disable-ec2-metadata',
+ self.h_disable_ec2_metadata, "always")
+ self.add_handler('config-mounts')
+
+ def get_config_obj(self,cfgfile):
+ f=file(cfgfile)
+ cfg=yaml.load(f.read())
+ f.close()
+ if cfg is None: cfg = { }
+ return(util.mergedict(cfg,self.cloud.cfg))
+
+ def convert_old_config(self):
+ # support reading the old ConfigObj format file and turning it
+ # into a yaml string
+ try:
+ f = file(self.conffile)
+ str=file.read().replace('=',': ')
+ f.close()
+ return str
+ except:
+ return("")
+
+ def add_handler(self, name, handler=None, freq=None):
+ if handler is None:
+ try:
+ handler=getattr(self,'h_%s' % name.replace('-','_'))
+ except:
+ raise Exception("Unknown hander for name %s" %name)
+ if freq is None:
+ freq = per_instance
+
+ self.handlers[name]= { 'handler': handler, 'freq': freq }
+
+ def get_handler_info(self, name):
+ return(self.handlers[name]['handler'], self.handlers[name]['freq'])
+
+ def parse_ssh_keys(self):
+ disableRoot = self.cfg['disable_root']
+ if disableRoot == 'true':
+ value = 'disabled_root'
+ return value
+ else:
+ ec2Key = self.cfg['ec2_fetch_key']
+ if ec2Key != 'none':
+ value = 'default_key'
+ return value
+ else:
+ return ec2Key
+
+ def handle(self, name, args):
+ handler = None
+ freq = None
+ try:
+ (handler, freq) = self.get_handler_info(name)
+ except:
+ raise Exception("Unknown config key %s\n" % name)
+
+ self.cloud.sem_and_run(name, freq, handler, [ name, args ])
+
+ def h_apt_update_upgrade(self,name,args):
+ update = util.get_cfg_option_bool(self.cfg, 'apt_update', False)
+ upgrade = util.get_cfg_option_bool(self.cfg, 'apt_upgrade', False)
+
+ if not util.get_cfg_option_bool(self.cfg, \
+ 'apt_preserve_sources_list', False):
+ if self.cfg.has_key("apt_mirror"):
+ mirror = self.cfg["apt_mirror"]
+ else:
+ mirror = self.cloud.get_mirror()
+ generate_sources_list(mirror)
+
+ # process 'apt_sources'
+ if self.cfg.has_key('apt_sources'):
+ errors = add_sources(self.cfg['apt_sources'])
+ for e in errors:
+ warn("Source Error: %s\n" % ':'.join(e))
+
+ pkglist = []
+ if 'packages' in self.cfg:
+ if isinstance(self.cfg['packages'],list):
+ pkglist = self.cfg['packages']
+ else: pkglist.append(self.cfg['packages'])
+
+ if update or upgrade or pkglist:
+ #retcode = subprocess.call(list)
+ subprocess.Popen(['apt-get', 'update']).communicate()
+
+ e=os.environ.copy()
+ e['DEBIAN_FRONTEND']='noninteractive'
+
+ if upgrade:
+ subprocess.Popen(['apt-get', 'upgrade', '--assume-yes'], env=e).communicate()
+
+ if pkglist:
+ cmd=['apt-get', 'install', '--assume-yes']
+ cmd.extend(pkglist)
+ subprocess.Popen(cmd, env=e).communicate()
+
+ return(True)
+
+ def h_disable_ec2_metadata(self,name,args):
+ if util.get_cfg_option_bool(self.cfg, "disable_ec2_metadata", False):
+ fwall="route add -host 169.254.169.254 reject"
+ subprocess.call(fwall.split(' '))
+
+ def h_config_ssh(self,name,args):
+ # remove the static keys from the pristine image
+ for f in glob.glob("/etc/ssh/ssh_host_*_key*"):
+ try: os.unlink(f)
+ except: pass
+
+ if self.cfg.has_key("ssh_keys"):
+ # if there are keys in cloud-config, use them
+ 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)
+ }
+
+ for key,val in self.cfg["ssh_keys"].items():
+ if key2file.has_key(key):
+ util.write_file(key2file[key][0],val,key2file[key][1])
+ else:
+ # if not, generate them
+ genkeys ='ssh-keygen -f /etc/ssh/ssh_host_rsa_key -t rsa -N ""; '
+ genkeys+='ssh-keygen -f /etc/ssh/ssh_host_dsa_key -t dsa -N ""; '
+ subprocess.call(('sh', '-c', "{ %s } </dev/null" % (genkeys)))
+
+ try:
+ user = util.get_cfg_option_str(self.cfg,'user')
+ disable_root = util.get_cfg_option_bool(self.cfg, "disable_root", True)
+ keys = self.cloud.get_public_ssh_keys()
+
+ if self.cfg.has_key("ssh_authorized_keys"):
+ cfgkeys = self.cfg["ssh_authorized_keys"]
+ keys.extend(cfgkeys)
+
+ apply_credentials(keys,user,disable_root)
+ except:
+ warn("applying credentials failed!\n")
+
+ send_ssh_keys_to_console()
+
+ def h_ec2_ebs_mounts(self,name,args):
+ print "Warning, not doing anything for config %s" % name
+
+ def h_config_setup_raid(self,name,args):
+ print "Warning, not doing anything for config %s" % name
+
+ def h_config_runurl(self,name,args):
+ print "Warning, not doing anything for config %s" % name
+
+ def h_config_mounts(self,name,args):
+ # handle 'mounts'
+
+ # these are our default set of mounts
+ defmnts = [ [ "ephemeral0", "/mnt", "auto", "defaults", "0", "0" ],
+ [ "swap", "none", "swap", "sw", "0", "0" ] ]
+
+ # fs_spec, fs_file, fs_vfstype, fs_mntops, fs-freq, fs_passno
+ defvals = [ None, None, "auto", "defaults", "0", "0" ]
+
+ cfgmnt = [ ]
+ if self.cfg.has_key("mounts"):
+ cfgmnt = self.cfg["mounts"]
+
+ for i in range(len(cfgmnt)):
+ # skip something that wasn't a list
+ if not isinstance(cfgmnt[i],list): continue
+
+ # workaround, allow user to specify 'ephemeral'
+ # rather than more ec2 correct 'ephemeral0'
+ if cfgmnt[i] == "ephemeral":
+ cfgmnt[i] = "ephemeral0"
+
+ newname = cfgmnt[i][0]
+ if not newname.startswith("/"):
+ newname = self.cloud.device_name_to_device(cfgmnt[i][0])
+ if newname is not None:
+ cfgmnt[i][0] = newname
+ else:
+ # there is no good way of differenciating between
+ # a name that *couldn't* exist in the md service and
+ # one that merely didnt
+ # in order to allow user to specify 'sda3' rather
+ # than '/dev/sda3', go through some hoops
+ ok = False
+ for f in [ "/", "sd", "hd", "vd", "xvd" ]:
+ if cfgmnt[i][0].startswith(f):
+ ok = True
+ break
+ if not ok:
+ cfgmnt[i][1] = None
+
+ for i in range(len(cfgmnt)):
+ # fill in values with
+ 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 not cfgmnt[i][0].startswith("/"):
+ cfgmnt[i][0]="/dev/%s" % cfgmnt[i][0]
+
+ # 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:
+ devname = self.cloud.device_name_to_device(defmnt[0])
+ if devname is None: continue
+ if not devname.startswith("/"):
+ defmnt[0] = "/dev/%s" % devname
+
+ cfgmnt_has = False
+ for cfgm in cfgmnt:
+ if cfgm[0] == defmnt[0]:
+ cfgmnt_has = True
+ break
+
+ if cfgmnt_has: 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 = filter(lambda x: x[1] is not None, cfgmnt)
+
+ if len(actlist) == 0: return
+
+ needswap = False
+ dirs = [ ]
+
+ fstab=file("/etc/fstab","ab")
+ fstab.write("# cloud-config mounts\n")
+ for line in actlist:
+ fstab.write('\t'.join(line) + "\n")
+ if line[2] == "swap": needswap = True
+ if line[1].startswith("/"): dirs.append(line[1])
+ fstab.close()
+
+ if needswap:
+ try: util.subp(("swapon", "-a"))
+ except: warn("Failed to enable swap")
+
+ for d in dirs:
+ if os.path.exists(d): continue
+ try: os.makedirs(d)
+ except: warn("Failed to make '%s' config-mount\n",d)
+
+ try: util.subp(("mount","-a"))
+ except: pass
+
+
+
+
+def apply_credentials(keys, user, disable_root):
+ keys = set(keys)
+ if user:
+ 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
+ else:
+ key_prefix = ''
+
+ setup_user_keys(keys, 'root', key_prefix)
+
+def setup_user_keys(keys, user, key_prefix):
+ import pwd
+ saved_umask = os.umask(077)
+
+ pwent = pwd.getpwnam(user)
+
+ ssh_dir = '%s/.ssh' % pwent.pw_dir
+ if not os.path.exists(ssh_dir):
+ os.mkdir(ssh_dir)
+ os.chown(ssh_dir, pwent.pw_uid, pwent.pw_gid)
+
+ authorized_keys = '%s/.ssh/authorized_keys' % pwent.pw_dir
+ fp = open(authorized_keys, 'a')
+ fp.write(''.join(['%s%s\n' % (key_prefix, key) for key in keys]))
+ fp.close()
+
+ os.chown(authorized_keys, pwent.pw_uid, pwent.pw_gid)
+
+ os.umask(saved_umask)
+
+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))
+
+
+def warn(str):
+ sys.stderr.write("Warning:%s\n" % str)
+
+# srclist is a list of dictionaries,
+# each entry must have: 'source'
+# may have: key, ( keyid and keyserver)
+def add_sources(srclist):
+ elst = []
+
+ for ent in srclist:
+ if not ent.has_key('source'):
+ elst.append([ "", "missing source" ])
+ continue
+
+ source=ent['source']
+ if source.startswith("ppa:"):
+ try: util.subp(["add-apt-repository",source])
+ except:
+ elst.append([source, "add-apt-repository failed"])
+ continue
+
+ if not ent.has_key('filename'):
+ ent['filename']='cloud_config_sources.list'
+
+ if not ent['filename'].startswith("/"):
+ ent['filename'] = "%s/%s" % \
+ ("/etc/apt/sources.list.d/", ent['filename'])
+
+ if ( ent.has_key('keyid') and not ent.has_key('key') ):
+ ks = "keyserver.ubuntu.com"
+ if ent.has_key('keyserver'): ks = ent['keyserver']
+ try:
+ ent['key'] = util.getkeybyid(ent['keyid'], ks)
+ except:
+ elst.append([source,"failed to get key from %s" % ks])
+ continue
+
+ if ent.has_key('key'):
+ try: util.subp(('apt-key', 'add', '-'), ent['key'])
+ except:
+ elst.append([source, "failed add key"])
+
+ try: util.write_file(ent['filename'], source + "\n")
+ except:
+ elst.append([source, "failed write to file %s" % ent['filename']])
+
+ return(elst)
+
+
+def generate_sources_list(mirror):
+ stdout, stderr = subprocess.Popen(['lsb_release', '-cs'], stdout=subprocess.PIPE).communicate()
+ codename = stdout.strip()
+
+ util.render_to_file('sources.list', '/etc/apt/sources.list', \
+ { 'mirror' : mirror, 'codename' : codename })