/
fab_bootstrap.py
387 lines (318 loc) · 10.8 KB
/
fab_bootstrap.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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
#!/usr/bin/env python26
# Python libs
import sys
from socket import gethostbyname as _gethostbyname, \
gethostname as _gethostname
# Fabric libs
from fabric.api import *
from fabric.contrib import files
# TODO(raul): find a better way so we don't need to hardcode
HOST_TO_ADD = {
'salt': '10.188.49.191',
'salt2': '10.188.49.190',
}
# Packages needed!
SALT_PKGS = (
'libzmq3-3.2.2-13.1',
'libffi-3.0.5-1.el5',
'libyaml-0.1.2-6.el5',
'gmp-4.1.4-10.el5',
'sshpass-1.05-1.el5',
'yum-utils-1.1.16-21.el5',
'python26-libs-2.6.8-2.el5',
'python26-2.6.8-2.el5',
'python26-markupsafe-0.11-3.el5',
'python26-crypto-2.3-5.el5',
'python26-msgpack-0.1.12-2.el5',
'python26-zmq-13.1.0-1',
'python26-m2crypto-0.21.1-5.el5',
'python26-PyYAML-3.08-4.el5',
'python26-babel-0.9.5-2.el5',
'python26-jinja2-2.5.5-4.el5',
'salt-2014.1.0-1.el5',
'salt-minion-2014.1.0-1.el5',
)
class _WrongFormatError(Exception):
pass
class _NeedSudoError(Exception):
pass
class _CmdFailedError(Exception):
pass
# TODO(raul) move this away from this file
def _get_repo_ip():
'''
Get the IP of the this host.
'''
return _gethostbyname(_gethostname())
def _yum(subcmd, pkgs=None, force=True, nogpgcheck=True, use_sudo=True):
'''
This run a `yum` command such as yum -y `subcmd` pkgs
'''
if not use_sudo:
raise _NeedSudoError("You need to run this as sudo!")
data = {}
if force:
data['force'] = '-y'
if nogpgcheck:
data['nogpgcheck'] = '--nogpgcheck'
if pkgs:
if type(pkgs) == type(str()):
data['pkgs'] = pkgs
elif type(pkgs) == type(list()) or type(pkgs) == type(tuple()):
data['pkgs'] = ' '.join(pkgs)
else:
raise _WrongFormatError('pkgs variable has a unsupported format.')
else:
if subcmd in ['upgrade', 'update']:
pkgs = ''
else:
raise _WrongFormatError('pkgs variable has a unsupported format.')
data['subcmd'] = subcmd
return sudo('yum {force} {nogpgcheck} {subcmd} {pkgs}'.format(**data))
def _is_minion_installed():
out = sudo('rpm -qa| grep salt-minion')
if out.succeeded:
return True
else:
return False
def _is_sudo():
'''
Checks if we have sudo permissions in the target machine
Check is performed by sudo -v
'''
with settings(warn_only='true'):
out = run('sudo -v')
if out.succeeded:
return True
else:
return False
def _install_repo(reponame, repotype='yum', use_sudo=True, update_cache=True, force=True):
'''
Install a repo by copying the file from local to remote.
When using update_cache=True, you *must* use use_sudo=True. This is
also the default behaviour.
TODO(raul): add other systems if needed (debian, ubuntu, etc)
force option?
'''
repopath = {
'yum' : '/etc/yum.repos.d',
# TODO(raul) add more here!
}.get(repotype)
out = put(reponame, '{0}/{1}'.format(repopath, reponame), use_sudo=use_sudo)
if out.succeeded and update_cache:
out = sudo('yum -y --nogpgcheck makecache')
return out.succeeded if out.succeeded else False
elif out.succeeded:
return True
else:
return False
def _add_minion_id(minion_id, minion_id_file='/etc/salt/minion_id', use_sudo=True):
'''
Helper function to add minion_id to /etc/salt/minion_id
'''
if not use_sudo:
raise _NeedSudoError("You need to run this as sudo!")
files.append(minion_id_file, minion_id, use_sudo=use_sudo)
def _put_minion_debug_mode(minion_cnf_file='/etc/salt/minion', log_level='log_level: trace', use_sudo=True):
'''
Helper function to put minion in debug mode
'''
if not use_sudo:
raise _NeedSudoError("You need to run this as sudo!")
files.append(minion_cnf_file, log_level, use_sudo=use_sudo)
def _remove_salt_dirs(use_sudo=True):
'''
Helper function to delete /etc/salt directory
'''
if not use_sudo:
raise _NeedSudoError("You need to run this as sudo!")
for d in ['/etc/salt', '/var/run/salt*', '/var/cache/salt']:
out = sudo('rm -rf {0}'.format(d))
if out.failed:
_CmdFailedError()
def _check_repo_dict(repos):
'''
This assumes that dictionary is like below:
d = {
'r1': 'repofile.repo',
'r2': 'anotherfile.repo',
...
...
'rN': 'N_file_with_repo_extension.repo'
}
For now, the only check is to make sure all files end with *.repo
and then return this as a list.
'''
for k,v in repos.items():
if v[-5:] != '.repo':
raise _CmdFailedError("{0} repo seems to be invalid".format(v))
return repos.values()
def add_etc_host_entry(host, ip, hosts_file='/etc/hosts', use_sudo=True):
'''
Helper function to add an entry in /etc/hosts
'''
files.append(hosts_file, '{0} {1}'.format(ip, host), use_sudo=use_sudo)
def remove_etc_host_entry(host, ip, hosts_file='/etc/hosts', use_sudo=True):
'''
Helper function to remove an entry in /etc/hosts
'''
files.sed('/etc/hosts', '^{0}.*{1}'.format(ip, host), '', use_sudo=True)
def remove_minion_pki_from_master():
'''
Helper function to remove the pki from minion in the master
This assumes the master is running in the same machine than fabric!
Process:
1.- Get current minion_id from remote end
2.- Remove key from *local* master....remember master must be running
in this machine
'''
minion_id = sudo("cat /etc/salt/minion_id")
local('rm -rf /etc/salt/pki/master/minions/{0}*'.format(str(minion_id)))
@task
def install_minion(minion_id, type="rhel5_x86_64", **repos):
'''
Install and configure target minion
1.- Setup /etc/hosts
2.- Setup and update yum repo
3.- Install salt minion
4.- Assigns ID to minion (minimal setup)
5.- Restart minion
6.- Done
# TODO(raul): This just installs in a redhat-family server.
# No Ubuntu, Suse, etc support yet.
'''
if len(repos) == 0:
raise _CmdFailedError("You need to add the repos to be installed in the target host.")
else:
repos = _check_repo_dict(repos)
with settings(warn_only='true'):
# Let's check the type of server
if type[:4] == 'rhel':
add_sudo_and_sshkey()
if _is_minion_installed():
print "minion is already installed"
sys.exit(0)
run_yum = False
# Note(raul): If one entry fails to be added then program will exit,
# so no need to take care in much detail.
for host, ip in HOST_TO_ADD.items():
add_etc_host_entry(host, ip, use_sudo=True)
# Use list() just in case more repos are needed in the future
for repo in repos:
if _install_repo(repo):
run_yum = True
else:
print "An error happened when installing the repo file(s): {0}".format(repo)
sys.exit(-1)
if run_yum:
out = _yum('install', SALT_PKGS)
if out.succeeded:
# Some post-install config
_add_minion_id(minion_id)
_put_minion_debug_mode()
restart_minion()
@task
def uninstall_minion(type="rhel5_x86_64"):
'''
Stops and uninstall salt minion on target host
'''
with settings(warn_only='true'):
# Let's check the type of server
if type[:4] == 'rhel':
run_yum = True
if run_yum:
stop_minion()
out = _yum('erase', SALT_PKGS)
for host, ip in HOST_TO_ADD.items():
remove_etc_host_entry(host, ip, use_sudo=True)
remove_minion_pki_from_master()
# TODO(raul): decide to delete or not this
# Remove salt dir
#_remove_salt_dirs()
@task
def stop_minion():
'''
Stop salt minion on target host
'''
sudo("service salt-minion stop")
@task
def restart_minion():
'''
Restart salt minion on target host
'''
sudo("service salt-minion restart")
@task
def check_minion_status():
sudo("service salt-minion status")
@task
def tail_minion_log(lines=30):
sudo("tail -n{0} /var/log/salt/minion".format(lines))
@task
@task
def set_minion_onboot():
'''
Set minion to start when machine boots
'''
sudo("chkconfig salt-minion on")
@task
def set_minion_iptables_rules():
'''
Add minimal iptables rules to allow salt to work.
# TODO(raul): what if iptables is not installed..
# should we consider some better error handling?
'''
cmd = '''if [[ $(grep -E "A.*INPUT.*dports.*4505.*4506.*ACCEPT" /etc/sysconfig/iptables) ]]; then
echo "Iptables rules already there."
else
iptables -A INPUT -p tcp -m multiport --dports 4505,4506 -j ACCEPT && service iptables save
fi
'''
sudo(cmd)
@task
def add_sudo():
'''
Add ``sudo`` permissions to target host
# TODO(raul): Better error handling
'''
# Note(raul): ``sudo`` string looks ugly but it has to be that way
sudo = '''
admin ALL=(ALL) NOPASSWD: ALL
Defaults:admin !requiretty
'''
sudoer_file = '/etc/sudoers.d/salt_bootstrap'
tmp_file = '/tmp/.sudoer_file'
run('cat > {1} << EOF{0}EOF'.format(sudo, tmp_file))
# FIXME(raul): root account is "disabled" by setting password to expire...hence use sudo :)
run('sudo su -c "chmod 0440 {0} && chown root.root {0} && mv {0} {1}"'.format(tmp_file, sudoer_file))
@task
def add_sshkey():
'''
Basic method to set ssh keys using fabric
'''
keyfile = "/tmp/%s.pub" % env.user
ssh_auth_keys = "~/.ssh/authorized_keys"
run("mkdir -p ~/.ssh && chmod 700 ~/.ssh")
out = put('~/.ssh/id_rsa.pub', keyfile)
if out.succeeded:
# TODO(raul): Replace this with contrib.file API?
run("if [[ $(grep \"$(cat {0})\" {2}) ]]; then echo {1}; else cat {0} >> {2}; fi".format(
keyfile,
"SSH key already there",
ssh_auth_keys))
run("chmod 600 ~/.ssh/authorized_keys")
run("rm %s" % keyfile)
@task
def add_sudo_and_sshkey():
'''
Helper function to check sudo rights and if not found
add them. It also adds the ssh keys, no check performed.
# TODO(raul): check for ssh keys?
'''
# FIXME(raul): this check doesnt work!
#if not _is_sudo():
add_sudo()
add_sshkey()
if __name__ == '__main__':
# FIXME(raul): improve usage and info here
print "Usage:"
print "fab -H 10.188.49.28 -f fab_bootstrap.py -u admin check_minion_status"