blob: 0a45b0243b1c9d4571b00400f0a91f9548cc2830 [file] [log] [blame]
Vasyl Saienkoaad112d2017-06-19 16:45:37 +03001# -*- coding: utf-8 -*-
2
3
4import gzip
5import json
6import logging
7import os
8import StringIO
9import shutil
10import six
11import tempfile
12import yaml
13
14
15HAS_LIBS = False
16try:
17 from oslo_utils import uuidutils
18 from oslo_utils import fileutils
19 from oslo_concurrency import processutils
20 from oslo_serialization import base64
21 HAS_LIBS = True
22except ImportError:
23 pass
24
25LOG = logging.getLogger(__name__)
26
27
28def __virtual__():
29 '''
30 Only load this module if mkisofs is installed on this minion.
31 '''
32 if not HAS_LIBS:
33 return False
34
35 for path in os.environ["PATH"].split(os.pathsep):
36 if os.access(os.path.join(path, 'mkisofs'), os.X_OK):
37 return True
38
39 return False
40
41class ConfigDriveBuilder(object):
42 """Build config drives, optionally as a context manager."""
43
44 def __init__(self, image_file):
45 self.image_file = image_file
46 self.mdfiles=[] # List with (path, data)
47
48 def __enter__(self):
49 fileutils.delete_if_exists(self.image_file)
50 return self
51
52 def __exit__(self, exctype, excval, exctb):
53 self.make_drive()
54
55 def add_file(self, path, data):
56 self.mdfiles.append((path, data))
57
58 def _add_file(self, basedir, path, data):
59 filepath = os.path.join(basedir, path)
60 dirname = os.path.dirname(filepath)
61 fileutils.ensure_tree(dirname)
62 with open(filepath, 'wb') as f:
63 # the given data can be either text or bytes. we can only write
64 # bytes into files.
65 if isinstance(data, six.text_type):
66 data = data.encode('utf-8')
67 f.write(data)
68
69 def _write_md_files(self, basedir):
70 for data in self.mdfiles:
71 self._add_file(basedir, data[0], data[1])
72
73 def _make_iso9660(self, path, tmpdir):
74
75 processutils.execute('mkisofs',
76 '-o', path,
77 '-ldots',
78 '-allow-lowercase',
79 '-allow-multidot',
80 '-l',
81 '-V', 'config-2',
82 '-r',
83 '-J',
84 '-quiet',
85 tmpdir,
86 attempts=1,
87 run_as_root=False)
88
89 def make_drive(self):
90 """Make the config drive.
91 :raises ProcessExecuteError if a helper process has failed.
92 """
93 try:
94 tmpdir = tempfile.mkdtemp()
95 self._write_md_files(tmpdir)
96 self._make_iso9660(self.image_file, tmpdir)
97 finally:
98 shutil.rmtree(tmpdir)
99
100
101def generate(dst, hostname, domainname, instance_id=None, public_keys=None,
102 user_data=None, network_data=None, ironic_format=False):
103 ''' Generate config drive
104
105 :param dst: destination file to place config drive.
106 :param hostname: hostname of Instance.
107 :param domainname: instance domain.
108 :param instance_id: UUID of the instance.
109 :param public_keys: dict of public keys.
110 :param user_data: custom user data dictionary.
111 :param network_data: custom network info dictionary.
112 :param ironic_format: create base64 of gzipped ISO format
113
114 CLI Example:
115 .. code-block:: bash
116 salt '*' configdrive.generate dst=/tmp/my_cfgdrive.iso hostname=host1
117 '''
118 instance_md = {}
119 public_keys = public_keys or {}
120
121 instance_md['uuid'] = instance_id or uuidutils.generate_uuid()
122 instance_md['hostname'] = '%s.%s' % (hostname, domainname)
123 instance_md['name'] = hostname
124 instance_md['public_keys'] = public_keys
125
126 data = json.dumps(instance_md)
127
128 if user_data:
129 user_data = '#cloud-config\n\n' + yaml.dump(user_data, default_flow_style=False)
130
131 LOG.debug('Generating config drive for %s' % hostname)
132
133 with ConfigDriveBuilder(dst) as cfgdrive:
134 cfgdrive.add_file('openstack/latest/meta_data.json', data)
135 if user_data:
136 cfgdrive.add_file('openstack/latest/user_data', user_data)
137 if network_data:
138 cfgdrive.add_file('openstack/latest/network_data.json',
139 json.dumps(network_data))
140 cfgdrive.add_file('openstack/latest/vendor_data.json', '{}')
141 cfgdrive.add_file('openstack/latest/vendor_data2.json', '{}')
142
143 b64_gzip = None
144 if ironic_format:
145 with open(dst) as f:
146 with tempfile.NamedTemporaryFile() as tmpzipfile:
147 g = gzip.GzipFile(fileobj=tmpzipfile, mode='wb')
148 shutil.copyfileobj(f, g)
149 g.close()
150 tmpzipfile.seek(0)
151 b64_gzip = base64.encode_as_bytes(tmpzipfile.read())
152 with open(dst, 'w') as f:
153 f.write(b64_gzip)
154
155 LOG.debug('Config drive was built %s' % dst)
156 res = {}
157 res['meta-data'] = data
158 if user_data:
159 res['user-data'] = user_data
160 if b64_gzip:
161 res['base64_gzip'] = b64_gzip
162 return res