-
Notifications
You must be signed in to change notification settings - Fork 5.5k
/
seed.py
300 lines (248 loc) · 8.97 KB
/
seed.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# -*- coding: utf-8 -*-
'''
Virtual machine image management tools
'''
from __future__ import absolute_import, unicode_literals, print_function
# Import python libs
import os
import shutil
import logging
import tempfile
# Import salt libs
import salt.crypt
import salt.utils.cloud
import salt.utils.files
import salt.config
import salt.syspaths
import uuid
# Set up logging
log = logging.getLogger(__name__)
# Don't shadow built-in's.
__func_alias__ = {
'apply_': 'apply'
}
def _file_or_content(file_):
if os.path.exists(file_):
with salt.utils.files.fopen(file_) as fic:
return salt.utils.stringutils.to_unicode(fic.read())
return file_
def prep_bootstrap(mpt):
'''
Update and get the random script to a random place
CLI Example:
.. code-block:: bash
salt '*' seed.prep_bootstrap /tmp
'''
# Verify that the boostrap script is downloaded
bs_ = __salt__['config.gather_bootstrap_script']()
fpd_ = os.path.join(mpt, 'tmp', "{0}".format(
uuid.uuid4()))
if not os.path.exists(fpd_):
os.makedirs(fpd_)
os.chmod(fpd_, 0o700)
fp_ = os.path.join(fpd_, os.path.basename(bs_))
# Copy script into tmp
shutil.copy(bs_, fp_)
tmppath = fpd_.replace(mpt, '')
return fp_, tmppath
def _mount(path, ftype, root=None):
mpt = None
if ftype == 'block':
mpt = tempfile.mkdtemp()
if not __salt__['mount.mount'](mpt, path):
os.rmdir(mpt)
return None
elif ftype == 'dir':
return path
elif ftype == 'file':
if 'guestfs.mount' in __salt__:
util = 'guestfs'
elif 'qemu_nbd.init' in __salt__:
util = 'qemu_nbd'
else:
return None
mpt = __salt__['mount.mount'](path, device=root, util=util)
if not mpt:
return None
return mpt
def _umount(mpt, ftype):
if ftype == 'block':
__salt__['mount.umount'](mpt)
os.rmdir(mpt)
elif ftype == 'file':
__salt__['mount.umount'](mpt, util='qemu_nbd')
def apply_(path, id_=None, config=None, approve_key=True, install=True,
prep_install=False, pub_key=None, priv_key=None, mount_point=None):
'''
Seed a location (disk image, directory, or block device) with the
minion config, approve the minion's key, and/or install salt-minion.
CLI Example:
.. code-block:: bash
salt 'minion' seed.apply path id [config=config_data] \\
[gen_key=(true|false)] [approve_key=(true|false)] \\
[install=(true|false)]
path
Full path to the directory, device, or disk image on the target
minion's file system.
id
Minion id with which to seed the path.
config
Minion configuration options. By default, the 'master' option is set to
the target host's 'master'.
approve_key
Request a pre-approval of the generated minion key. Requires
that the salt-master be configured to either auto-accept all keys or
expect a signing request from the target host. Default: true.
install
Install salt-minion, if absent. Default: true.
prep_install
Prepare the bootstrap script, but don't run it. Default: false
'''
stats = __salt__['file.stats'](path, follow_symlinks=True)
if not stats:
return '{0} does not exist'.format(path)
ftype = stats['type']
path = stats['target']
log.debug('Mounting %s at %s', ftype, path)
try:
os.makedirs(path)
except OSError:
# The directory already exists
pass
mpt = _mount(path, ftype, mount_point)
if not mpt:
return '{0} could not be mounted'.format(path)
tmp = os.path.join(mpt, 'tmp')
log.debug('Attempting to create directory %s', tmp)
try:
os.makedirs(tmp)
except OSError:
if not os.path.isdir(tmp):
raise
cfg_files = mkconfig(config, tmp=tmp, id_=id_, approve_key=approve_key,
pub_key=pub_key, priv_key=priv_key)
if _check_install(mpt):
# salt-minion is already installed, just move the config and keys
# into place
log.info('salt-minion pre-installed on image, '
'configuring as %s', id_)
minion_config = salt.config.minion_config(cfg_files['config'])
pki_dir = minion_config['pki_dir']
if not os.path.isdir(os.path.join(mpt, pki_dir.lstrip('/'))):
__salt__['file.makedirs'](
os.path.join(mpt, pki_dir.lstrip('/'), '')
)
os.rename(cfg_files['privkey'], os.path.join(
mpt, pki_dir.lstrip('/'), 'minion.pem'))
os.rename(cfg_files['pubkey'], os.path.join(
mpt, pki_dir.lstrip('/'), 'minion.pub'))
os.rename(cfg_files['config'], os.path.join(mpt, 'etc/salt/minion'))
res = True
elif install:
log.info('Attempting to install salt-minion to %s', mpt)
res = _install(mpt)
elif prep_install:
log.error('The prep_install option is no longer supported. Please use '
'the bootstrap script installed with Salt, located at %s.',
salt.syspaths.BOOTSTRAP)
res = False
else:
log.warning('No useful action performed on %s', mpt)
res = False
_umount(mpt, ftype)
return res
def mkconfig(config=None,
tmp=None,
id_=None,
approve_key=True,
pub_key=None,
priv_key=None):
'''
Generate keys and config and put them in a tmp directory.
pub_key
absolute path or file content of an optional preseeded salt key
priv_key
absolute path or file content of an optional preseeded salt key
CLI Example:
.. code-block:: bash
salt 'minion' seed.mkconfig [config=config_data] [tmp=tmp_dir] \\
[id_=minion_id] [approve_key=(true|false)]
'''
if tmp is None:
tmp = tempfile.mkdtemp()
if config is None:
config = {}
if 'master' not in config and __opts__['master'] != 'salt':
config['master'] = __opts__['master']
if id_:
config['id'] = id_
# Write the new minion's config to a tmp file
tmp_config = os.path.join(tmp, 'minion')
with salt.utils.files.fopen(tmp_config, 'w+') as fp_:
fp_.write(salt.utils.cloud.salt_config_to_yaml(config))
# Generate keys for the minion
pubkeyfn = os.path.join(tmp, 'minion.pub')
privkeyfn = os.path.join(tmp, 'minion.pem')
preseeded = pub_key and priv_key
if preseeded:
log.debug('Writing minion.pub to %s', pubkeyfn)
log.debug('Writing minion.pem to %s', privkeyfn)
with salt.utils.files.fopen(pubkeyfn, 'w') as fic:
fic.write(salt.utils.stringutils.to_str(_file_or_content(pub_key)))
with salt.utils.files.fopen(privkeyfn, 'w') as fic:
fic.write(salt.utils.stringutils.to_str(_file_or_content(priv_key)))
os.chmod(pubkeyfn, 0o600)
os.chmod(privkeyfn, 0o600)
else:
salt.crypt.gen_keys(tmp, 'minion', 2048)
if approve_key and not preseeded:
with salt.utils.files.fopen(pubkeyfn) as fp_:
pubkey = salt.utils.stringutils.to_unicode(fp_.read())
__salt__['pillar.ext']({'virtkey': [id_, pubkey]})
return {'config': tmp_config, 'pubkey': pubkeyfn, 'privkey': privkeyfn}
def _install(mpt):
'''
Determine whether salt-minion is installed and, if not,
install it.
Return True if install is successful or already installed.
'''
_check_resolv(mpt)
boot_, tmppath = (prep_bootstrap(mpt)
or salt.syspaths.BOOTSTRAP)
# Exec the chroot command
cmd = 'if type salt-minion; then exit 0; '
cmd += 'else sh {0} -c /tmp; fi'.format(os.path.join(tmppath, 'bootstrap-salt.sh'))
return not __salt__['cmd.run_chroot'](mpt, cmd, python_shell=True)['retcode']
def _check_resolv(mpt):
'''
Check that the resolv.conf is present and populated
'''
resolv = os.path.join(mpt, 'etc/resolv.conf')
replace = False
if os.path.islink(resolv):
resolv = os.path.realpath(resolv)
if not os.path.isdir(os.path.dirname(resolv)):
os.makedirs(os.path.dirname(resolv))
if not os.path.isfile(resolv):
replace = True
if not replace:
with salt.utils.files.fopen(resolv, 'rb') as fp_:
conts = salt.utils.stringutils.to_unicode(fp_.read())
if 'nameserver' not in conts:
replace = True
if 'nameserver 127.0.0.1' in conts:
replace = True
if replace:
shutil.copy('/etc/resolv.conf', resolv)
def _check_install(root):
sh_ = '/bin/sh'
if os.path.isfile(os.path.join(root, 'bin/bash')):
sh_ = '/bin/bash'
cmd = ('if ! type salt-minion; then exit 1; fi')
cmd = 'chroot \'{0}\' {1} -c \'{2}\''.format(
root,
sh_,
cmd)
return not __salt__['cmd.retcode'](cmd,
output_loglevel='quiet',
python_shell=True)