|
- # -*- coding: utf-8 -*-
-
- import errno
- import json
- import logging
- import os
- import shutil
- import six
- import subprocess
- import tempfile
- import uuid
- import yaml
-
- LOG = logging.getLogger(__name__)
-
- class ConfigDriveBuilder(object):
- """Build config drives, optionally as a context manager."""
-
- def __init__(self, image_file):
- self.image_file = image_file
- self.mdfiles=[]
-
- def __enter__(self):
- self._delete_if_exists(self.image_file)
- return self
-
- def __exit__(self, exctype, excval, exctb):
- self.make_drive()
-
- @staticmethod
- def _ensure_tree(path):
- try:
- os.makedirs(path)
- except OSError as e:
- if e.errno == errno.EEXIST and os.path.isdir(path):
- pass
- else:
- raise
-
- @staticmethod
- def _delete_if_exists(path):
- try:
- os.unlink(path)
- except OSError as e:
- if e.errno != errno.ENOENT:
- raise
-
- 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)
- self._ensure_tree(dirname)
- with open(filepath, 'wb') as f:
- 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):
- cmd = ['mkisofs',
- '-o', path,
- '-ldots',
- '-allow-lowercase',
- '-allow-multidot',
- '-l',
- '-V', 'config-2',
- '-r',
- '-J',
- '-quiet',
- tmpdir]
- try:
- LOG.info('Running cmd (subprocess): %s', cmd)
- _pipe = subprocess.PIPE
- obj = subprocess.Popen(cmd,
- stdin=_pipe,
- stdout=_pipe,
- stderr=_pipe,
- close_fds=True)
- (stdout, stderr) = obj.communicate()
- obj.stdin.close()
- _returncode = obj.returncode
- LOG.debug('Cmd "%s" returned: %s', cmd, _returncode)
- if _returncode != 0:
- output = 'Stdout: %s\nStderr: %s' % (stdout, stderr)
- LOG.error('The command "%s" failed. %s',
- cmd, output)
- raise subprocess.CalledProcessError(cmd=cmd,
- returncode=_returncode,
- output=output)
- except OSError as err:
- LOG.error('Got an OSError in the command: "%s". Errno: %s', cmd,
- err.errno)
- raise
-
- def make_drive(self):
- """Make the config drive.
- :raises CalledProcessError 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, user_data=None,
- network_data=None):
-
- ''' 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 user_data: custom user data dictionary.
- :param network_data: custom network info dictionary.
-
- '''
- instance_md = {}
- instance_md['uuid'] = instance_id or str(uuid.uuid4())
- instance_md['hostname'] = '%s.%s' % (hostname, domainname)
- instance_md['name'] = hostname
-
- if user_data:
- user_data = '#cloud-config\n\n' + yaml.dump(user_data, default_flow_style=False)
-
- data = json.dumps(instance_md)
- 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))
-
- LOG.debug('Config drive was built %s' % dst)
|