diff options
Diffstat (limited to 'cloudinit/sources/__init__.py')
-rw-r--r-- | cloudinit/sources/__init__.py | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py new file mode 100644 index 00000000..b25724a5 --- /dev/null +++ b/cloudinit/sources/__init__.py @@ -0,0 +1,223 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2012 Canonical Ltd. +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. +# Copyright (C) 2012 Yahoo! Inc. +# +# Author: Scott Moser <scott.moser@canonical.com> +# Author: Juerg Haefliger <juerg.haefliger@hp.com> +# Author: Joshua Harlow <harlowja@yahoo-inc.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 abc + +from cloudinit import importer +from cloudinit import log as logging +from cloudinit import user_data as ud +from cloudinit import util + +DEP_FILESYSTEM = "FILESYSTEM" +DEP_NETWORK = "NETWORK" +DS_PREFIX = 'DataSource' + +LOG = logging.getLogger(__name__) + + +class DataSourceNotFoundException(Exception): + pass + + +class DataSource(object): + + __metaclass__ = abc.ABCMeta + + def __init__(self, sys_cfg, distro, paths, ud_proc=None): + self.sys_cfg = sys_cfg + self.distro = distro + self.paths = paths + self.userdata = None + self.metadata = None + self.userdata_raw = None + name = util.obj_name(self) + if name.startswith(DS_PREFIX): + name = name[len(DS_PREFIX):] + self.ds_cfg = util.get_cfg_by_path(self.sys_cfg, + ("datasource", name), {}) + if not ud_proc: + self.ud_proc = ud.UserDataProcessor(self.paths) + else: + self.ud_proc = ud_proc + + def get_userdata(self): + if self.userdata is None: + raw_data = self.get_userdata_raw() + self.userdata = self.ud_proc.process(raw_data) + return self.userdata + + def get_userdata_raw(self): + return self.userdata_raw + + # the data sources' config_obj is a cloud-config formated + # object that came to it from ways other than cloud-config + # because cloud-config content would be handled elsewhere + def get_config_obj(self): + return {} + + def get_public_ssh_keys(self): + keys = [] + + if not self.metadata or 'public-keys' not in self.metadata: + return keys + + if isinstance(self.metadata['public-keys'], (basestring, str)): + return str(self.metadata['public-keys']).splitlines() + + if isinstance(self.metadata['public-keys'], (list, set)): + return list(self.metadata['public-keys']) + + if isinstance(self.metadata['public-keys'], (dict)): + for (_keyname, klist) in self.metadata['public-keys'].iteritems(): + # lp:506332 uec metadata service responds with + # data that makes boto populate a string for 'klist' rather + # than a list. + if isinstance(klist, (str, basestring)): + klist = [klist] + if isinstance(klist, (list, set)): + for pkey in klist: + # There is an empty string at + # the end of the keylist, trim it + if pkey: + keys.append(pkey) + + return keys + + def device_name_to_device(self, _name): + # translate a 'name' to a device + # the primary function at this point is on ec2 + # to consult metadata service, that has + # ephemeral0: sdb + # and return 'sdb' for input 'ephemeral0' + return None + + def get_locale(self): + return 'en_US.UTF-8' + + def get_local_mirror(self): + # ?? + return None + + def get_instance_id(self): + if not self.metadata or 'instance-id' not in self.metadata: + # Return a magic not really instance id string + return "iid-datasource" + return str(self.metadata['instance-id']) + + def get_hostname(self, fqdn=False): + defdomain = "localdomain" + defhost = "localhost" + domain = defdomain + + if not self.metadata or not 'local-hostname' in self.metadata: + # this is somewhat questionable really. + # the cloud datasource was asked for a hostname + # and didn't have one. raising error might be more appropriate + # but instead, basically look up the existing hostname + toks = [] + hostname = util.get_hostname() + fqdn = util.get_fqdn_from_hosts(hostname) + if fqdn and fqdn.find(".") > 0: + toks = str(fqdn).split(".") + elif hostname: + toks = [hostname, defdomain] + else: + toks = [defhost, defdomain] + else: + # if there is an ipv4 address in 'local-hostname', then + # make up a hostname (LP: #475354) in format ip-xx.xx.xx.xx + lhost = self.metadata['local-hostname'] + if util.is_ipv4(lhost): + toks = "ip-%s" % lhost.replace(".", "-") + else: + toks = lhost.split(".") + + if len(toks) > 1: + hostname = toks[0] + domain = '.'.join(toks[1:]) + else: + hostname = toks[0] + + if fqdn: + return "%s.%s" % (hostname, domain) + else: + return hostname + + +def find_source(sys_cfg, distro, paths, ds_deps, cfg_list, pkg_list): + ds_list = list_sources(cfg_list, ds_deps, pkg_list) + ds_names = [util.obj_name(f) for f in ds_list] + LOG.debug("Searching for data source in: %s", ds_names) + + for cls in ds_list: + try: + LOG.debug("Seeing if we can get any data from %s", cls) + s = cls(sys_cfg, distro, paths) + if s.get_data(): + return (s, util.obj_name(cls)) + except Exception: + util.logexc(LOG, "Getting data from %s failed", cls) + + msg = ("Did not find any data source," + " searched classes: (%s)") % (", ".join(ds_names)) + raise DataSourceNotFoundException(msg) + + +# Return a list of classes that have the same depends as 'depends' +# iterate through cfg_list, loading "DataSource*" modules +# and calling their "get_datasource_list". +# Return an ordered list of classes that match (if any) +def list_sources(cfg_list, depends, pkg_list): + src_list = [] + LOG.debug(("Looking for for data source in: %s," + " via packages %s that matches dependencies %s"), + cfg_list, pkg_list, depends) + for ds_name in cfg_list: + if not ds_name.startswith(DS_PREFIX): + ds_name = '%s%s' % (DS_PREFIX, ds_name) + m_locs = importer.find_module(ds_name, + pkg_list, + ['get_datasource_list']) + for m_loc in m_locs: + mod = importer.import_module(m_loc) + lister = getattr(mod, "get_datasource_list") + matches = lister(depends) + if matches: + src_list.extend(matches) + break + return src_list + + +# 'depends' is a list of dependencies (DEP_FILESYSTEM) +# ds_list is a list of 2 item lists +# ds_list = [ +# ( class, ( depends-that-this-class-needs ) ) +# } +# It returns a list of 'class' that matched these deps exactly +# It mainly is a helper function for DataSourceCollections +def list_from_depends(depends, ds_list): + ret_list = [] + depset = set(depends) + for (cls, deps) in ds_list: + if depset == set(deps): + ret_list.append(cls) + return ret_list |