sunshine 1.0.3 → 1.1.0

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 (42) hide show
  1. data/History.txt +22 -2
  2. data/Manifest.txt +7 -0
  3. data/README.txt +333 -57
  4. data/Rakefile +1 -1
  5. data/lib/commands/add.rb +2 -2
  6. data/lib/commands/default.rb +15 -8
  7. data/lib/commands/list.rb +5 -3
  8. data/lib/commands/restart.rb +2 -2
  9. data/lib/commands/rm.rb +2 -2
  10. data/lib/commands/run.rb +2 -2
  11. data/lib/commands/start.rb +2 -2
  12. data/lib/commands/stop.rb +2 -2
  13. data/lib/sunshine.rb +117 -132
  14. data/lib/sunshine/app.rb +116 -10
  15. data/lib/sunshine/crontab.rb +11 -2
  16. data/lib/sunshine/daemon.rb +60 -46
  17. data/lib/sunshine/daemons/apache.rb +10 -2
  18. data/lib/sunshine/daemons/ar_sendmail.rb +0 -6
  19. data/lib/sunshine/daemons/delayed_job.rb +2 -0
  20. data/lib/sunshine/daemons/mongrel_rails.rb +32 -0
  21. data/lib/sunshine/daemons/nginx.rb +3 -0
  22. data/lib/sunshine/daemons/rainbows.rb +2 -0
  23. data/lib/sunshine/daemons/server.rb +51 -24
  24. data/lib/sunshine/daemons/server_cluster.rb +47 -0
  25. data/lib/sunshine/daemons/thin.rb +36 -0
  26. data/lib/sunshine/daemons/unicorn.rb +4 -1
  27. data/lib/sunshine/dependencies.rb +10 -3
  28. data/lib/sunshine/healthcheck.rb +2 -2
  29. data/lib/sunshine/remote_shell.rb +11 -2
  30. data/lib/sunshine/repo.rb +1 -1
  31. data/lib/sunshine/repos/rsync_repo.rb +1 -0
  32. data/templates/apache/apache.conf.erb +25 -18
  33. data/templates/mongrel_rails/mongrel_rails.conf.erb +9 -0
  34. data/templates/nginx/nginx.conf.erb +12 -9
  35. data/templates/thin/thin.conf.erb +12 -0
  36. data/test/helper_methods.rb +161 -0
  37. data/test/unit/test_daemon.rb +1 -8
  38. data/test/unit/test_nginx.rb +1 -1
  39. data/test/unit/test_server.rb +16 -0
  40. data/test/unit/test_server_cluster.rb +46 -0
  41. data/test/unit/test_sunshine.rb +18 -12
  42. metadata +14 -11
data/lib/sunshine/app.rb CHANGED
@@ -1,15 +1,18 @@
1
1
  module Sunshine
2
2
 
3
3
  ##
4
- # App objects are the core of sunshine deployment. The sunshine paradygm
4
+ # App objects are the core of Sunshine deployment. The Sunshine paradygm
5
5
  # is to construct an app object, and run custom deploy code by passing
6
6
  # a block to its deploy method:
7
7
  #
8
- # options = {
8
+ # someserver = Sunshine::RemoteShell.new "user@someserver.com",
9
+ # :roles => [:web, :app]
10
+ #
11
+ # options = {
9
12
  # :name => 'myapp',
10
13
  # :repo => {:type => :svn, :url => 'svn://blah...'},
11
14
  # :root_path => '/usr/local/myapp',
12
- # :remote_shells => ['user@someserver.com']
15
+ # :remote_shells => 'user@someserver.com'
13
16
  # }
14
17
  #
15
18
  # app = Sunshine::App.new(options)
@@ -19,10 +22,12 @@ module Sunshine
19
22
  # app_server = Sunshine::Rainbows.new(app)
20
23
  # app_server.restart
21
24
  #
22
- # Sunshine::Nginx.new(app, :point_to => app_server).restart
25
+ # Sunshine::Nginx.new(app, :point_to => app_server).setup
23
26
  #
24
27
  # end
25
28
  #
29
+ # app.start :force => true
30
+ #
26
31
  # Multiple apps can be defined, and deployed from a single deploy script.
27
32
  # The constructor also supports passing a yaml file path:
28
33
  #
@@ -31,8 +36,104 @@ module Sunshine
31
36
  # Deployment can be expressed more concisely by calling App::deploy:
32
37
  #
33
38
  # App.deploy("path/to/config.yml") do |app|
34
- # Sunshine::Rainbows.new(app).restart
39
+ # Sunshine::Rainbows.new(app).setup
35
40
  # end
41
+ #
42
+ #
43
+ # An App holds information about where to deploy an application to and
44
+ # how to deploy it, as well as many convenience methods to setup and
45
+ # manipulate the deployment process. Most of these methods support passing
46
+ # remote shell find options:
47
+ #
48
+ # app.rake 'db:migrate', :role => :db
49
+ # app.deploy :host => 'server1.com'
50
+ #
51
+ # See App#find for more information.
52
+ #
53
+ # App instantiation can be done in several ways:
54
+ # App.new instantiation_hash
55
+ # App.new "path/to/config.yml", optional_extra_hash
56
+ # App.new #=> will attempt to load ruby's file DATA as yaml
57
+ #
58
+ # Yaml files must define settings on a per-environment basis. The default
59
+ # environment will be used if the deploy_env is not found in the config.
60
+ # Let's consider the following config:
61
+ #
62
+ # #config.yml:
63
+ # ---
64
+ # :default:
65
+ # :repo:
66
+ # :type: :svn
67
+ # :url: http://subversion/repo/tags/release-001
68
+ # :remote_shells: dev.myserver.com
69
+ #
70
+ # :qa:
71
+ # :remote_shells:
72
+ # - qa1.myserver.com
73
+ # - qa2.myserver.com
74
+ #
75
+ # :qa_special:
76
+ # :inherits: :qa
77
+ # :root_path: /path/to/application
78
+ #
79
+ # By default, environment definitions inherit the :default environment. In
80
+ # this instance, :qa_special also inherits from :qa.
81
+ # With the given config, I could setup the App instance as so:
82
+ #
83
+ # App.new "config.yml", :deploy_env => :development
84
+ # # Note: by default, App will get the deploy_env value
85
+ # # from Sunshine.deploy_env
86
+ #
87
+ # The above will simply load the default config. The following, however,
88
+ # will load the :qa_special config which inherits from
89
+ # both :qa and :default:
90
+ #
91
+ # App.new "config.yml", :deploy_env => :qa_special
92
+ #
93
+ #
94
+ # Another way of instantiating an App is to pass it a hash. Unlike the yaml
95
+ # config file, the hash is not on a per-environment basis and isexpected
96
+ # to already have the correct values for the given environment.
97
+ # The following is equivalent to loading the above :default environment:
98
+ #
99
+ # App.new :remote_shells => "dev.myserver.com",
100
+ # :repo => {
101
+ # :type => :svn,
102
+ # :url => "http://subversion/repo/tags/release-001"
103
+ # }
104
+ #
105
+ # In theory, the minimum amount of information required to instantiate
106
+ # an app is the repo and remote_shells. If the repo option is omitted,
107
+ # the App will attempt to detect if the pwd is a checkout out repo and
108
+ # use that information. If you would like to deploy an application that
109
+ # is not under source countrol, you may do so by using Sunshine::RsyncRepo,
110
+ # or passing :rsync in your hash as your repo type.
111
+ #
112
+ #
113
+ # Options supported by App.new are the following:
114
+ #
115
+ # :deploy_env:: String - specify the env to deploy with; defaults to
116
+ # Sunshine#deploy_env.
117
+ #
118
+ # :deploy_name:: String - if you want to specify a name for your deploy and
119
+ # checkout directory (affects the checkout_path); defaults to Time.now.to_i.
120
+ #
121
+ # :remote_shells:: String|Array|Sunshine::Shell - the shell(s) to use for
122
+ # deployment. Accepts any single instance or array of a Sunshine::Shell
123
+ # type instance or Sunshine::Shell instantiator-friendly arguments.
124
+ #
125
+ # :repo:: Hash|Sunshine::Repo - the scm and repo to use for deployment.
126
+ # Accepts any hash that can be passed to Sunshine::Repo::new_of_type
127
+ # or any Sunshine::Repo object.
128
+ #
129
+ # :root_path:: String - the absolute path the deployed application
130
+ # should live in; defaults to "#{Sunshine.web_directory}/#{@name}".
131
+ #
132
+ # :shell_env:: Hash - environment variables to add to deploy shells.
133
+ #
134
+ # :sudo:: true|false|nil|String - which sudo value should be assigned to
135
+ # deploy shells; defaults to Sunshine#sudo. For more information on using
136
+ # sudo, see the Using Permissions section in README.txt.
36
137
 
37
138
  class App
38
139
 
@@ -51,6 +152,11 @@ module Sunshine
51
152
  attr_reader :root_path, :checkout_path, :current_path, :deploys_path
52
153
  attr_reader :shared_path, :log_path, :deploy_name, :deploy_env
53
154
 
155
+ ##
156
+ # App instantiation can be done in several ways:
157
+ # App.new instantiation_hash
158
+ # App.new "path/to/config.yml", optional_extra_hash
159
+ # App.new #=> will attempt to load ruby's file DATA as yaml
54
160
 
55
161
  def initialize config_file=Sunshine::DATA, options={}
56
162
  options, config_file = config_file, Sunshine::DATA if Hash === config_file
@@ -833,13 +939,13 @@ module Sunshine
833
939
  # server_apps_from_config ["svr1", "svr2", "svr3"]
834
940
  # #=> [<ServerApp @host="svr1">,<ServerApp @host="svr2">, ...]
835
941
  #
836
- # d_servers = [["svr1", {:roles => "web db app"}], "svr2", "svr3"]
837
- # server_apps_from_config d_servers
942
+ # remote_shells = [["svr1", {:roles => "web db app"}], "svr2", "svr3"]
943
+ # server_apps_from_config remote_shells
838
944
  # #=> [<ServerApp @host="svr1">,<ServerApp @host="svr2">, ...]
839
945
 
840
- def server_apps_from_config d_servers
841
- d_servers = [*d_servers].compact
842
- d_servers.map{|ds| ServerApp.new(*[self,*ds]) }
946
+ def server_apps_from_config shells
947
+ shells = [*shells].compact
948
+ shells.map{|shell| ServerApp.new(*[self,*shell]) }
843
949
  end
844
950
 
845
951
 
@@ -1,8 +1,8 @@
1
1
  module Sunshine
2
2
 
3
3
  ##
4
- # A simple namespaced grouping of cron jobs that can be written
5
- # to a deploy server.
4
+ # A simple namespaced grouping of cron jobs that can be read and written
5
+ # to a shell.
6
6
 
7
7
  class Crontab
8
8
 
@@ -117,11 +117,17 @@ module Sunshine
117
117
 
118
118
  private
119
119
 
120
+ ##
121
+ # Write the string to the shell's crontab.
122
+
120
123
  def write_crontab content
121
124
  @shell.call("echo '#{content.gsub(/'/){|s| "'\\''"}}' | crontab")
122
125
  end
123
126
 
124
127
 
128
+ ##
129
+ # Deletes all jobs of a given namespace in a crontab string.
130
+
125
131
  def delete_jobs crontab, namespace=nil
126
132
  start_id, end_id = get_job_ids namespace
127
133
 
@@ -131,6 +137,9 @@ module Sunshine
131
137
  end
132
138
 
133
139
 
140
+ ##
141
+ # Returns the cronjob begin and end flags.
142
+
134
143
  def get_job_ids namespace=nil
135
144
  namespace ||= "[^\n]*"
136
145
 
@@ -17,8 +17,17 @@ module Sunshine
17
17
  # for template rendering.
18
18
 
19
19
  def self.binder_methods
20
- [:app, :name, :target, :bin, :pid, :port,
21
- :processes, :config_path, :log_file, :timeout]
20
+ [:app, :name, :bin, :pid, :processes, :config_path, :log_file, :timeout]
21
+ end
22
+
23
+
24
+ ##
25
+ # Returns the short, snake-case version of the class:
26
+ # Sunshine::Daemon.short_name
27
+ # #=> "daemon"
28
+
29
+ def self.short_name
30
+ @short_name ||= self.underscore self.to_s.split("::").last
22
31
  end
23
32
 
24
33
 
@@ -31,7 +40,7 @@ module Sunshine
31
40
  end
32
41
 
33
42
 
34
- attr_reader :app, :name, :target
43
+ attr_reader :app, :name
35
44
 
36
45
  attr_accessor :bin, :pid, :processes, :timeout, :sudo, :server_apps
37
46
 
@@ -43,34 +52,31 @@ module Sunshine
43
52
  # Daemon objects need only an App object to be instantiated but many options
44
53
  # are available for customization:
45
54
  #
46
- # :pid:: pid_path - set the pid; default: app.shared_path/pids/svr_name.pid
47
- # defaults to app.shared_path/pids/svr_name.pid
48
- #
49
- # :bin:: bin_path - set the daemon app bin path (e.g. usr/local/nginx)
50
- # defaults to svr_name
55
+ # :bin:: bin_path - Set the daemon app bin path (e.g. usr/local/nginx)
56
+ # defaults to svr_name.
51
57
  #
52
- # :sudo:: bool|str - define if sudo should be used and with what user
58
+ # :processes:: prcss_num - Number of processes daemon should run;
59
+ # defaults to 1.
53
60
  #
54
- # :timeout:: int|str - timeout to use for daemon config
55
- # defaults to 1
61
+ # :config_file:: name - Remote file name the daemon should load;
62
+ # defaults to svr_name.conf
56
63
  #
57
- # :processes:: prcss_num - number of processes daemon should run
58
- # defaults to 0
64
+ # :config_path:: path - Remote path daemon configs will be uploaded to;
65
+ # defaults to app.current_path/daemons/svr_name
59
66
  #
60
- # :config_template:: path - glob path to tempates to render and upload
61
- # defaults to sunshine_path/templates/svr_name/*
67
+ # :config_template:: path - Glob path to tempates to render and upload;
68
+ # defaults to sunshine_path/templates/svr_name/*
62
69
  #
63
- # :config_path:: path - remote path daemon configs will be uploaded to
64
- # defaults to app.current_path/daemons/svr_name
70
+ # :log_path:: path - Path to where the log files should be output;
71
+ # defaults to app.log_path.
65
72
  #
66
- # :config_file:: name - remote file name the daemon should load
67
- # defaults to svr_name.conf
73
+ # :pid:: pid_path - Set the pid; default: app.shared_path/pids/svr_name.pid
74
+ # defaults to app.shared_path/pids/svr_name.pid.
68
75
  #
69
- # :log_path:: path - path to where the log files should be output
70
- # defaults to app.log_path
76
+ # :sudo:: bool|str - Define if sudo should be used to run the daemon,
77
+ # and/or with what user.
71
78
  #
72
- # :point_to:: app|daemon - an abstract target to point to
73
- # defaults to the passed app
79
+ # :timeout:: int - Timeout to use for daemon config, defaults to 0.
74
80
  #
75
81
  # The Daemon constructor also supports any App#find options to narrow
76
82
  # the server apps to use. Note: subclasses such as Server already have
@@ -78,27 +84,24 @@ module Sunshine
78
84
 
79
85
  def initialize app, options={}
80
86
  @options = options
87
+ @app = app
81
88
 
82
- @app = app
83
- @target = options[:point_to] || @app
84
-
85
- @short_class_name = self.class.underscore self.class.to_s.split("::").last
86
-
87
- @name = options[:name] || @short_class_name
88
-
89
- @pid = options[:pid] || "#{@app.shared_path}/pids/#{@name}.pid"
90
- @bin = options[:bin] || @name
89
+ @name = options[:name] || self.class.short_name
90
+ @pid = options[:pid] || "#{@app.shared_path}/pids/#{@name}.pid"
91
+ @bin = options[:bin] || self.class.short_name
91
92
  @sudo = options[:sudo]
92
- @timeout = options[:timeout] || 0
93
- @dep_name = options[:dep_name] || @name
93
+ @timeout = options[:timeout] || 0
94
+ @dep_name = options[:dep_name] || self.class.short_name
94
95
  @processes = options[:processes] || 1
96
+ @sigkill = 'QUIT'
95
97
 
96
- @config_template = options[:config_template]
97
- @config_template ||= "#{Sunshine::ROOT}/templates/#{@short_class_name}/*"
98
+ @config_template = options[:config_template] ||
99
+ "#{Sunshine::ROOT}/templates/#{self.class.short_name}/*"
98
100
 
99
101
  @config_path = options[:config_path] ||
100
102
  "#{@app.current_path}/daemons/#{@name}"
101
- @config_file = options[:config_file] || "#{@name}.conf"
103
+
104
+ @config_file = options[:config_file] || "#{self.class.short_name}.conf"
102
105
 
103
106
  log_path = options[:log_path] || @app.log_path
104
107
  @log_files = {
@@ -195,6 +198,20 @@ module Sunshine
195
198
  end
196
199
 
197
200
 
201
+ ##
202
+ # Check if the daemon is running on all servers
203
+
204
+ def status
205
+ each_server_app do |server_app|
206
+ server_app.shell.call status_cmd, :sudo => pick_sudo(server_app.shell)
207
+ end
208
+ true
209
+
210
+ rescue CmdError => e
211
+ false
212
+ end
213
+
214
+
198
215
  ##
199
216
  # Stop the daemon app.
200
217
 
@@ -248,12 +265,11 @@ module Sunshine
248
265
 
249
266
 
250
267
  ##
251
- # Gets the command that stops the daemon.
252
- # Should be overridden by child classes.
268
+ # Default daemon stop command.
253
269
 
254
270
  def stop_cmd
255
- return @stop_cmd ||
256
- raise(CriticalDeployError, "@stop_cmd undefined. Can't stop #{@name}")
271
+ "test -f #{@pid} && kill -#{@sigkill} $(cat #{@pid}) && sleep 1 && "+
272
+ "rm -f #{@pid} || echo 'Could not kill #{@name} pid for #{@app.name}';"
257
273
  end
258
274
 
259
275
 
@@ -304,7 +320,7 @@ module Sunshine
304
320
  # Upload config files and run them through erb with the provided
305
321
  # binding if necessary.
306
322
 
307
- def upload_config_files(shell, setup_binding=binding)
323
+ def upload_config_files shell, setup_binding=binding
308
324
  config_template_files.each do |config_file|
309
325
 
310
326
  if File.extname(config_file) == ".erb"
@@ -343,10 +359,6 @@ module Sunshine
343
359
  shell.expand_path path
344
360
  end
345
361
 
346
- binder.set :target_server do
347
- target.server_name || server_name
348
- end
349
-
350
362
  binder
351
363
  end
352
364
 
@@ -399,6 +411,8 @@ module Sunshine
399
411
 
400
412
  def register_after_user_script
401
413
  @app.after_user_script do |app|
414
+ next unless has_setup?
415
+
402
416
  each_server_app do |sa|
403
417
  sudo = pick_sudo sa.shell
404
418
 
@@ -2,6 +2,10 @@ module Sunshine
2
2
 
3
3
  ##
4
4
  # A wrapper for configuring the apache2 server.
5
+ # Note: Due to Apache default limitations, the @connections attribute
6
+ # defaults to 256.
7
+ #
8
+ # Note: The minimum timeout supported by Apache is 1 second.
5
9
 
6
10
  class Apache < Server
7
11
 
@@ -12,8 +16,12 @@ module Sunshine
12
16
 
13
17
  @sigkill = 'WINCH'
14
18
 
15
- # TODO: have a separate max_clients and processes
16
- @max_clients = options[:max_clients] || options[:processes] || 128
19
+ @supports_rack = false
20
+ @supports_passenger = true
21
+
22
+ @connections = options[:connections] || 256
23
+
24
+ @timeout = 1 if @timeout < 1
17
25
 
18
26
  @dep_name = options[:dep_name] ||
19
27
  use_passenger? ? 'passenger-apache' : 'apache2'
@@ -18,11 +18,5 @@ module Sunshine
18
18
  def start_cmd
19
19
  "cd #{@app.current_path} && #{@bin} -p #{@pid} -d"
20
20
  end
21
-
22
-
23
- def stop_cmd
24
- "test -f #{@pid} && kill `cat #{@pid}` || "+
25
- "echo 'No #{@name} process to stop for #{@app.name}'; rm -f #{@pid};"
26
- end
27
21
  end
28
22
  end
@@ -4,6 +4,8 @@ module Sunshine
4
4
  # Simple daemon wrapper for delayed_job daemon setup and control.
5
5
  # By default, uses server apps with the :dj role. Supports
6
6
  # the :processes option.
7
+ #
8
+ # Note: The pid location is fixed at [current_path]/tmp/pids/delayed_job.pid"
7
9
 
8
10
  class DelayedJob < Daemon
9
11
 
@@ -0,0 +1,32 @@
1
+ module Sunshine
2
+
3
+ ##
4
+ # Simple wrapper around mongrel setup and commands. For clustering,
5
+ # look at using Sunshine's ServerCluster method, e.g.:
6
+ # MongrelRails.new_cluster 10, app, :port => 5000
7
+ #
8
+ # Note: This is a rails-only server, implemented for compatibility
9
+ # with older non-rack rails applications. Consider upgrading your
10
+ # application to run on rack and/or thin.
11
+ #
12
+ # Note: Mongrel only supports a single log file.
13
+ # The default stdout file is used.
14
+
15
+ class MongrelRails < Server
16
+
17
+ def initialize app, options={}
18
+ super
19
+
20
+ @dep_name = options[:dep_name] || "mongrel"
21
+
22
+ @supports_rack = false
23
+ @supports_passenger = false
24
+ end
25
+
26
+
27
+ def start_cmd
28
+ "cd #{@app.current_path} && mongrel_rails start "+
29
+ "-C #{self.config_file_path}"
30
+ end
31
+ end
32
+ end