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.
Files changed (4) hide show
  1. data/LICENSE.md +13 -0
  2. data/README.md +37 -0
  3. data/bin/servicebuilder +328 -0
  4. 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
+
@@ -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
+