sunshine 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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