| # -*- 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 |