diff options
Diffstat (limited to 'cloudinit/__init__.py')
-rw-r--r-- | cloudinit/__init__.py | 309 |
1 files changed, 176 insertions, 133 deletions
diff --git a/cloudinit/__init__.py b/cloudinit/__init__.py index 6a59a23f..9c02ff8a 100644 --- a/cloudinit/__init__.py +++ b/cloudinit/__init__.py @@ -18,94 +18,35 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # -datadir = '/var/lib/cloud/data' -semdir = '/var/lib/cloud/sem' -pluginsdir = datadir + '/plugins' -cachedir = datadir + '/cache' -userdata_raw = datadir + '/user-data.txt' -userdata = datadir + '/user-data.txt.i' -user_scripts_dir = datadir + "/scripts" -boothooks_dir = datadir + "/boothooks" -cloud_config = datadir + '/cloud-config.txt' -#cloud_config = '/tmp/cloud-config.txt' -data_source_cache = cachedir + '/obj.pkl' +varlibdir = '/var/lib/cloud' +cur_instance_link = varlibdir + "/instance" +boot_finished = cur_instance_link + "/boot-finished" system_config = '/etc/cloud/cloud.cfg' +seeddir = varlibdir + "/seed" cfg_env_name = "CLOUD_CFG" cfg_builtin = """ +log_cfgs: [ ] cloud_type: auto -user: ubuntu -disable_root: 1 - -cloud_config_modules: - - mounts - - ssh-import-id - - ssh - - grub-dpkg - - apt-update-upgrade - - puppet - - updates-check - - disable-ec2-metadata - - runcmd - - byobu - -log_cfg: built_in +def_log_file: /var/log/cloud-init.log +syslog_fix_perms: syslog:adm """ - -def_log_file = '/var/log/cloud-init.log' logger_name = "cloudinit" -built_in_log_base = """ -[loggers] -keys=root,cloudinit - -[handlers] -keys=consoleHandler,cloudLogHandler - -[formatters] -keys=simpleFormatter,arg0Formatter - -[logger_root] -level=DEBUG -handlers=consoleHandler,cloudLogHandler - -[logger_cloudinit] -level=DEBUG -qualname=cloudinit -handlers= -propagate=1 - -[handler_consoleHandler] -class=StreamHandler -level=WARNING -formatter=arg0Formatter -args=(sys.stderr,) - -[formatter_arg0Formatter] -format=%(asctime)s - %(filename)s[%(levelname)s]: %(message)s - -[formatter_simpleFormatter] -format=[CLOUDINIT] %(asctime)s - %(filename)s[%(levelname)s]: %(message)s -datefmt= - -""" - -built_in_log_clougLogHandlerLog=""" -[handler_cloudLogHandler] -class=FileHandler -level=DEBUG -formatter=simpleFormatter -args=('__CLOUDINIT_LOGGER_FILE__',) -""" - -built_in_log_cloudLogHandlerSyslog= """ -[handler_cloudLogHandler] -class=handlers.SysLogHandler -level=DEBUG -formatter=simpleFormatter -args=("/dev/log", handlers.SysLogHandler.LOG_USER) -""" - +pathmap = { + "handlers" : "/handlers", + "scripts" : "/scripts", + "sem" : "/sem", + "boothooks" : "/boothooks", + "userdata_raw" : "/user-data.txt", + "userdata" : "/user-data-raw.txt.i", + "obj_pkl" : "/obj.pkl", + "cloud_config" : "/cloud-config.txt", + "datadir" : "/data", + None : "", +} + +parsed_cfgs = { } import os from configobj import ConfigObj @@ -121,43 +62,44 @@ import util import logging import logging.config import StringIO +import glob class NullHandler(logging.Handler): - def emit(self,record): pass + def emit(self,record): pass log = logging.getLogger(logger_name) log.addHandler(NullHandler()) def logging_set_from_cfg_file(cfg_file=system_config): - logging_set_from_cfg(util.get_base_cfg(cfg_file,cfg_builtin)) + logging_set_from_cfg(util.get_base_cfg(cfg_file,cfg_builtin,parsed_cfgs)) + +def logging_set_from_cfg(cfg): + log_cfgs = [] + logcfg=util.get_cfg_option_str(cfg, "log_cfg", False) + if logcfg: + # if there is a 'logcfg' entry in the config, respect + # it, it is the old keyname + log_cfgs = [ logcfg ] + elif "log_cfgs" in cfg: + for cfg in cfg['log_cfgs']: + if isinstance(cfg,list): + log_cfgs.append('\n'.join(cfg)) + else: + log_cfgs.append() + + if not len(log_cfgs): + sys.stderr.write("Warning, no logging configured\n") + return -def logging_set_from_cfg(cfg, logfile=None): - if logfile is None: + for logcfg in log_cfgs: try: - open(def_log_file,"a").close() - logfile = def_log_file - except IOError as e: - if e.errno == errno.EACCES: - logfile = "/dev/null" - else: raise - - logcfg=util.get_cfg_option_str(cfg, "log_cfg", "built_in") - failsafe = "%s\n%s" % (built_in_log_base, built_in_log_clougLogHandlerLog) - builtin = False - if logcfg.lower() == "built_in": - logcfg = "%s\n%s" % (built_in_log_base, built_in_log_cloudLogHandlerSyslog) - builtin = True - - logcfg=logcfg.replace("__CLOUDINIT_LOGGER_FILE__",logfile) - try: - logging.config.fileConfig(StringIO.StringIO(logcfg)) - return - except: - if not builtin: - sys.stderr.write("Warning, setting config.fileConfig failed\n") + logging.config.fileConfig(StringIO.StringIO(logcfg)) + return + except: + pass + + raise Exception("no valid logging found\n") - failsafe=failsafe.replace("__CLOUDINIT_LOGGER_FILE__",logfile) - logging.config.fileConfig(StringIO.StringIO(failsafe)) import DataSourceEc2 import DataSourceNoCloud @@ -174,7 +116,6 @@ class CloudInit: "all": ( "nocloud-net", "ec2" ), "local" : ( "nocloud", ), } - cfg = None part_handlers = { } old_conffile = '/etc/ec2-init/ec2-config.cfg' @@ -196,7 +137,7 @@ class CloudInit: if self.cfg: return(self.cfg) - conf = util.get_base_cfg(system_config,cfg_builtin) + conf = util.get_base_cfg(self.sysconfig,cfg_builtin, parsed_cfgs) # support reading the old ConfigObj format file and merging # it into the yaml dictionary @@ -212,7 +153,11 @@ class CloudInit: def restore_from_cache(self): try: - f=open(data_source_cache, "rb") + # we try to restore from a current link and static path + # by using the instance link, if purge_cache was called + # the file wont exist + cache = get_ipath_cur('obj_pkl') + f=open(cache, "rb") data = cPickle.load(f) self.datasource = data return True @@ -220,16 +165,17 @@ class CloudInit: return False def write_to_cache(self): + cache = self.get_ipath("obj_pkl") try: - os.makedirs(os.path.dirname(data_source_cache)) + os.makedirs(os.path.dirname(cache)) except OSError as e: if e.errno != errno.EEXIST: return False try: - f=open(data_source_cache, "wb") + f=open(cache, "wb") data = cPickle.dump(self.datasource,f) - os.chmod(data_source_cache,0400) + os.chmod(cache,0400) return True except: return False @@ -252,6 +198,7 @@ class CloudInit: for ds in cfglist.split(','): dslist.append(strip(ds).tolower()) + log.debug("searching for data source in [%s]" % str(dslist)) for ds in dslist: if ds not in self.datasource_map: log.warn("data source %s not found in map" % ds) @@ -270,27 +217,48 @@ class CloudInit: log.debug("did not find data source from %s" % dslist) raise DataSourceNotFoundException("Could not find data source") + def set_cur_instance(self): + try: + os.unlink(cur_instance_link) + except OSError, e: + if e.errno != errno.ENOENT: raise + + os.symlink("./instances/%s" % self.get_instance_id(), cur_instance_link) + idir = self.get_ipath() + dlist = [] + for d in [ "handlers", "scripts", "sem" ]: + dlist.append("%s/%s" % (idir, d)) + + util.ensure_dirs(dlist) + def get_userdata(self): return(self.datasource.get_userdata()) + def get_userdata_raw(self): + return(self.datasource.get_userdata_raw()) + + def get_instance_id(self): + return(self.datasource.get_instance_id()) + def update_cache(self): self.write_to_cache() self.store_userdata() def store_userdata(self): - util.write_file(userdata_raw, self.datasource.get_userdata_raw(), 0600) - util.write_file(userdata, self.datasource.get_userdata(), 0600) + util.write_file(self.get_ipath('userdata_raw'), + self.datasource.get_userdata_raw(), 0600) + util.write_file(self.get_ipath('userdata'), + self.datasource.get_userdata(), 0600) def initctl_emit(self): + cc_path = get_ipath_cur('cloud_config') subprocess.Popen(['initctl', 'emit', 'cloud-config', - '%s=%s' % (cfg_env_name,cloud_config)]).communicate() + '%s=%s' % (cfg_env_name,cc_path)]).communicate() def sem_getpath(self,name,freq): - freqtok = freq if freq == 'once-per-instance': - freqtok = self.datasource.get_instance_id() - - return("%s/%s.%s" % (semdir,name,freqtok)) + return("%s/%s" % (self.get_ipath("sem"),name)) + return("%s/%s.%s" % (get_cpath("sem"), name, freq)) def sem_has_run(self,name,freq): if freq == "always": return False @@ -349,9 +317,40 @@ class CloudInit: self.sem_clear(semname,freq) raise + # get_ipath : get the instance path for a name in pathmap + # (/var/lib/cloud/instances/<instance>/name)<name>) + def get_ipath(self, name=None): + return("%s/instances/%s%s" + % (varlibdir,self.get_instance_id(), pathmap[name])) + def consume_userdata(self): self.get_userdata() data = self + + cdir = get_cpath("handlers") + idir = self.get_ipath("handlers") + + # add the path to the plugins dir to the top of our list for import + # instance dir should be read before cloud-dir + sys.path.insert(0,cdir) + sys.path.insert(0,idir) + + # add handlers in cdir + for fname in glob.glob("%s/*.py" % cdir): + if not os.path.isfile(fname): continue + modname = os.path.basename(fname)[0:-3] + try: + mod = __import__(modname) + lister = getattr(mod, "list_types") + handler = getattr(mod, "handle_part") + mtypes = lister() + for mtype in mtypes: + self.part_handlers[mtype]=handler + log.debug("added handler for [%s] from %s" % (mtypes,fname)) + except: + log.warn("failed to initialize handler in %s" % fname) + util.logexc(log) + # give callbacks opportunity to initialize for ctype, func in self.part_handlers.items(): func(data, "__begin__",None,None) @@ -368,16 +367,13 @@ class CloudInit: self.handlercount = 0 return - # add the path to the plugins dir to the top of our list for import - if self.handlercount == 0: - sys.path.insert(0,pluginsdir) - self.handlercount=self.handlercount+1 - # write content to pluginsdir + # write content to instance's handlerdir + handlerdir = self.get_ipath("handler") modname = 'part-handler-%03d' % self.handlercount modfname = modname + ".py" - util.write_file("%s/%s" % (pluginsdir,modfname), payload, 0600) + util.write_file("%s/%s" % (handlerdir,modfname), payload, 0600) try: mod = __import__(modname) @@ -402,7 +398,9 @@ class CloudInit: return filename=filename.replace(os.sep,'_') - util.write_file("%s/%s" % (user_scripts_dir,filename), payload, 0700) + scriptsdir = get_ipath_cur('scripts') + util.write_file("%s/%s/%s" % + (scriptsdir,self.get_instance_id(),filename), payload, 0700) def handle_upstart_job(self,data,ctype,filename,payload): if ctype == "__end__" or ctype == "__begin__": return @@ -416,6 +414,7 @@ class CloudInit: self.cloud_config_str="" return if ctype == "__end__": + cloud_config = self.get_ipath("cloud_config") util.write_file(cloud_config, self.cloud_config_str, 0600) ## this could merge the cloud config with the system config @@ -452,6 +451,7 @@ class CloudInit: elif start != 0: payload=payload[start:] + boothooks_dir = self.get_ipath("boothooks") filepath = "%s/%s" % (boothooks_dir,filename) util.write_file(filepath, payload, 0700) try: @@ -480,15 +480,58 @@ class CloudInit: def device_name_to_device(self,name): return(self.datasource.device_name_to_device(name)) + # I really don't know if this should be here or not, but + # I needed it in cc_update_hostname, where that code had a valid 'cloud' + # reference, but did not have a cloudinit handle + # (ie, no cloudinit.get_cpath()) + def get_cpath(self,name=None): + return(get_cpath(name)) + + +def initfs(): + subds = [ 'scripts/per-instance', 'scripts/per-once', 'scripts/per-boot', + 'seed', 'instances', 'handlers', 'sem', 'data' ] + dlist = [ ] + for subd in subds: + dlist.append("%s/%s" % (varlibdir, subd)) + util.ensure_dirs(dlist) + + cfg = util.get_base_cfg(system_config,cfg_builtin,parsed_cfgs) + log_file = None + if 'def_log_file' in cfg: + log_file = cfg['def_log_file'] + fp = open(log_file,"ab") + fp.close() + if log_file and 'syslog' in cfg: + perms = cfg['syslog'] + (u,g) = perms.split(':',1) + if u == "-1" or u == "None": u = None + if g == "-1" or g == "None": g = None + util.chownbyname(log_file, u, g) def purge_cache(): - try: - os.unlink(data_source_cache) - except OSError as e: - if e.errno != errno.ENOENT: return(False) - except: - return(False) + rmlist = ( boot_finished , cur_instance_link ) + for f in rmlist: + try: + os.unlink(f) + except OSError as e: + if e.errno == errno.ENOENT: continue + return(False) + except: + return(False) return(True) +# get_ipath_cur: get the current instance path for an item +def get_ipath_cur(name=None): + return("%s/instance/%s" % (varlibdir, pathmap[name])) + +# get_cpath : get the "clouddir" (/var/lib/cloud/<name>) +# for a name in dirmap +def get_cpath(name=None): + return("%s%s" % (varlibdir, pathmap[name])) + +def get_base_cfg(): + return(util.get_base_cfg(system_config,cfg_builtin,parsed_cfgs)) + class DataSourceNotFoundException(Exception): pass |