servicebuilder 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE.md +13 -0
- data/README.md +37 -0
- data/bin/servicebuilder +328 -0
- metadata +83 -0
data/LICENSE.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2012 Square Inc.
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
servicebuilder
|
2
|
+
==========
|
3
|
+
|
4
|
+
servicebuilder - a tool for building runit directory structures from
|
5
|
+
a simple YAML configuration. Tested with ruby 1.8.
|
6
|
+
|
7
|
+
Usage
|
8
|
+
-----
|
9
|
+
<code>servicebuilder -c CONF_DIR -s STAGING_DIRECTORY -d INSTALL_DIRECTORY</code>
|
10
|
+
|
11
|
+
Notes
|
12
|
+
-----
|
13
|
+
|
14
|
+
The STAGING_DIRECTORY is where your service directories will be created. Runit
|
15
|
+
should *not* be monitoring this directory. To have runit notice a service, it
|
16
|
+
will be symlinked into the INSTALL_DIRECTORY. This directory should be the
|
17
|
+
directory monitored by runsvd. Runit will then begin supervising the service.
|
18
|
+
|
19
|
+
CONF_DIR should be something like /etc/servicebuilder.d/ . It must be
|
20
|
+
filled with config files that end in ".yaml".
|
21
|
+
|
22
|
+
Sample config file:
|
23
|
+
|
24
|
+
<code>
|
25
|
+
mysql:
|
26
|
+
run: mysqld -fg
|
27
|
+
sleep: 30
|
28
|
+
sshd:
|
29
|
+
run: sshd -fg
|
30
|
+
log: svlogd -tt
|
31
|
+
logsleep: 5
|
32
|
+
</code>
|
33
|
+
|
34
|
+
All "run" scripts and "log" scripts WILL BE PREPENDED WITH AN "exec"!
|
35
|
+
Your run script MUST RUN IN THE FOREGROUND or you'll create a fork bomb.
|
36
|
+
For more information about runit, see http://smarden.org/runit/ .
|
37
|
+
|
data/bin/servicebuilder
ADDED
@@ -0,0 +1,328 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# servicebuilder - a tool for building runit directory structures from
|
4
|
+
# a simple YAML configuration. Tested with ruby 1.8.
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
# servicebuilder -c CONF_DIR -s STAGING_DIRECTORY -d INSTALL_DIRECTORY
|
8
|
+
#
|
9
|
+
# The STAGING_DIRECTORY is where your service directories will be created.
|
10
|
+
# Runit should *not* be monitoring this directory. To have runit notice
|
11
|
+
# a service, it will be symlinked into the INSTALL_DIRECTORY. Runit
|
12
|
+
# will then begin supervising the service.
|
13
|
+
#
|
14
|
+
# CONF_DIR should be something like /etc/servicebuilder.d/ . It must be
|
15
|
+
# filled with config files that end in ".yaml".
|
16
|
+
# Config file:
|
17
|
+
#
|
18
|
+
# mysql:
|
19
|
+
# run: mysqld -fg
|
20
|
+
# sleep: 30
|
21
|
+
# sshd:
|
22
|
+
# run: sshd -fg
|
23
|
+
# log: svlogd -tt
|
24
|
+
# logsleep: 5
|
25
|
+
#
|
26
|
+
# All "run" scripts and "log" scripts WILL BE PREPENDED WITH AN "exec"!
|
27
|
+
# Your run script MUST RUN IN THE FOREGROUND or you'll create a fork bomb.
|
28
|
+
# For more information about runit, see http://smarden.org/runit/ .
|
29
|
+
#
|
30
|
+
|
31
|
+
require 'getoptlong'
|
32
|
+
require 'yaml'
|
33
|
+
require 'fileutils'
|
34
|
+
|
35
|
+
# A Service has a name, a runscript, and a logscript.
|
36
|
+
# Create a Service object, populate :name, :run, :log,
|
37
|
+
# :stagedir, and :activatedir. Then, s.create! to build it in the stagedir.
|
38
|
+
# s.activate! to symlink it into the activedir.
|
39
|
+
class Service
|
40
|
+
attr_accessor :name, :run, :log, :stagedir, :activatedir, :sleep, :logsleep
|
41
|
+
|
42
|
+
# Methods to wrap your runscript and logscript around something safe
|
43
|
+
def runscript
|
44
|
+
retstring = <<EOF
|
45
|
+
#!/usr/bin/ruby
|
46
|
+
$stderr.reopen(STDOUT)
|
47
|
+
require 'yaml'
|
48
|
+
sleep #{self.sleep}
|
49
|
+
exec *YAML.load(DATA.read)
|
50
|
+
sleep 2
|
51
|
+
__END__
|
52
|
+
#{self.run.to_yaml}
|
53
|
+
EOF
|
54
|
+
return retstring
|
55
|
+
end
|
56
|
+
|
57
|
+
def logscript
|
58
|
+
retstring = <<EOF
|
59
|
+
#!/usr/bin/ruby
|
60
|
+
require 'yaml'
|
61
|
+
sleep #{self.logsleep}
|
62
|
+
exec *YAML.load(DATA.read)
|
63
|
+
sleep 2
|
64
|
+
__END__
|
65
|
+
#{self.log.to_yaml}
|
66
|
+
EOF
|
67
|
+
return retstring
|
68
|
+
end
|
69
|
+
|
70
|
+
# Build the directory hierarchy in the staging directory.
|
71
|
+
def create!
|
72
|
+
modified = false
|
73
|
+
# Create directories if they need to be created
|
74
|
+
if not File.directory?(@stagedir)
|
75
|
+
puts "Creating directory #{@stagedir}"
|
76
|
+
Kernel.system("mkdir", "-p", @stagedir)
|
77
|
+
modified = true
|
78
|
+
end
|
79
|
+
if not File.directory?(File.join(@stagedir, "log"))
|
80
|
+
puts "Creating directory #{@stagedir}/log"
|
81
|
+
Kernel.system("mkdir", "-p", File.join(@stagedir, "log"))
|
82
|
+
modified = true
|
83
|
+
end
|
84
|
+
if not File.directory?(File.join(@stagedir, "log", "main"))
|
85
|
+
puts "Creating directory #{@stagedir}/log/main"
|
86
|
+
Kernel.system("mkdir", "-p", File.join(@stagedir, "log", "main"))
|
87
|
+
FileUtils.chown('nobody', 'nobody',
|
88
|
+
File.join(@stagedir, "log", "main"))
|
89
|
+
modified = true
|
90
|
+
end
|
91
|
+
|
92
|
+
# Test to see if we need to edit the run and log/run scripts
|
93
|
+
writerun = true
|
94
|
+
if File.file?(File.join(@stagedir, "run"))
|
95
|
+
File.open(File.join(@stagedir, "run")) do |f|
|
96
|
+
currun = f.read
|
97
|
+
if(currun == self.runscript)
|
98
|
+
writerun = false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
writelog = true
|
104
|
+
if File.file?(File.join(@stagedir, "log", "run"))
|
105
|
+
File.open(File.join(@stagedir, "log", "run")) do |f|
|
106
|
+
curlog = f.read
|
107
|
+
if(curlog == self.logscript)
|
108
|
+
writelog = false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Write run and log/run
|
114
|
+
if writerun
|
115
|
+
File.open(File.join(@stagedir, "run"), 'w') do |f|
|
116
|
+
puts "Writing #{@stagedir}/run"
|
117
|
+
f.write(self.runscript)
|
118
|
+
end
|
119
|
+
File.chmod(0755, File.join(@stagedir, "run"))
|
120
|
+
modified = true
|
121
|
+
end
|
122
|
+
|
123
|
+
if writelog
|
124
|
+
File.open(File.join(@stagedir, "log", "run"), 'w') do |f|
|
125
|
+
puts "Writing #{@stagedir}/log/run"
|
126
|
+
f.write(self.logscript)
|
127
|
+
end
|
128
|
+
File.chmod(0755, File.join(@stagedir, "log", "run"))
|
129
|
+
modified = true
|
130
|
+
end
|
131
|
+
|
132
|
+
# return if we had to modify the directories
|
133
|
+
return modified
|
134
|
+
end
|
135
|
+
|
136
|
+
# Symlink the staging directory into the active directory.
|
137
|
+
def activate!
|
138
|
+
if File.exists?(@activatedir)
|
139
|
+
if not File.symlink?(@activatedir)
|
140
|
+
puts "#{@activatedir} is not a symlink, unsure how to continue"
|
141
|
+
exit(1)
|
142
|
+
end
|
143
|
+
if not File.readlink(@activatedir) == @stagedir
|
144
|
+
puts "#{@activatedir} is not a symlink to #{@stagedir}, " +
|
145
|
+
"unsure how to continue"
|
146
|
+
exit(1)
|
147
|
+
end
|
148
|
+
else
|
149
|
+
puts "Symlinking #{@stagedir} to #{@activatedir}"
|
150
|
+
File.symlink(@stagedir, @activatedir)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def usage
|
156
|
+
puts "Usage: $0 --conf CONFIG_DIRECTORY -s STAGING_DIRECTORY "
|
157
|
+
puts " -d INSTALL_DIRECTORY"
|
158
|
+
exit(1)
|
159
|
+
end
|
160
|
+
|
161
|
+
opts = GetoptLong.new(['--confdir', '-c', GetoptLong::REQUIRED_ARGUMENT],
|
162
|
+
['--stagedir', '-s', GetoptLong::REQUIRED_ARGUMENT],
|
163
|
+
['--activatedir', '-d', GetoptLong::REQUIRED_ARGUMENT],
|
164
|
+
['--help', '-h', GetoptLong::NO_ARGUMENT])
|
165
|
+
|
166
|
+
confdir = '/etc/servicebuilder.d'
|
167
|
+
stagedir = '/var/service-stage'
|
168
|
+
activatedir = '/var/service'
|
169
|
+
|
170
|
+
opts.each do |opt, arg|
|
171
|
+
case opt
|
172
|
+
when '--help'
|
173
|
+
usage()
|
174
|
+
when '--stagedir'
|
175
|
+
stagedir = File.expand_path(arg)
|
176
|
+
when '--activatedir'
|
177
|
+
activatedir = File.expand_path(arg)
|
178
|
+
when '--confdir'
|
179
|
+
confdir = arg
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# require these options
|
184
|
+
[confdir, stagedir, activatedir].each do |v|
|
185
|
+
usage() unless v
|
186
|
+
end
|
187
|
+
|
188
|
+
haveservices = {}
|
189
|
+
wantservices = {}
|
190
|
+
|
191
|
+
# read the config files, build the list of services that we want to exist
|
192
|
+
confdata = {}
|
193
|
+
raise "#{confdir} does not exist!" unless File.directory?(confdir)
|
194
|
+
Dir.open(confdir) do |dir|
|
195
|
+
dir.each do |file|
|
196
|
+
next if file =~ /^\./
|
197
|
+
next unless file =~ /\.yaml$/
|
198
|
+
configfile = File.join(confdir, file)
|
199
|
+
next unless File.file?(configfile)
|
200
|
+
config = File.read(configfile)
|
201
|
+
data = YAML.load(config)
|
202
|
+
raise "#{configfile} is empty!" if data.empty?
|
203
|
+
data.each do |k, v|
|
204
|
+
if confdata.has_key?(k)
|
205
|
+
raise "Service #{k} defined twice"
|
206
|
+
end
|
207
|
+
confdata[k] = v
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
confdata.each do |k, d|
|
213
|
+
# build a Service object for each key in the configuration
|
214
|
+
servicename = k
|
215
|
+
raise "service #{k} has no run script" unless d.has_key?('run')
|
216
|
+
|
217
|
+
run = d['run']
|
218
|
+
if run.class != Array
|
219
|
+
raise "Error, bad args passed to run statement"
|
220
|
+
end
|
221
|
+
sleep = 2
|
222
|
+
if d.has_key?('sleep') and d['sleep'].to_i >= 0
|
223
|
+
sleep = d['sleep'].to_i
|
224
|
+
end
|
225
|
+
logsleep = 2
|
226
|
+
if d.has_key?('logsleep') and d['logsleep'].to_i >= 0
|
227
|
+
logsleep = d['logsleep'].to_i
|
228
|
+
end
|
229
|
+
if d.member?('log')
|
230
|
+
log = d['log']
|
231
|
+
if log.class != Array
|
232
|
+
raise "Error, bad args passed to log statement"
|
233
|
+
end
|
234
|
+
else
|
235
|
+
log = %w{chpst -unobody svlogd -tt ./main}
|
236
|
+
end
|
237
|
+
|
238
|
+
s = Service.new
|
239
|
+
s.name = servicename
|
240
|
+
s.run = run
|
241
|
+
s.log = log
|
242
|
+
s.sleep = sleep
|
243
|
+
s.logsleep = logsleep
|
244
|
+
s.stagedir = File.join(stagedir, servicename)
|
245
|
+
s.activatedir = File.join(activatedir, servicename)
|
246
|
+
|
247
|
+
# mark that yes, we do want this service to persist
|
248
|
+
wantservices[servicename] = s
|
249
|
+
end
|
250
|
+
|
251
|
+
# make sure /var/service exists
|
252
|
+
unless File.directory?(activatedir)
|
253
|
+
if File.exists?(activatedir)
|
254
|
+
raise "#{activatedir} exists but is not a directory, bailing"
|
255
|
+
end
|
256
|
+
FileUtils.mkdir(activatedir)
|
257
|
+
end
|
258
|
+
|
259
|
+
# activate them by symlinking the staged service into the activate directory
|
260
|
+
wantservices.values.each do |s|
|
261
|
+
puts "Found #{s.name}"
|
262
|
+
modified = s.create!
|
263
|
+
# Restart the service if we've modified it
|
264
|
+
if modified
|
265
|
+
puts "Running sv t #{stagedir}/#{s.name}"
|
266
|
+
system("sv t #{stagedir}/#{s.name}")
|
267
|
+
puts "Warning: Unable to restart service #{s.name}" if $?
|
268
|
+
end
|
269
|
+
s.activate!
|
270
|
+
end
|
271
|
+
|
272
|
+
# remove all services that we do not want
|
273
|
+
found_staging = []
|
274
|
+
found_activate = []
|
275
|
+
|
276
|
+
# find every service found in the staging directory
|
277
|
+
Dir.open(stagedir) do |dir|
|
278
|
+
dir.each do |file|
|
279
|
+
next if file =~ /^\./
|
280
|
+
next unless File.directory?(File.join(stagedir, file))
|
281
|
+
found_staging.push(File.join(stagedir, file))
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# find every service found in the activate directory
|
286
|
+
Dir.open(activatedir) do |dir|
|
287
|
+
dir.each do |file|
|
288
|
+
next if file =~ /^\./
|
289
|
+
next unless File.directory?(File.join(activatedir, file))
|
290
|
+
found_activate.push(File.join(activatedir, file))
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# for each activated service, rm it if we don't want it anymore
|
295
|
+
found_activate.each do |dir|
|
296
|
+
still_wanted = false
|
297
|
+
wantservices.each do |k, v|
|
298
|
+
if v.activatedir == dir
|
299
|
+
still_wanted = true
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
if still_wanted == false
|
304
|
+
puts "Unlinking #{dir} from activated location"
|
305
|
+
File.unlink(dir)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# for each staged service, rm if we don't want it anymore
|
310
|
+
# we send sv exit only now so runit doesn't automatically restart it
|
311
|
+
# when it notices runsv not running in the activated directory
|
312
|
+
found_staging.each do |dir|
|
313
|
+
still_wanted = false
|
314
|
+
wantservices.each do |k, v|
|
315
|
+
if v.stagedir == dir
|
316
|
+
still_wanted = true
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
if still_wanted == false
|
321
|
+
puts "Stopping service #{dir}"
|
322
|
+
system("sv stop #{dir}")
|
323
|
+
puts "Warning: Unable to stop service #{dir}" if $?
|
324
|
+
|
325
|
+
puts "Removing service #{dir}"
|
326
|
+
FileUtils.rm_r(dir)
|
327
|
+
end
|
328
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: servicebuilder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Erik Bourget
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-05-23 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rdoc
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description: Tool to build runit services from simple configuration files.
|
35
|
+
email:
|
36
|
+
- github@squareup.com
|
37
|
+
executables:
|
38
|
+
- servicebuilder
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE.md
|
43
|
+
files:
|
44
|
+
- bin/servicebuilder
|
45
|
+
- README.md
|
46
|
+
- LICENSE.md
|
47
|
+
homepage: http://github.com/square/prodeng
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --charset=UTF-8
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 23
|
70
|
+
segments:
|
71
|
+
- 1
|
72
|
+
- 3
|
73
|
+
- 6
|
74
|
+
version: 1.3.6
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.8.24
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: Tool to build runit services from simple configuration files.
|
82
|
+
test_files: []
|
83
|
+
|