sunshine 1.0.0.pre

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 (71) hide show
  1. data/History.txt +237 -0
  2. data/Manifest.txt +70 -0
  3. data/README.txt +277 -0
  4. data/Rakefile +46 -0
  5. data/bin/sunshine +5 -0
  6. data/examples/deploy.rb +61 -0
  7. data/examples/deploy_tasks.rake +112 -0
  8. data/examples/standalone_deploy.rb +31 -0
  9. data/lib/commands/add.rb +96 -0
  10. data/lib/commands/default.rb +169 -0
  11. data/lib/commands/list.rb +322 -0
  12. data/lib/commands/restart.rb +62 -0
  13. data/lib/commands/rm.rb +83 -0
  14. data/lib/commands/run.rb +151 -0
  15. data/lib/commands/start.rb +72 -0
  16. data/lib/commands/stop.rb +61 -0
  17. data/lib/sunshine/app.rb +876 -0
  18. data/lib/sunshine/binder.rb +70 -0
  19. data/lib/sunshine/crontab.rb +143 -0
  20. data/lib/sunshine/daemon.rb +380 -0
  21. data/lib/sunshine/daemons/ar_sendmail.rb +28 -0
  22. data/lib/sunshine/daemons/delayed_job.rb +30 -0
  23. data/lib/sunshine/daemons/nginx.rb +104 -0
  24. data/lib/sunshine/daemons/rainbows.rb +35 -0
  25. data/lib/sunshine/daemons/server.rb +66 -0
  26. data/lib/sunshine/daemons/unicorn.rb +26 -0
  27. data/lib/sunshine/dependencies.rb +103 -0
  28. data/lib/sunshine/dependency_lib.rb +200 -0
  29. data/lib/sunshine/exceptions.rb +54 -0
  30. data/lib/sunshine/healthcheck.rb +83 -0
  31. data/lib/sunshine/output.rb +131 -0
  32. data/lib/sunshine/package_managers/apt.rb +48 -0
  33. data/lib/sunshine/package_managers/dependency.rb +349 -0
  34. data/lib/sunshine/package_managers/gem.rb +54 -0
  35. data/lib/sunshine/package_managers/yum.rb +62 -0
  36. data/lib/sunshine/remote_shell.rb +241 -0
  37. data/lib/sunshine/repo.rb +128 -0
  38. data/lib/sunshine/repos/git_repo.rb +122 -0
  39. data/lib/sunshine/repos/rsync_repo.rb +29 -0
  40. data/lib/sunshine/repos/svn_repo.rb +78 -0
  41. data/lib/sunshine/server_app.rb +554 -0
  42. data/lib/sunshine/shell.rb +384 -0
  43. data/lib/sunshine.rb +391 -0
  44. data/templates/logrotate/logrotate.conf.erb +11 -0
  45. data/templates/nginx/nginx.conf.erb +109 -0
  46. data/templates/nginx/nginx_optimize.conf +23 -0
  47. data/templates/nginx/nginx_proxy.conf +13 -0
  48. data/templates/rainbows/rainbows.conf.erb +18 -0
  49. data/templates/tasks/sunshine.rake +114 -0
  50. data/templates/unicorn/unicorn.conf.erb +6 -0
  51. data/test/fixtures/app_configs/test_app.yml +11 -0
  52. data/test/fixtures/sunshine_test/test_upload +0 -0
  53. data/test/mocks/mock_object.rb +179 -0
  54. data/test/mocks/mock_open4.rb +117 -0
  55. data/test/test_helper.rb +188 -0
  56. data/test/unit/test_app.rb +489 -0
  57. data/test/unit/test_binder.rb +20 -0
  58. data/test/unit/test_crontab.rb +128 -0
  59. data/test/unit/test_git_repo.rb +26 -0
  60. data/test/unit/test_healthcheck.rb +70 -0
  61. data/test/unit/test_nginx.rb +107 -0
  62. data/test/unit/test_rainbows.rb +26 -0
  63. data/test/unit/test_remote_shell.rb +102 -0
  64. data/test/unit/test_repo.rb +42 -0
  65. data/test/unit/test_server.rb +324 -0
  66. data/test/unit/test_server_app.rb +425 -0
  67. data/test/unit/test_shell.rb +97 -0
  68. data/test/unit/test_sunshine.rb +157 -0
  69. data/test/unit/test_svn_repo.rb +55 -0
  70. data/test/unit/test_unicorn.rb +22 -0
  71. metadata +217 -0
@@ -0,0 +1,131 @@
1
+ module Sunshine
2
+
3
+ ##
4
+ # The Output class handles all of the logging to the shell
5
+ # during the Sunshine runtime.
6
+
7
+ class Output
8
+
9
+ COLORS = {
10
+ Logger::UNKNOWN => :red,
11
+ Logger::FATAL => :red,
12
+ Logger::ERROR => :red,
13
+ Logger::WARN => :yellow,
14
+ Logger::INFO => :default,
15
+ Logger::DEBUG => :cyan,
16
+ }
17
+
18
+ attr_accessor :logger, :indent, :level
19
+
20
+ def initialize(options={})
21
+ @logger = Logger.new options[:output] || $stdout
22
+ @logger.formatter = lambda{|sev, time, progname, msg| msg}
23
+ @level = @logger.level = options[:level] || Logger::DEBUG
24
+ @indent = 0
25
+ end
26
+
27
+ ##
28
+ # Prints messages according to the standard output format.
29
+ # Options supported:
30
+ # :level: level of the log message
31
+ # :indent: indentation of the message
32
+ def print(title, message, options={})
33
+ severity = options[:level] || Logger::DEBUG
34
+ color = COLORS[severity]
35
+
36
+ options[:indent] = 0 if options[:indent] < 0
37
+ indent = " " * (options[:indent].to_i * 2)
38
+
39
+ print_string = message.split("\n")
40
+ print_string.map!{|m| "#{indent}[#{title}] #{m.chomp}"}
41
+ print_string = "#{print_string.join("\n")} \n"
42
+ print_string = print_string.foreground(color)
43
+ print_string = print_string.bright if indent.empty?
44
+
45
+ @logger.add(severity, print_string)
46
+ end
47
+
48
+ ##
49
+ # Generic log message which handles log indentation (for clarity).
50
+ # Log indentation if achieved done by passing a block:
51
+ #
52
+ # output.log("MAIN", "Main thing is happening") do
53
+ # ...
54
+ # output.log("SUB1", "Sub process thing") do
55
+ # ...
56
+ # output.log("SUB2", "Innermost process thing")
57
+ # end
58
+ # end
59
+ #
60
+ # output.log("MAIN", "Start something else")
61
+ #
62
+ # ------
63
+ # > [MAIN] Main thing is happening
64
+ # > [SUB1] Sub process thing
65
+ # > [SUB2] Innermost process thing
66
+ # >
67
+ # > [MAIN] Start something else
68
+ #
69
+ # Log level is set to the instance's default unless
70
+ # specified in the options argument with :level => Logger::LEVEL.
71
+ # The default log level is Logger::INFO
72
+ #
73
+ # Best practice for using log levels is to call the level methods
74
+ # which all work similarly to the log method:
75
+ # unknown, fatal, error, warn, info, debug
76
+ def log(title, message, options={}, &block)
77
+ unless Sunshine.trace?
78
+ return block.call if block_given?
79
+ return
80
+ end
81
+
82
+ options = {:indent => @indent}.merge(options)
83
+ self.print(title, message, options)
84
+ if block_given?
85
+ @indent = @indent + 1
86
+ begin
87
+ block.call
88
+ ensure
89
+ @indent = @indent - 1 unless @indent <= 0
90
+ @logger << "\n"
91
+ end
92
+ end
93
+ end
94
+
95
+ ##
96
+ # Log a message of log level unknown.
97
+ def unknown(title, message, options={}, &block)
98
+ self.log(title, message, options.merge(:level => Logger::UNKNOWN), &block)
99
+ end
100
+
101
+ ##
102
+ # Log a message of log level fatal.
103
+ def fatal(title, message, options={}, &block)
104
+ self.log(title, message, options.merge(:level => Logger::FATAL), &block)
105
+ end
106
+
107
+ ##
108
+ # Log a message of log level error.
109
+ def error(title, message, options={}, &block)
110
+ self.log(title, message, options.merge(:level => Logger::ERROR), &block)
111
+ end
112
+
113
+ ##
114
+ # Log a message of log level warn.
115
+ def warn(title, message, options={}, &block)
116
+ self.log(title, message, options.merge(:level => Logger::WARN), &block)
117
+ end
118
+
119
+ ##
120
+ # Log a message of log level info.
121
+ def info(title, message, options={}, &block)
122
+ self.log(title, message, options.merge(:level => Logger::INFO), &block)
123
+ end
124
+
125
+ ##
126
+ # Log a message of log level debug.
127
+ def debug(title, message, options={}, &block)
128
+ self.log(title, message, options.merge(:level => Logger::DEBUG), &block)
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,48 @@
1
+ module Sunshine
2
+
3
+ ##
4
+ # The Apt dependency class supports basic apt-get features:
5
+ #
6
+ # dependency_lib.instance_eval do
7
+ # apt "ruby", :version => '1.9'
8
+ # end
9
+ #
10
+ # See the Dependency class for more info.
11
+
12
+ class Apt < Dependency
13
+
14
+ def initialize(name, options={}, &block)
15
+ super(name, options) do
16
+ pkg_name = build_pkg_name @pkg.dup, options
17
+
18
+ install "apt-get install -y #{pkg_name}"
19
+ uninstall "apt-get remove -y #{pkg_name}"
20
+
21
+ @pkg = "#{@pkg}-#{options[:version]}" if options[:version]
22
+ check_test "apt-cache search ^#{@pkg} | grep -c ^#{@pkg}", '-ge 1'
23
+
24
+ instance_eval(&block) if block_given?
25
+ end
26
+ end
27
+
28
+
29
+ private
30
+
31
+ def build_pkg_name pkg_name, options={}
32
+ pkg_name << "=#{options[:version]}" if options[:version]
33
+
34
+ pkg_name
35
+ end
36
+
37
+
38
+ def run_command(command, options={})
39
+ if @dependency_lib
40
+ if @dependency_lib.exist?('apt')
41
+ @dependency_lib.install 'apt', options
42
+ end
43
+ end
44
+
45
+ super
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,349 @@
1
+ module Sunshine
2
+
3
+ ##
4
+ # Dependency objects let you define how to install, check, and remove
5
+ # a described package, including parent dependency lookup and installation.
6
+ #
7
+ # Dependency.new "ruby", :tree => dependency_lib do
8
+ # install "sudo yum install ruby"
9
+ # uninstall "sudo yum remove ruby"
10
+ # check_test "yum list installed ruby | grep -c ruby", "-ge 1"
11
+ # end
12
+ #
13
+ # Dependencies are more commonly defined through a Settler class'
14
+ # constructor methods:
15
+ #
16
+ # dependency_lib.instance_eval do
17
+ # dependency 'custom' do
18
+ # requires 'yum', 'ruby'
19
+ # install 'sudo yum install custom'
20
+ # uninstall 'sudo yum remove custom'
21
+ # check 'yum list installed custom'
22
+ # end
23
+ # end
24
+ #
25
+ # The Dependency class is simple to inherit and use as a built-in part of
26
+ # Settler (see the Yum implementation for more info):
27
+ #
28
+ # class Yum < Dependency
29
+ # def initialize(name, options={}, &block)
30
+ # super(dep_lib, name, options) do
31
+ # # Define install, check, and uninstall scripts specific to yum
32
+ # end
33
+ # end
34
+ # ...
35
+ # end
36
+ #
37
+ # Once a subclass is defined a constructor method is added automatically
38
+ # to the Settler class:
39
+ #
40
+ # dependency_lib.instance_eval do
41
+ # yum "ruby", :version => '1.9'
42
+ # end
43
+
44
+ class Dependency
45
+
46
+ class CmdError < Exception; end
47
+ class InstallError < Exception; end
48
+ class UninstallError < Exception; end
49
+
50
+
51
+ ##
52
+ # Check if sudo should be used
53
+
54
+ def self.sudo
55
+ @sudo ||= nil
56
+ end
57
+
58
+
59
+ ##
60
+ # Assign a sudo value. A value of nil means 'don't assign sudo',
61
+ # true means sudo, string means sudo -u, false means, explicitely
62
+ # don't use sudo. Yum and Gem dependency types default to sudo=true.
63
+
64
+ def self.sudo= value
65
+ @sudo = value
66
+ end
67
+
68
+
69
+ attr_reader :name, :pkg, :parents, :children
70
+
71
+ def initialize name, options={}, &block
72
+ @dependency_lib = options[:tree]
73
+
74
+ @name = name.to_s
75
+ @pkg = options[:pkg] || @name
76
+ @options = options.dup
77
+
78
+ @install = nil
79
+ @uninstall = nil
80
+ @check = nil
81
+
82
+ @parents = []
83
+ @children = []
84
+
85
+ @shell = Sunshine.shell
86
+
87
+ requires(*options[:requires]) if options[:requires]
88
+
89
+ instance_eval(&block) if block_given?
90
+ end
91
+
92
+
93
+ ##
94
+ # Append a child dependency
95
+
96
+ def add_child name
97
+ @children << name
98
+ end
99
+
100
+
101
+ ##
102
+ # Get direct child dependencies
103
+
104
+ def child_dependencies
105
+ @children
106
+ end
107
+
108
+
109
+ ##
110
+ # Define the command that checks if the dependency is installed.
111
+ # The check command must have an appropriate exitcode:
112
+ #
113
+ # dep.check "test -s 'yum list installed depname'"
114
+
115
+ def check cmd_str=nil, &block
116
+ @check = cmd_str || block
117
+ end
118
+
119
+
120
+ ##
121
+ # Define checking that the dependency is installed via unix's 'test':
122
+ #
123
+ # dep.check_test "yum list installed depname | grep -c depname", "-ge 1"
124
+
125
+ def check_test cmd_str, condition_str
126
+ check "test \"$(#{cmd_str})\" #{condition_str}"
127
+ end
128
+
129
+
130
+ ##
131
+ # Define the install command for the dependency:
132
+ #
133
+ # dep.install "yum install depname"
134
+
135
+ def install cmd=nil, &block
136
+ @install = cmd || block
137
+ end
138
+
139
+
140
+ ##
141
+ # Run the install command for the dependency
142
+ # Allows options:
143
+ # :call:: obj - an object that responds to call will be passed the bash cmd
144
+ # :skip_parents:: true - install regardless of missing parent dependencies
145
+ #
146
+ # runner = lambda{|str| system(str)}
147
+ # dep.install! :call => runner
148
+
149
+ def install! options={}
150
+ return if installed?(options)
151
+
152
+ if options[:skip_parents]
153
+ missing = missing_parents?
154
+ if missing
155
+ raise(InstallError, "Could not install #{@name}. "+
156
+ "Missing dependencies #{missing.join(", ")}")
157
+ end
158
+ else
159
+ install_parents!(options)
160
+ end
161
+
162
+ run_command(@install, options)
163
+ raise(InstallError, "Failed installing #{@name}") unless
164
+ installed?(options)
165
+ end
166
+
167
+
168
+ ##
169
+ # Call install on direct parent dependencies
170
+ # Allows options:
171
+ # :call:: obj - an object that responds to call will be passed the bash cmd
172
+ #
173
+ # runner = lambda{|str| system(str)}
174
+ # dep.install_parents! :call => runner
175
+
176
+ def install_parents! options={}
177
+ return unless @dependency_lib
178
+
179
+ @parents.each do |dep|
180
+ @dependency_lib.get(dep, options).install!(options)
181
+ end
182
+ end
183
+
184
+
185
+ ##
186
+ # Run the check command to verify that the dependency is installed
187
+ # Allows options:
188
+ # :call:: obj - an object that responds to call will be passed the bash cmd
189
+ #
190
+ # runner = lambda{|str| system(str)}
191
+ # dep.installed? :call => runner
192
+
193
+ def installed? options={}
194
+ run_command @check, options
195
+ rescue => e
196
+ false
197
+ end
198
+
199
+
200
+ ##
201
+ # Checks if any parents dependencies are missing
202
+ # Allows options:
203
+ # :call:: obj - an object that responds to call will be passed the bash cmd
204
+ #
205
+ # runner = lambda{|str| system(str)}
206
+ # dep.missing_parents? :call => runner
207
+
208
+
209
+ def missing_parents? options={}
210
+ return unless @dependency_lib
211
+
212
+ missing = []
213
+ @parents.each do |dep|
214
+ parent_dep = @dependency_lib.get dep, options
215
+
216
+ missing << dep unless parent_dep.installed?(options)
217
+
218
+ return missing if options[:limit] && options[:limit] == missing.length
219
+ end
220
+
221
+ missing.empty? ? nil : missing
222
+ end
223
+
224
+
225
+ ##
226
+ # Get direct parent dependencies
227
+
228
+ def parent_dependencies
229
+ @parents
230
+ end
231
+
232
+
233
+ ##
234
+ # Define which dependencies this dependency relies on:
235
+ #
236
+ # dep.requires 'rubygems', 'rdoc'
237
+
238
+ def requires *deps
239
+ return unless @dependency_lib
240
+
241
+ @parents.concat(deps).uniq!
242
+ deps.each do |dep|
243
+ @dependency_lib.dependencies[dep].each{|d| d.add_child(@name) }
244
+ end
245
+ end
246
+
247
+
248
+ ##
249
+ # Define the uninstall command for the dependency:
250
+ #
251
+ # dep.uninstall "yum remove depname"
252
+
253
+ def uninstall cmd=nil, &block
254
+ @uninstall = cmd || block
255
+ end
256
+
257
+
258
+ ##
259
+ # Run the uninstall command for the dependency
260
+ # Allows options:
261
+ # :call:: obj - an object that responds to call will be passed the bash cmd
262
+ # :force:: true - uninstalls regardless of child dependencies
263
+ # :remove_children:: true - removes direct child dependencies
264
+ # :remove_children:: :recursive - removes children recursively
265
+
266
+ def uninstall! options={}
267
+ if !options[:remove_children] && !options[:force]
268
+ raise UninstallError, "The #{@name} has child dependencies."
269
+ end
270
+ uninstall_children!(options) if options[:remove_children]
271
+ run_command(@uninstall, options)
272
+ raise(UninstallError, "Failed removing #{@name}") if installed?(options)
273
+ end
274
+
275
+
276
+ ##
277
+ # Removes child dependencies
278
+ # Allows options:
279
+ # :call:: obj - an object that responds to call will be passed the bash cmd
280
+ # :force:: true - uninstalls regardless of child dependencies
281
+ # :remove_children:: true - removes direct child dependencies
282
+ # :remove_children:: :recursive - removes children recursively
283
+
284
+ def uninstall_children! options={}
285
+ return unless @dependency_lib
286
+
287
+ options = options.dup
288
+
289
+ @children.each do |dep|
290
+ options.delete(:remove_children) unless
291
+ options[:remove_children] == :recursive
292
+
293
+ @dependency_lib.get(dep, options).uninstall!(options)
294
+ end
295
+ end
296
+
297
+
298
+ ##
299
+ # Alias for name
300
+
301
+ def to_s
302
+ @name
303
+ end
304
+
305
+
306
+ private
307
+
308
+ def run_command command, options={}
309
+ shell = options[:call] || @shell
310
+
311
+ if Proc === command
312
+ command.call shell, self.class.sudo
313
+
314
+ else
315
+ shell.call command, :sudo => self.class.sudo
316
+ end
317
+ end
318
+
319
+
320
+ def self.short_class_name str
321
+ str.to_s.split(":").last
322
+ end
323
+
324
+
325
+ def self.underscore str
326
+ str.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
327
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
328
+ end
329
+
330
+
331
+ def self.inherited(subclass)
332
+ class_name = short_class_name subclass.to_s
333
+ method_name = underscore class_name
334
+
335
+ DependencyLib.class_eval <<-STR, __FILE__, __LINE__ + 1
336
+ def #{method_name}(name, options={}, &block)
337
+ dep = #{class_name}.new(name, options.merge(:tree => self), &block)
338
+ self.add dep
339
+ dep
340
+ end
341
+ STR
342
+
343
+ DependencyLib.dependency_types << subclass
344
+ end
345
+
346
+ inherited self
347
+
348
+ end
349
+ end
@@ -0,0 +1,54 @@
1
+ module Sunshine
2
+
3
+ ##
4
+ # The Gem dependency class supports most of rubygem's installation features:
5
+ #
6
+ # dependency_lib.instance_eval do
7
+ # gem "rdoc", :version => '~>0.8',
8
+ # :source => 'http://gemcutter.org',
9
+ # :opts => '--use-lib blah' # Anything after --
10
+ # end
11
+ #
12
+ # See the Dependency class for more info.
13
+
14
+ class Gem < Dependency
15
+
16
+ self.sudo = true
17
+
18
+
19
+ def initialize(name, options={}, &block)
20
+ super(name, options) do
21
+ version = options[:version] ? " --version '#{options[:version]}'" : ""
22
+
23
+ source = if options[:source]
24
+ " --source #{options[:source]} --source http://gemcutter.org"
25
+ end
26
+
27
+ install_opts = " --no-ri --no-rdoc"
28
+ if options[:opts]
29
+ install_opts = "#{install_opts} -- #{options[:opts]}"
30
+ end
31
+
32
+ install "gem install #{@pkg}#{version}#{source}#{install_opts}"
33
+ uninstall "gem uninstall #{@pkg}#{version}"
34
+ check "gem list #{@pkg} -i#{version}"
35
+
36
+ requires(*options[:require].to_a) if options[:require]
37
+
38
+ instance_eval(&block) if block_given?
39
+ end
40
+ end
41
+
42
+
43
+ private
44
+
45
+ def run_command(command, options={})
46
+ if @dependency_lib
47
+ @dependency_lib.install 'rubygems', options if
48
+ @dependency_lib.exist?('rubygems')
49
+ end
50
+
51
+ super
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,62 @@
1
+ module Sunshine
2
+
3
+ ##
4
+ # The Yum dependency class supports most of yum's installation features:
5
+ #
6
+ # dependency_lib.instance_eval do
7
+ # yum "ruby", :version => '1.9',
8
+ # :rel => 'release-num',
9
+ # :arch => 'i386',
10
+ # :epoch => 'some-epoch'
11
+ # end
12
+ #
13
+ # See the Dependency class for more info.
14
+
15
+ class Yum < Dependency
16
+
17
+ self.sudo = true
18
+
19
+
20
+ def initialize(name, options={}, &block)
21
+ super(name, options) do
22
+ pkg_name = build_pkg_name @pkg.dup, options
23
+
24
+ install "yum install -y #{pkg_name}"
25
+ uninstall "yum remove -y #{pkg_name}"
26
+ check_test "yum list installed #{pkg_name} | grep -c #{@pkg}", '-ge 1'
27
+
28
+ instance_eval(&block) if block_given?
29
+ end
30
+ end
31
+
32
+
33
+ private
34
+
35
+ def build_pkg_name pkg_name, options={}
36
+ if options[:version]
37
+ pkg_name << "-#{options[:version]}"
38
+
39
+ if options[:rel]
40
+ pkg_name << "-#{options[:rel]}"
41
+
42
+ pkg_name = "#{options[:epoch]}:#{pkg_name}" if
43
+ options[:arch] && options[:epoch]
44
+ end
45
+ end
46
+
47
+ pkg_name << ".#{options[:arch]}" if options[:arch]
48
+ pkg_name
49
+ end
50
+
51
+
52
+ def run_command(command, options={})
53
+ if @dependency_lib
54
+ if @dependency_lib.exist?('yum')
55
+ @dependency_lib.install 'yum', options
56
+ end
57
+ end
58
+
59
+ super
60
+ end
61
+ end
62
+ end