-
Notifications
You must be signed in to change notification settings - Fork 70
/
solo.py
378 lines (344 loc) · 15 KB
/
solo.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
#Copyright 2010-2014 Miquel Torres <tobami@gmail.com>
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
#
"""Chef Solo deployment"""
import os
import re
from fabric.api import *
from fabric import colors
from fabric.contrib.files import append, exists, upload_template
from fabric.utils import abort
from littlechef import cookbook_paths
from littlechef import LOGFILE
# Path to local patch
BASEDIR = os.path.abspath(os.path.dirname(__file__).replace('\\', '/'))
def install(distro_type, distro, gems, version, stop_client, method):
"""Calls the appropriate installation function for the given distro"""
if distro_type == "debian":
if gems == "yes":
_gem_apt_install()
elif method == "omnibus":
_omnibus_install(version=version)
else:
chef_versions = ["0.9", "0.10"]
if version not in chef_versions:
abort('Wrong Chef version specified. Valid versions are {0}'.format(
", ".join(chef_versions)))
_apt_install(distro, version, stop_client)
elif distro_type == "rpm":
if gems == "yes":
_gem_rpm_install()
elif method == "omnibus":
_omnibus_install(version=version)
else:
_rpm_install()
elif distro_type == "gentoo":
_emerge_install()
elif distro_type == "pacman":
_gem_pacman_install()
elif distro_type == "freebsd":
_gem_ports_install()
else:
abort('wrong distro type: {0}'.format(distro_type))
def configure(current_node=None):
"""Deploy chef-solo specific files"""
current_node = current_node or {}
# Ensure that the /tmp/chef-solo/cache directory exist
cache_dir = "{0}/cache".format(env.node_work_path)
# First remote call, could go wrong
try:
cache_exists = exists(cache_dir)
except EOFError as e:
abort("Could not login to node, got: {0}".format(e))
if not cache_exists:
with settings(hide('running', 'stdout'), warn_only=True):
output = sudo('mkdir -p {0}'.format(cache_dir))
if output.failed:
error = "Could not create {0} dir. ".format(env.node_work_path)
error += "Do you have sudo rights?"
abort(error)
# Change ownership of /tmp/chef-solo/ so that we can rsync
with hide('running', 'stdout'):
with settings(warn_only=True):
output = sudo(
'chown -R {0} {1}'.format(env.user, env.node_work_path))
if output.failed:
error = "Could not modify {0} dir. ".format(env.node_work_path)
error += "Do you have sudo rights?"
abort(error)
# Set up chef solo configuration
logging_path = os.path.dirname(LOGFILE)
if not exists(logging_path):
sudo('mkdir -p {0}'.format(logging_path))
if not exists('/etc/chef'):
sudo('mkdir -p /etc/chef')
# Set parameters and upload solo.rb template
reversed_cookbook_paths = cookbook_paths[:]
reversed_cookbook_paths.reverse()
cookbook_paths_list = '[{0}]'.format(', '.join(
['"{0}/{1}"'.format(env.node_work_path, x) \
for x in reversed_cookbook_paths]))
data = {
'node_work_path': env.node_work_path,
'cookbook_paths_list': cookbook_paths_list,
'environment': current_node.get('chef_environment', '_default'),
'verbose': "true" if env.verbose else "false"
}
with settings(hide('everything')):
try:
upload_template(os.path.join(BASEDIR, 'solo.rb'), '/etc/chef/',
context=data, use_sudo=True, backup=False,
mode=0400)
except SystemExit:
error = ("Failed to upload '/etc/chef/solo.rb'\nThis "
"can happen when the deployment user does not have a "
"home directory, which is needed as a temporary location")
abort(error)
with hide('stdout'):
sudo('chown root:$(id -g -n root) {0}'.format('/etc/chef/solo.rb'))
def check_distro():
"""Check that the given distro is supported and return the distro type"""
def print_supported_distros(platform):
supported_distros = (
"Currently supported distros are:"
" Debian, Ubuntu, RHEL (CentOS, RHEL, SL),"
" Gentoo, Arch Linux or FreeBSD")
print supported_distros
abort("Unsupported distro '{0}'".format(platform))
with settings(hide('warnings', 'running', 'stdout', 'stderr'),
warn_only=True):
# use /bin/sh to determine our OS. FreeBSD doesn't have /bin/bash
original_shell = env.shell
env.shell = "/bin/sh -c"
os_implementation = run('uname -o')
if 'Linux' in os_implementation:
env.shell = original_shell
output = sudo('cat /etc/issue')
if 'Debian GNU/Linux 5.0' in output:
distro = "lenny"
distro_type = "debian"
platform = "debian"
elif 'Debian GNU/Linux 6.0' in output:
distro = "squeeze"
distro_type = "debian"
platform = "debian"
elif 'Debian GNU/Linux 7' in output:
distro = "wheezy"
distro_type = "debian"
platform = "debian"
elif 'Ubuntu' in output:
distro = sudo('lsb_release -cs')
distro_type = "debian"
platform = "ubuntu"
elif 'CentOS' in output:
distro = "CentOS"
distro_type = "rpm"
platform = "centos"
elif 'Red Hat Enterprise Linux' in output:
distro = "Red Hat"
distro_type = "rpm"
platform = "redhat"
elif 'Scientific Linux' in output:
distro = "Scientific Linux"
distro_type = "rpm"
platform = "scientific"
elif 'This is \\n.\\O (\\s \\m \\r) \\t' in output:
distro = "Gentoo"
distro_type = "gentoo"
platform = "gentoo"
elif 'Arch Linux \\r (\\n) (\\l)' in output:
distro = "Arch Linux"
distro_type = "pacman"
platform = "arch"
else:
print_supported_distros(output)
elif 'FreeBSD' in os_implementation:
env.shell = "/bin/sh -c"
distro = "FreeBSD"
distro_type = "freebsd"
platform = "freebsd"
else:
print_supported_distros(os_implementation)
return distro_type, distro, platform
def _gem_install():
"""Install Chef from gems"""
# Install RubyGems from Source
rubygems_version = "1.8.10"
ruby_version = "'~> 10.0'"
run('wget http://production.cf.rubygems.org/rubygems/rubygems-{0}.tgz'
.format(rubygems_version))
run('tar zxf rubygems-{0}.tgz'.format(rubygems_version))
with cd('rubygems-{0}'.format(rubygems_version)):
sudo('ruby setup.rb --no-format-executable'.format(rubygems_version))
sudo('rm -rf rubygems-{0} rubygems-{0}.tgz'.format(rubygems_version))
sudo('gem install --no-rdoc --no-ri chef -v {0}'.format(ruby_version))
def _gem_apt_install():
"""Install Chef from gems for apt based distros"""
with hide('stdout', 'running'):
sudo('apt-get update')
prefix = "DEBIAN_FRONTEND=noninteractive"
packages = "ruby ruby-dev libopenssl-ruby irb build-essential wget"
packages += " ssl-cert rsync"
sudo('{0} apt-get --yes install {1}'.format(prefix, packages))
_gem_install()
def _gem_rpm_install():
"""Install Chef from gems for rpm based distros"""
_add_rpm_repos()
needed_packages = "make ruby ruby-shadow gcc gcc-c++ ruby-devel wget rsync"
with show('running'):
sudo('yum -y install {0}'.format(needed_packages))
_gem_install()
def _gem_pacman_install():
"""Install Chef from gems for pacman based distros"""
with hide('stdout', 'running'):
sudo('pacman -Syu --noconfirm')
with show('running'):
sudo('pacman -S --noconfirm ruby base-devel wget rsync')
sudo('gem install --no-rdoc --no-ri chef')
def _gem_ports_install():
"""Install Chef from gems for FreeBSD"""
with hide('stdout', 'running'):
sudo('grep -q RUBY_VER /etc/make.conf || echo \'RUBY_VER=1.9\' >> /etc/make.conf')
sudo('grep -q RUBY_DEFAULT_VER /etc/make.conf || echo \'RUBY_DEFAULT_VER=1.9\' >> /etc/make.conf')
with show('running'):
sudo('which -s rsync || pkg_add -r rsync')
sudo('which -s perl || pkg_add -r perl')
sudo('which -s m4 || pkg_add -r m4')
sudo('which -s chef || (cd /usr/ports/sysutils/rubygem-chef && make -DBATCH install)')
def _omnibus_install(version):
"""Install Chef using the omnibus installer"""
url = "https://www.opscode.com/chef/install.sh"
with hide('stdout', 'running'):
local("""python -c "import urllib; print urllib.urlopen('{0}').read()" > /tmp/install.sh""".format(url))
put('/tmp/install.sh', '/tmp/install.sh')
print("Downloading and installing Chef {0}...".format(version))
with hide('stdout'):
sudo("""bash /tmp/install.sh -v {0}""".format(version))
def _apt_install(distro, version, stop_client='yes'):
"""Install Chef for debian based distros"""
with settings(hide('stdout', 'running')):
with settings(hide('warnings'), warn_only=True):
wget_is_installed = sudo('which wget')
if wget_is_installed.failed:
# Install wget
print "Installing wget..."
# we may not be able to install wget without updating first
sudo('apt-get update')
output = sudo('apt-get --yes install wget')
if output.failed:
print(colors.red("Error while installing wget:"))
abort(output.lstrip("\\n"))
rsync_is_installed = sudo('which rsync')
if rsync_is_installed.failed:
# Install rsync
print "Installing rsync..."
# we may not be able to install rsync without updating first
sudo('apt-get update')
output = sudo('apt-get --yes install rsync')
if output.failed:
print(colors.red("Error while installing rsync:"))
abort(output.lstrip("\\n"))
# Add Opscode Debian repo
print("Setting up Opscode repository...")
if version == "0.9":
version = ""
else:
version = "-" + version
append('opscode.list',
'deb http://apt.opscode.com/ {0}{1} main'.format(distro, version),
use_sudo=True)
sudo('mv opscode.list /etc/apt/sources.list.d/')
# Add repository GPG key
gpg_key = "http://apt.opscode.com/packages@opscode.com.gpg.key"
sudo('wget -qO - {0} | sudo apt-key add -'.format(gpg_key))
# Load package list from new repository
with settings(hide('warnings'), warn_only=True):
output = sudo('apt-get update')
if output.failed:
print(colors.red(
"Error while executing 'apt-get install chef':"))
abort(output)
# Install Chef Solo
print("Installing Chef Solo")
# Ensure we don't get asked for the Chef Server
command = "echo chef chef/chef_server_url select ''"
command += " | debconf-set-selections"
sudo(command)
# Install package
with settings(hide('warnings'), warn_only=True):
output = sudo('apt-get --yes install ucf chef')
if output.failed:
print(colors.red(
"Error while executing 'apt-get install chef':"))
abort(output)
if stop_client == 'yes':
# We only want chef-solo, stop chef-client and remove it from init
sudo('update-rc.d -f chef-client remove')
with settings(hide('warnings'), warn_only=True):
# The logrotate entry will force restart of chef-client
sudo('rm /etc/logrotate.d/chef')
with settings(hide('warnings'), warn_only=True):
output = sudo('service chef-client stop')
if output.failed:
# Probably an older distro without the newer "service" command
sudo('/etc/init.d/chef-client stop')
def _add_rpm_repos():
"""Add RPM repositories for Chef
Opscode doesn't officially support an ELFF resporitory any longer:
http://wiki.opscode.com/display/chef/Installation+on+RHEL+and+CentOS+5+with
+RPMs
Using http://rbel.frameos.org/
"""
version_string = sudo('cat /etc/redhat-release')
try:
rhel_version = re.findall("\d[\d.]*", version_string)[0].split('.')[0]
except IndexError:
print "Warning: could not correctly detect the Red Hat version"
print "Defaulting to 5 packages"
rhel_version = "5"
epel_release = "epel-release-5-4.noarch"
if rhel_version == "6":
epel_release = "epel-release-6-8.noarch"
with show('running'):
# Install the EPEL Yum Repository.
with settings(hide('warnings', 'running'), warn_only=True):
repo_url = "http://dl.fedoraproject.org"
repo_path = "/pub/epel/{0}/i386/".format(rhel_version)
repo_path += "{0}.rpm".format(epel_release)
output = sudo('rpm -Uvh {0}{1}'.format(repo_url, repo_path))
installed = "package {0} is already installed".format(epel_release)
if output.failed and installed not in output:
abort(output)
# Install the FrameOS RBEL Yum Repository.
with settings(hide('warnings', 'running'), warn_only=True):
repo_url = "http://rbel.co"
repo_path = "/rbel{0}".format(rhel_version)
output = sudo('rpm -Uvh {0}{1}'.format(repo_url, repo_path))
installed = "package rbel{0}-release-1.0-2.el{0}".format(
rhel_version)
installed += ".noarch is already installed"
if output.failed and installed not in output:
abort(output)
def _rpm_install():
"""Install Chef for rpm based distros"""
_add_rpm_repos()
with show('running'):
# Ensure we have an up-to-date ruby, as we need >=1.8.7
sudo('yum -y upgrade ruby*')
# Install Chef
sudo('yum -y install rubygem-chef')
def _emerge_install():
"""Install Chef for Gentoo"""
with show('running'):
sudo("USE='-test' ACCEPT_KEYWORDS='~amd64' emerge -u chef")