
This makes the user able to pass in multi-line input to the public-key flag, and it will be handled correctly (just as if it came from the authorized_keys file)
232 lines
6.8 KiB
Python
232 lines
6.8 KiB
Python
# Copyright (C) 2012 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 cloudinit.DataSource as DataSource
|
|
|
|
from cloudinit import seeddir as base_seeddir
|
|
from cloudinit import log
|
|
import cloudinit.util as util
|
|
import os.path
|
|
import os
|
|
import json
|
|
import subprocess
|
|
|
|
DEFAULT_IID = "iid-dsconfigdrive"
|
|
|
|
|
|
class DataSourceConfigDrive(DataSource.DataSource):
|
|
seed = None
|
|
seeddir = base_seeddir + '/config_drive'
|
|
cfg = {}
|
|
userdata_raw = None
|
|
metadata = None
|
|
dsmode = "local"
|
|
|
|
def __str__(self):
|
|
mstr = "DataSourceConfigDrive[%s]" % self.dsmode
|
|
mstr = mstr + " [seed=%s]" % self.seed
|
|
return(mstr)
|
|
|
|
def get_data(self):
|
|
found = None
|
|
md = {}
|
|
ud = ""
|
|
|
|
defaults = {"instance-id": DEFAULT_IID, "dsmode": "pass"}
|
|
|
|
if os.path.isdir(self.seeddir):
|
|
try:
|
|
(md, ud) = read_config_drive_dir(self.seeddir)
|
|
found = self.seeddir
|
|
except nonConfigDriveDir:
|
|
pass
|
|
|
|
if not found:
|
|
dev = cfg_drive_device()
|
|
if dev:
|
|
try:
|
|
(md, ud) = util.mount_callback_umount(dev,
|
|
read_config_drive_dir)
|
|
found = dev
|
|
except (nonConfigDriveDir, util.mountFailedError):
|
|
pass
|
|
|
|
if not found:
|
|
return False
|
|
|
|
if 'dsconfig' in md:
|
|
self.cfg = md['dscfg']
|
|
|
|
md = util.mergedict(md, defaults)
|
|
|
|
# update interfaces and ifup only on the local datasource
|
|
# this way the DataSourceConfigDriveNet doesn't do it also.
|
|
if 'network-interfaces' in md and self.dsmode == "local":
|
|
if md['dsmode'] == "pass":
|
|
log.info("updating network interfaces from configdrive")
|
|
else:
|
|
log.debug("updating network interfaces from configdrive")
|
|
|
|
util.write_file("/etc/network/interfaces",
|
|
md['network-interfaces'])
|
|
try:
|
|
(out, err) = util.subp(['ifup', '--all'])
|
|
if len(out) or len(err):
|
|
log.warn("ifup --all had stderr: %s" % err)
|
|
|
|
except subprocess.CalledProcessError as exc:
|
|
log.warn("ifup --all failed: %s" % (exc.output[1]))
|
|
|
|
self.seed = found
|
|
self.metadata = md
|
|
self.userdata_raw = ud
|
|
|
|
if md['dsmode'] == self.dsmode:
|
|
return True
|
|
|
|
log.debug("%s: not claiming datasource, dsmode=%s" %
|
|
(self, md['dsmode']))
|
|
return False
|
|
|
|
def get_public_ssh_keys(self):
|
|
if not 'public-keys' in self.metadata:
|
|
return([])
|
|
return(self.metadata['public-keys'])
|
|
|
|
# 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 DataSourceConfigDriveNet(DataSourceConfigDrive):
|
|
dsmode = "net"
|
|
|
|
|
|
class nonConfigDriveDir(Exception):
|
|
pass
|
|
|
|
|
|
def cfg_drive_device():
|
|
""" get the config drive device. return a string like '/dev/vdb'
|
|
or None (if there is no non-root device attached). This does not
|
|
check the contents, only reports that if there *were* a config_drive
|
|
attached, it would be this device.
|
|
per config_drive documentation, this is
|
|
"associated as the last available disk on the instance"
|
|
"""
|
|
|
|
if 'CLOUD_INIT_CONFIG_DRIVE_DEVICE' in os.environ:
|
|
return(os.environ['CLOUD_INIT_CONFIG_DRIVE_DEVICE'])
|
|
|
|
# we are looking for a raw block device (sda, not sda1) with a vfat
|
|
# filesystem on it.
|
|
|
|
letters = "abcdefghijklmnopqrstuvwxyz"
|
|
devs = util.find_devs_with("TYPE=vfat")
|
|
|
|
# filter out anything not ending in a letter (ignore partitions)
|
|
devs = [f for f in devs if f[-1] in letters]
|
|
|
|
# sort them in reverse so "last" device is first
|
|
devs.sort(reverse=True)
|
|
|
|
if len(devs):
|
|
return(devs[0])
|
|
|
|
return(None)
|
|
|
|
|
|
def read_config_drive_dir(source_dir):
|
|
"""
|
|
read_config_drive_dir(source_dir):
|
|
read source_dir, and return a tuple with metadata dict and user-data
|
|
string populated. If not a valid dir, raise a nonConfigDriveDir
|
|
"""
|
|
md = {}
|
|
ud = ""
|
|
|
|
flist = ("etc/network/interfaces", "root/.ssh/authorized_keys", "meta.js")
|
|
found = [f for f in flist if os.path.isfile("%s/%s" % (source_dir, f))]
|
|
keydata = ""
|
|
|
|
if len(found) == 0:
|
|
raise nonConfigDriveDir("%s: %s" % (source_dir, "no files found"))
|
|
|
|
if "etc/network/interfaces" in found:
|
|
with open("%s/%s" % (source_dir, "/etc/network/interfaces")) as fp:
|
|
md['network-interfaces'] = fp.read()
|
|
|
|
if "root/.ssh/authorized_keys" in found:
|
|
with open("%s/%s" % (source_dir, "root/.ssh/authorized_keys")) as fp:
|
|
keydata = fp.read()
|
|
|
|
meta_js = {}
|
|
|
|
if "meta.js" in found:
|
|
content = ''
|
|
with open("%s/%s" % (source_dir, "meta.js")) as fp:
|
|
content = fp.read()
|
|
md['meta_js'] = content
|
|
try:
|
|
meta_js = json.loads(content)
|
|
except ValueError:
|
|
raise nonConfigDriveDir("%s: %s" %
|
|
(source_dir, "invalid json in meta.js"))
|
|
|
|
keydata = meta_js.get('public-keys', keydata)
|
|
|
|
if keydata:
|
|
lines = keydata.splitlines()
|
|
md['public-keys'] = [l for l in lines
|
|
if len(l) and not l.startswith("#")]
|
|
|
|
for copy in ('dsmode', 'instance-id', 'dscfg'):
|
|
if copy in meta_js:
|
|
md[copy] = meta_js[copy]
|
|
|
|
if 'user-data' in meta_js:
|
|
ud = meta_js['user-data']
|
|
|
|
return(md, ud)
|
|
|
|
datasources = (
|
|
(DataSourceConfigDrive, (DataSource.DEP_FILESYSTEM, )),
|
|
(DataSourceConfigDriveNet,
|
|
(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__":
|
|
def main():
|
|
import sys
|
|
import pprint
|
|
print cfg_drive_device()
|
|
(md, ud) = read_config_drive_dir(sys.argv[1])
|
|
print "=== md ==="
|
|
pprint.pprint(md)
|
|
print "=== ud ==="
|
|
print(ud)
|
|
|
|
main()
|
|
|
|
# vi: ts=4 expandtab
|