# vi: ts=4 expandtab # # Copyright (C) 2009-2010 Canonical Ltd. # # Author: Scott Moser # # 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 yaml import os import os.path import errno import subprocess from Cheetah.Template import Template import cloudinit import urllib2 import logging import traceback import re WARN = logging.WARN DEBUG = logging.DEBUG INFO = logging.INFO def read_conf(fname): try: stream = open(fname,"r") conf = yaml.load(stream) stream.close() return conf except IOError as e: if e.errno == errno.ENOENT: return { } raise def get_base_cfg(cfgfile,cfg_builtin="", parsed_cfgs=None): kerncfg = { } syscfg = { } if parsed_cfgs and cfgfile in parsed_cfgs: return(parsed_cfgs[cfgfile]) contents = read_file_with_includes(cfgfile) if contents: syscfg = yaml.load(contents) kern_contents = read_cc_from_cmdline() if kern_contents: kerncfg = yaml.load(kern_contents) # kernel parameters override system config combined = mergedict(kerncfg, syscfg) if cfg_builtin: builtin = yaml.load(cfg_builtin) fin = mergedict(combined,builtin) else: fin = combined if parsed_cfgs != None: parsed_cfgs[cfgfile] = fin return(fin) def get_cfg_option_bool(yobj, key, default=False): if not yobj.has_key(key): return default val = yobj[key] if val is True: return True if str(val).lower() in [ 'true', '1', 'on', 'yes']: return True return False def get_cfg_option_str(yobj, key, default=None): if not yobj.has_key(key): return default return yobj[key] def get_cfg_option_list_or_str(yobj, key, default=None): if not yobj.has_key(key): return default if isinstance(yobj[key],list): return yobj[key] return([yobj[key]]) # merge values from src into cand. # if src has a key, cand will not override def mergedict(src,cand): if isinstance(src,dict) and isinstance(cand,dict): for k,v in cand.iteritems(): if k not in src: src[k] = v else: src[k] = mergedict(src[k],v) return src def write_file(file,content,mode=0644,omode="wb"): try: os.makedirs(os.path.dirname(file)) except OSError as e: if e.errno != errno.EEXIST: raise e f=open(file,omode) if mode != None: os.chmod(file,mode) f.write(content) f.close() # get keyid from keyserver def getkeybyid(keyid,keyserver): 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] return(subp(args)[0]) def subp(args, input=None): s_in = None if input is not None: s_in = subprocess.PIPE sp = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=s_in) out,err = sp.communicate(input) if sp.returncode is not 0: raise subprocess.CalledProcessError(sp.returncode,args) return(out,err) def render_to_file(template, outfile, searchList): t = Template(file='/etc/cloud/templates/%s.tmpl' % template, searchList=[searchList]) f = open(outfile, 'w') f.write(t.respond()) f.close() def render_string(template, searchList): return(Template(template, searchList=[searchList]).respond()) # read_optional_seed # returns boolean indicating success or failure (presense of files) # if files are present, populates 'fill' dictionary with 'user-data' and # 'meta-data' entries def read_optional_seed(fill,base="",ext="", timeout=2): try: (md,ud) = read_seeded(base,ext,timeout) fill['user-data']= ud fill['meta-data']= md return True except OSError, e: if e.errno == errno.ENOENT: return False raise # raise OSError with enoent if not found def read_seeded(base="", ext="", timeout=2): if base.startswith("/"): base="file://%s" % base if base.find("%s") >= 0: ud_url = base % ("user-data" + ext) md_url = base % ("meta-data" + ext) else: ud_url = "%s%s%s" % (base, "user-data", ext) md_url = "%s%s%s" % (base, "meta-data", ext) try: md_resp = urllib2.urlopen(urllib2.Request(md_url), timeout=timeout) ud_resp = urllib2.urlopen(urllib2.Request(ud_url), timeout=timeout) md_str = md_resp.read() ud = ud_resp.read() md = yaml.load(md_str) return(md,ud) except urllib2.HTTPError: raise except urllib2.URLError, e: if isinstance(e.reason,OSError) and e.reason.errno == errno.ENOENT: raise e.reason raise e def logexc(log,lvl=logging.DEBUG): log.log(lvl,traceback.format_exc()) class RecursiveInclude(Exception): pass def read_file_with_includes(fname, rel = ".", stack=[], patt = None): if not fname.startswith("/"): fname = os.sep.join((rel, fname)) fname = os.path.realpath(fname) if fname in stack: raise(RecursiveInclude("%s recursively included" % fname)) if len(stack) > 10: raise(RecursiveInclude("%s included, stack size = %i" % (fname, len(stack)))) if patt == None: patt = re.compile("^#(opt_include|include)[ \t].*$",re.MULTILINE) try: fp = open(fname) contents = fp.read() fp.close() except: raise rel = os.path.dirname(fname) stack.append(fname) cur = 0 clen = len(contents) while True: match = patt.search(contents[cur:]) if not match: break loc = match.start() + cur endl = match.end() + cur (key, cur_fname) = contents[loc:endl].split(None,2) cur_fname = cur_fname.strip() try: inc_contents = read_file_with_includes(cur_fname, rel, stack, patt) except IOError, e: if e.errno == errno.ENOENT and key == "#opt_include": inc_contents = "" else: raise contents = contents[0:loc] + inc_contents + contents[endl+1:] cur = loc + len(inc_contents) stack.pop() return(contents) def get_cmdline(): if 'DEBUG_PROC_CMDLINE' in os.environ: cmdline = os.environ["DEBUG_PROC_CMDLINE"] else: try: cmdfp = open("/proc/cmdline") cmdline = cmdfp.read().strip() cmdfp.close() except: cmdline = "" return(cmdline) def read_cc_from_cmdline(cmdline=None): # this should support reading cloud-config information from # the kernel command line. It is intended to support content of the # format: # cc: [end_cc] # this would include: # cc: ssh_import_id: [smoser, kirkland]\\n # cc: ssh_import_id: [smoser, bob]\\nruncmd: [ [ ls, -l ], echo hi ] end_cc # cc:ssh_import_id: [smoser] end_cc cc:runcmd: [ [ ls, -l ] ] end_cc if cmdline is None: cmdline = get_cmdline() tag_begin="cc:" tag_end="end_cc" begin_l = len(tag_begin) end_l = len(tag_end) clen = len(cmdline) tokens = [ ] begin = cmdline.find(tag_begin) while begin >= 0: end = cmdline.find(tag_end, begin + begin_l) if end < 0: end = clen tokens.append(cmdline[begin+begin_l:end].lstrip().replace("\\n","\n")) begin = cmdline.find(tag_begin, end + end_l) return('\n'.join(tokens)) def ensure_dirs(dirlist, mode=0755): fixmodes = [] for d in dirlist: try: if mode != None: os.makedirs(d) else: os.makedirs(d, mode) except OSError as e: if e.errno != errno.EEXIST: raise if mode != None: fixmodes.append(d) for d in fixmodes: os.chmod(d, mode) def chownbyname(fname,user=None,group=None): uid = -1 gid = -1 if user == None and group == None: return if user: import pwd uid = pwd.getpwnam(user).pw_uid if group: import grp gid = grp.getgrnam(group).gr_gid os.chown(fname,uid,gid)