Dzmitry Stremkouski | 97927ee | 2018-08-23 23:20:38 +0200 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | |
| 3 | import json |
| 4 | import logging |
| 5 | import os |
| 6 | import shutil |
| 7 | import six |
| 8 | import tempfile |
| 9 | import yaml |
| 10 | |
| 11 | from oslo_utils import uuidutils |
| 12 | from oslo_utils import fileutils |
| 13 | from oslo_concurrency import processutils |
| 14 | |
| 15 | class ConfigDriveBuilder(object): |
| 16 | """Build config drives, optionally as a context manager.""" |
| 17 | |
| 18 | def __init__(self, image_file): |
| 19 | self.image_file = image_file |
| 20 | self.mdfiles=[] |
| 21 | |
| 22 | def __enter__(self): |
| 23 | fileutils.delete_if_exists(self.image_file) |
| 24 | return self |
| 25 | |
| 26 | def __exit__(self, exctype, excval, exctb): |
| 27 | self.make_drive() |
| 28 | |
| 29 | def add_file(self, path, data): |
| 30 | self.mdfiles.append((path, data)) |
| 31 | |
| 32 | def _add_file(self, basedir, path, data): |
| 33 | filepath = os.path.join(basedir, path) |
| 34 | dirname = os.path.dirname(filepath) |
| 35 | fileutils.ensure_tree(dirname) |
| 36 | with open(filepath, 'wb') as f: |
| 37 | if isinstance(data, six.text_type): |
| 38 | data = data.encode('utf-8') |
| 39 | f.write(data) |
| 40 | |
| 41 | def _write_md_files(self, basedir): |
| 42 | for data in self.mdfiles: |
| 43 | self._add_file(basedir, data[0], data[1]) |
| 44 | |
| 45 | def _make_iso9660(self, path, tmpdir): |
| 46 | |
| 47 | processutils.execute('mkisofs', |
| 48 | '-o', path, |
| 49 | '-ldots', |
| 50 | '-allow-lowercase', |
| 51 | '-allow-multidot', |
| 52 | '-l', |
| 53 | '-V', 'config-2', |
| 54 | '-r', |
| 55 | '-J', |
| 56 | '-quiet', |
| 57 | tmpdir, |
| 58 | attempts=1, |
| 59 | run_as_root=False) |
| 60 | |
| 61 | def make_drive(self): |
| 62 | """Make the config drive. |
| 63 | :raises ProcessExecuteError if a helper process has failed. |
| 64 | """ |
| 65 | try: |
| 66 | tmpdir = tempfile.mkdtemp() |
| 67 | self._write_md_files(tmpdir) |
| 68 | self._make_iso9660(self.image_file, tmpdir) |
| 69 | finally: |
| 70 | shutil.rmtree(tmpdir) |
| 71 | |
| 72 | |
| 73 | def generate( |
| 74 | dst, |
| 75 | hostname, |
| 76 | domainname, |
| 77 | instance_id=None, |
| 78 | user_data=None, |
| 79 | network_data=None, |
| 80 | saltconfig=None |
| 81 | ): |
| 82 | |
| 83 | ''' Generate config drive |
| 84 | |
| 85 | :param dst: destination file to place config drive. |
| 86 | :param hostname: hostname of Instance. |
| 87 | :param domainname: instance domain. |
| 88 | :param instance_id: UUID of the instance. |
| 89 | :param user_data: custom user data dictionary. type: json |
| 90 | :param network_data: custom network info dictionary. type: json |
| 91 | :param saltconfig: salt minion configuration. type: json |
| 92 | |
| 93 | ''' |
| 94 | |
| 95 | instance_md = {} |
| 96 | instance_md['uuid'] = instance_id or uuidutils.generate_uuid() |
| 97 | instance_md['hostname'] = '%s.%s' % (hostname, domainname) |
| 98 | instance_md['name'] = hostname |
| 99 | |
| 100 | if user_data: |
| 101 | user_data = '#cloud-config\n\n' + yaml.dump(yaml.load(user_data), default_flow_style=False) |
| 102 | if saltconfig: |
| 103 | user_data += yaml.dump(yaml.load(str(saltconfig)), default_flow_style=False) |
| 104 | |
| 105 | data = json.dumps(instance_md) |
| 106 | |
| 107 | with ConfigDriveBuilder(dst) as cfgdrive: |
| 108 | cfgdrive.add_file('openstack/latest/meta_data.json', data) |
| 109 | if user_data: |
| 110 | cfgdrive.add_file('openstack/latest/user_data', user_data) |
| 111 | if network_data: |
| 112 | cfgdrive.add_file('openstack/latest/network_data.json', network_data) |
| 113 | cfgdrive.add_file('openstack/latest/vendor_data.json', '{}') |
| 114 | cfgdrive.add_file('openstack/latest/vendor_data2.json', '{}') |