
The DataSources that are loaded are now controlled entirely via configuration file of 'datasource_list', like: datasource_list: [ "NoCloud", "OVF", "Ec2" ] Each item in that list is a "DataSourceCollection". for each item in the list, cloudinit will attempt to load: cloudinit.DataSource<item> and, failing that, DataSource<item> The module is required to have a method named 'get_datasource_list' in it that takes a single list of "dependencies" and returns a list of python classes inside the collection that can run needing only those dependencies. The dependencies are defines in DataSource.py. Currently: DEP_FILESYSTEM = "FILESYSTEM" DEP_NETWORK = "NETWORK" When 'get_datasource_list' is called for the DataSourceOVF module with [DEP_FILESYSTEM], then DataSourceOVF returns a single item list with a reference to the 'DataSourceOVF' class. When 'get_datasource_list' is called for the DataSourceOVF module with [DEP_FILESYSTEM, DEP_NETWORK], it will return a single item list with a reference to 'DataSourceOVFNet'. cloudinit will then instanciate the class and call its 'get_data' method. if the get_data method returns 'True', then it selects this class as the selected Datasource.
307 lines
9.0 KiB
Python
307 lines
9.0 KiB
Python
# 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 DataSource
|
|
|
|
from cloudinit import seeddir
|
|
import cloudinit.util as util
|
|
import sys
|
|
import os.path
|
|
import os
|
|
import errno
|
|
from xml.dom import minidom
|
|
from xml.dom import Node
|
|
import base64
|
|
import re
|
|
import tempfile
|
|
import subprocess
|
|
|
|
class DataSourceOVF(DataSource.DataSource):
|
|
seed = None
|
|
seeddir = seeddir + '/ovf'
|
|
environment = None
|
|
cfg = { }
|
|
userdata_raw = None
|
|
metadata = None
|
|
supported_seed_starts = ( "/" , "file://" )
|
|
|
|
def __str__(self):
|
|
mstr="DataSourceOVF"
|
|
mstr = mstr + " [seed=%s]" % self.seed
|
|
return(mstr)
|
|
|
|
def get_data(self):
|
|
found = [ ]
|
|
md = { }
|
|
ud = ""
|
|
|
|
defaults = {
|
|
"local-hostname" : "ubuntuhost",
|
|
"instance-id" : "nocloud"
|
|
}
|
|
|
|
(seedfile, contents) = get_ovf_env(seeddir)
|
|
if seedfile:
|
|
# found a seed dir
|
|
seed = "%s/%s" % (seeddir,seedfile)
|
|
(md, ud, cfg) = read_ovf_environment(contents)
|
|
self.environment = contents
|
|
|
|
found.append(seed)
|
|
else:
|
|
np = { 'iso' : transport_iso9660,
|
|
'vmware-guestd' : transport_vmware_guestd, }
|
|
for name, transfunc in np.iteritems():
|
|
(contents, dev, fname) = transfunc()
|
|
if contents: break
|
|
|
|
if contents:
|
|
(md, ud, cfg) = read_ovf_environment(contents)
|
|
self.environment = contents
|
|
found.append(name)
|
|
|
|
# There was no OVF transports found
|
|
if len(found) == 0:
|
|
return False
|
|
|
|
if 'seedfrom' in md:
|
|
seedfrom = md['seedfrom']
|
|
seedfound = False
|
|
for proto in self.supported_seed_starts:
|
|
if seedfrom.startswith(proto):
|
|
seedfound = proto
|
|
break
|
|
if not seedfound:
|
|
self.log.debug("seed from %s not supported by %s" %
|
|
(seedfrom, self.__class__))
|
|
return False
|
|
|
|
(md_seed,ud) = util.read_seeded(seedfrom)
|
|
self.log.debug("using seeded cache data from %s" % seedfrom)
|
|
|
|
md = util.mergedict(md,md_seed)
|
|
found.append(seedfrom)
|
|
|
|
|
|
md = util.mergedict(md,defaults)
|
|
self.seed = ",".join(found)
|
|
self.metadata = md;
|
|
self.userdata_raw = ud
|
|
self.cfg = cfg
|
|
return True
|
|
|
|
def get_public_ssh_keys(self):
|
|
if not 'public-keys' in self.metadata: return([])
|
|
return([self.metadata['public-keys'],])
|
|
|
|
def get_hostname(self):
|
|
return(self.metadata['local-hostname'])
|
|
|
|
# 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(self.cfg)
|
|
|
|
class DataSourceOVFNet(DataSourceOVF):
|
|
seeddir = seeddir + '/ovf-net'
|
|
supported_seed_starts = ( "http://", "https://", "ftp://" )
|
|
|
|
# this will return a dict with some content
|
|
# meta-data, user-data
|
|
def read_ovf_environment(contents):
|
|
props = getProperties(contents)
|
|
md = { }
|
|
cfg = { }
|
|
ud = ""
|
|
cfg_props = [ 'password', ]
|
|
md_props = [ 'seedfrom', 'local-hostname', 'public-keys', 'instance-id' ]
|
|
for prop, val in props.iteritems():
|
|
if prop == 'hostname': prop = "local-hostname"
|
|
if prop in md_props:
|
|
md[prop] = val
|
|
elif prop in cfg_props:
|
|
cfg[prop] = val
|
|
elif prop == "user-data":
|
|
try:
|
|
ud = base64.decodestring(val)
|
|
except:
|
|
ud = val
|
|
return(md, ud, cfg)
|
|
|
|
|
|
# returns tuple of filename (in 'dirname', and the contents of the file)
|
|
# on "not found", returns 'None' for filename and False for contents
|
|
def get_ovf_env(dirname):
|
|
env_names = ("ovf-env.xml", "ovf_env.xml", "OVF_ENV.XML", "OVF-ENV.XML" )
|
|
for fname in env_names:
|
|
if os.path.isfile("%s/%s" % (dirname,fname)):
|
|
fp = open("%s/%s" % (dirname,fname))
|
|
contents = fp.read()
|
|
fp.close()
|
|
return(fname,contents)
|
|
return(None,False)
|
|
|
|
# transport functions take no input and return
|
|
# a 3 tuple of content, path, filename
|
|
def transport_iso9660(require_iso=False):
|
|
|
|
# default_regex matches values in
|
|
# /lib/udev/rules.d/60-cdrom_id.rules
|
|
# KERNEL!="sr[0-9]*|hd[a-z]|xvd*", GOTO="cdrom_end"
|
|
envname = "CLOUD_INIT_CDROM_DEV_REGEX"
|
|
default_regex = "^(sr[0-9]+|hd[a-z]|xvd.*)"
|
|
|
|
devname_regex = os.environ.get(envname,default_regex)
|
|
cdmatch = re.compile(devname_regex)
|
|
|
|
# go through mounts to see if it was already mounted
|
|
fp = open("/proc/mounts")
|
|
mounts = fp.readlines()
|
|
fp.close()
|
|
|
|
mounted = { }
|
|
for mpline in mounts:
|
|
(dev,mp,fstype,opts,freq,passno) = mpline.split()
|
|
mounted[dev]=(dev,fstype,mp,False)
|
|
mp = mp.replace("\\040"," ")
|
|
if fstype != "iso9660" and require_iso: continue
|
|
|
|
if cdmatch.match(dev[5:]) == None: # take off '/dev/'
|
|
continue
|
|
|
|
(fname,contents) = get_ovf_env(mp)
|
|
if contents is not False:
|
|
return(contents,dev,fname)
|
|
|
|
tmpd = None
|
|
dvnull = None
|
|
|
|
devs = os.listdir("/dev/")
|
|
devs.sort()
|
|
|
|
for dev in devs:
|
|
fullp = "/dev/%s" % dev
|
|
|
|
if fullp in mounted or not cdmatch.match(dev) or os.path.isdir(fullp):
|
|
continue
|
|
|
|
if tmpd is None:
|
|
tmpd = tempfile.mkdtemp()
|
|
if dvnull is None:
|
|
try:
|
|
dvnull = open("/dev/null")
|
|
except:
|
|
pass
|
|
|
|
cmd = [ "mount", "-o", "ro", fullp, tmpd ]
|
|
if require_iso: cmd.extend(('-t','iso9660'))
|
|
|
|
rc = subprocess.call(cmd, stderr=dvnull, stdout=dvnull, stdin=dvnull)
|
|
if rc:
|
|
continue
|
|
|
|
(fname,contents) = get_ovf_env(tmpd)
|
|
|
|
subprocess.call(["umount", tmpd])
|
|
|
|
if contents is not False:
|
|
os.rmdir(tmpd)
|
|
return(contents,fullp,fname)
|
|
|
|
if tmpd:
|
|
os.rmdir(tmpd)
|
|
|
|
if dvnull:
|
|
dvnull.close()
|
|
|
|
return(False, None, None)
|
|
|
|
def transport_vmware_guestd():
|
|
# http://blogs.vmware.com/vapp/2009/07/selfconfiguration-and-the-ovf-environment.html
|
|
# try:
|
|
# cmd = ['vmware-guestd', '--cmd', 'info-get guestinfo.ovfEnv']
|
|
# (out,err) = subp(cmd)
|
|
# return(out, 'guestinfo.ovfEnv', 'vmware-guestd')
|
|
# except:
|
|
# # would need to error check here and see why this failed
|
|
# # to know if log/error should be raised
|
|
# return(False, None, None)
|
|
return(False, None, None)
|
|
|
|
|
|
def findChild(node,filter_func):
|
|
ret = []
|
|
if not node.hasChildNodes(): return ret
|
|
for child in node.childNodes:
|
|
if filter_func(child): ret.append(child)
|
|
return(ret)
|
|
|
|
def getProperties(environString):
|
|
dom = minidom.parseString(environString)
|
|
if dom.documentElement.localName != "Environment":
|
|
raise Exception("No Environment Node")
|
|
|
|
if not dom.documentElement.hasChildNodes():
|
|
raise Exception("No Child Nodes")
|
|
|
|
envNsURI = "http://schemas.dmtf.org/ovf/environment/1"
|
|
|
|
# could also check here that elem.namespaceURI ==
|
|
# "http://schemas.dmtf.org/ovf/environment/1"
|
|
propSections = findChild(dom.documentElement,
|
|
lambda n: n.localName == "PropertySection")
|
|
|
|
if len(propSections) == 0:
|
|
raise Exception("No 'PropertySection's")
|
|
|
|
props = { }
|
|
propElems = findChild(propSections[0], lambda n: n.localName == "Property")
|
|
|
|
for elem in propElems:
|
|
key = elem.attributes.getNamedItemNS(envNsURI,"key").value
|
|
val = elem.attributes.getNamedItemNS(envNsURI,"value").value
|
|
props[key] = val
|
|
|
|
return(props)
|
|
|
|
datasources = (
|
|
( DataSourceOVF, ( DataSource.DEP_FILESYSTEM, ) ),
|
|
( DataSourceOVFNet,
|
|
( DataSource.DEP_FILESYSTEM, DataSource.DEP_NETWORK ) ),
|
|
)
|
|
|
|
# return a list of data sources that match this set of dependencies
|
|
def get_datasource_list(depends):
|
|
return(DataSource.list_from_depends(depends, datasources))
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
envStr = open(sys.argv[1]).read()
|
|
props = getProperties(envStr)
|
|
import pprint
|
|
pprint.pprint(props)
|
|
|
|
md, ud, cfg = read_ovf_environment(envStr)
|
|
print "=== md ==="
|
|
pprint.pprint(md)
|
|
print "=== ud ==="
|
|
pprint.pprint(ud)
|
|
print "=== cfg ==="
|
|
pprint.pprint(cfg)
|