blob: 0a45b0243b1c9d4571b00400f0a91f9548cc2830 [file] [log] [blame]
# -*- coding: utf-8 -*-
import gzip
import json
import logging
import os
import StringIO
import shutil
import six
import tempfile
import yaml
HAS_LIBS = False
try:
from oslo_utils import uuidutils
from oslo_utils import fileutils
from oslo_concurrency import processutils
from oslo_serialization import base64
HAS_LIBS = True
except ImportError:
pass
LOG = logging.getLogger(__name__)
def __virtual__():
'''
Only load this module if mkisofs is installed on this minion.
'''
if not HAS_LIBS:
return False
for path in os.environ["PATH"].split(os.pathsep):
if os.access(os.path.join(path, 'mkisofs'), os.X_OK):
return True
return False
class ConfigDriveBuilder(object):
"""Build config drives, optionally as a context manager."""
def __init__(self, image_file):
self.image_file = image_file
self.mdfiles=[] # List with (path, data)
def __enter__(self):
fileutils.delete_if_exists(self.image_file)
return self
def __exit__(self, exctype, excval, exctb):
self.make_drive()
def add_file(self, path, data):
self.mdfiles.append((path, data))
def _add_file(self, basedir, path, data):
filepath = os.path.join(basedir, path)
dirname = os.path.dirname(filepath)
fileutils.ensure_tree(dirname)
with open(filepath, 'wb') as f:
# the given data can be either text or bytes. we can only write
# bytes into files.
if isinstance(data, six.text_type):
data = data.encode('utf-8')
f.write(data)
def _write_md_files(self, basedir):
for data in self.mdfiles:
self._add_file(basedir, data[0], data[1])
def _make_iso9660(self, path, tmpdir):
processutils.execute('mkisofs',
'-o', path,
'-ldots',
'-allow-lowercase',
'-allow-multidot',
'-l',
'-V', 'config-2',
'-r',
'-J',
'-quiet',
tmpdir,
attempts=1,
run_as_root=False)
def make_drive(self):
"""Make the config drive.
:raises ProcessExecuteError if a helper process has failed.
"""
try:
tmpdir = tempfile.mkdtemp()
self._write_md_files(tmpdir)
self._make_iso9660(self.image_file, tmpdir)
finally:
shutil.rmtree(tmpdir)
def generate(dst, hostname, domainname, instance_id=None, public_keys=None,
user_data=None, network_data=None, ironic_format=False):
''' Generate config drive
:param dst: destination file to place config drive.
:param hostname: hostname of Instance.
:param domainname: instance domain.
:param instance_id: UUID of the instance.
:param public_keys: dict of public keys.
:param user_data: custom user data dictionary.
:param network_data: custom network info dictionary.
:param ironic_format: create base64 of gzipped ISO format
CLI Example:
.. code-block:: bash
salt '*' configdrive.generate dst=/tmp/my_cfgdrive.iso hostname=host1
'''
instance_md = {}
public_keys = public_keys or {}
instance_md['uuid'] = instance_id or uuidutils.generate_uuid()
instance_md['hostname'] = '%s.%s' % (hostname, domainname)
instance_md['name'] = hostname
instance_md['public_keys'] = public_keys
data = json.dumps(instance_md)
if user_data:
user_data = '#cloud-config\n\n' + yaml.dump(user_data, default_flow_style=False)
LOG.debug('Generating config drive for %s' % hostname)
with ConfigDriveBuilder(dst) as cfgdrive:
cfgdrive.add_file('openstack/latest/meta_data.json', data)
if user_data:
cfgdrive.add_file('openstack/latest/user_data', user_data)
if network_data:
cfgdrive.add_file('openstack/latest/network_data.json',
json.dumps(network_data))
cfgdrive.add_file('openstack/latest/vendor_data.json', '{}')
cfgdrive.add_file('openstack/latest/vendor_data2.json', '{}')
b64_gzip = None
if ironic_format:
with open(dst) as f:
with tempfile.NamedTemporaryFile() as tmpzipfile:
g = gzip.GzipFile(fileobj=tmpzipfile, mode='wb')
shutil.copyfileobj(f, g)
g.close()
tmpzipfile.seek(0)
b64_gzip = base64.encode_as_bytes(tmpzipfile.read())
with open(dst, 'w') as f:
f.write(b64_gzip)
LOG.debug('Config drive was built %s' % dst)
res = {}
res['meta-data'] = data
if user_data:
res['user-data'] = user_data
if b64_gzip:
res['base64_gzip'] = b64_gzip
return res