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,322 @@
1
+ require 'json'
2
+
3
+ module Sunshine
4
+
5
+ ##
6
+ # List and perform simple state actions on lists of sunshine apps.
7
+ #
8
+ # Usage: sunshine list app_name [more names...] [options]
9
+ #
10
+ # Arguments:
11
+ # app_name Name of an application to list.
12
+ #
13
+ # Options:
14
+ # -s, --status Check if an app is running.
15
+ # -d, --details Get details about the deployed apps.
16
+ # -h, --health [STATUS] Set or get the healthcheck status.
17
+ # (enable, disable, remove)
18
+ # -f, --format FORMAT Set the output format (txt, yml, json)
19
+ # -u, --user USER User to use for remote login. Use with -r
20
+ # -r, --remote svr1,svr2 Run on one or more remote servers.
21
+ # -v, --verbose Run in verbose mode.
22
+
23
+ class ListCommand < DefaultCommand
24
+
25
+ ##
26
+ # Takes an array and a hash, runs the command and returns:
27
+ # true: success
28
+ # false: failed
29
+ # exitcode:
30
+ # code == 0: success
31
+ # code != 0: failed
32
+ # and optionally an accompanying message.
33
+
34
+ def self.exec names, config
35
+ action = config['return'] || :exist?
36
+
37
+ args = config[action.to_s] || []
38
+ args = [args, names].flatten
39
+
40
+ output = exec_each_server config do |shell|
41
+ new(shell).send(action, *args)
42
+ end
43
+
44
+ return output
45
+ end
46
+
47
+
48
+ ##
49
+ # Executes common list functionality for each deploy server.
50
+
51
+ def self.exec_each_server config
52
+ shells = config['servers']
53
+ format = config['format']
54
+
55
+ responses = {}
56
+ success = true
57
+
58
+ shells.each do |shell|
59
+ shell.connect
60
+
61
+ begin
62
+ state, response = yield(shell)
63
+ rescue => e
64
+ state = false
65
+ response = "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
66
+ end
67
+
68
+ host = shell.host
69
+ success = state if success
70
+ responses[host] = build_response state, response
71
+
72
+ shell.disconnect
73
+ end
74
+
75
+ output = format ? self.send(format, responses) : responses
76
+ return success, output
77
+ end
78
+
79
+
80
+ ##
81
+ # Formats response as text output:
82
+ # ------------------
83
+ # subdomain.host.com
84
+ # ------------------
85
+ # app_name: running
86
+
87
+ def self.txt_format res_hash
88
+ str_out = ""
89
+
90
+ res_hash.each do |host, response|
91
+ separator = "-" * host.length
92
+
93
+ host_status = if Hash === response[:data]
94
+ apps_status = response[:data].map do |app_name, status|
95
+ "#{app_name}: #{status[:data]}\n"
96
+ end
97
+ apps_status.join("\n")
98
+
99
+ else
100
+ response[:data]
101
+ end
102
+
103
+ str_out << "\n"
104
+ str_out << [separator, host, separator].join("\n")
105
+ str_out << "\n"
106
+ str_out << host_status
107
+ str_out << "\n"
108
+ end
109
+
110
+ str_out
111
+ end
112
+
113
+
114
+ ##
115
+ # Formats response as yaml:
116
+
117
+ def self.yml_format res_hash
118
+ res_hash.to_yaml
119
+ end
120
+
121
+
122
+ ##
123
+ # Formats response as json:
124
+
125
+ def self.json_format res_hash
126
+ res_hash.to_json
127
+ end
128
+
129
+
130
+
131
+ attr_accessor :app_list, :shell
132
+
133
+ def initialize shell
134
+ @shell = shell
135
+ @app_list = self.class.load_list @shell
136
+ end
137
+
138
+
139
+ ##
140
+ # Reads and returns the specified apps' info file.
141
+ # Returns a response hash (see ListCommand#each_app).
142
+
143
+ def details(*app_names)
144
+ each_app(*app_names) do |server_app|
145
+ "\n#{server_app.deploy_details.to_yaml}"
146
+ end
147
+ end
148
+
149
+
150
+ ##
151
+ # Returns the path of specified apps.
152
+ # Returns a response hash (see ListCommand#each_app).
153
+
154
+ def exist?(*app_names)
155
+ each_app(*app_names) do |server_app|
156
+ server_app.root_path
157
+ end
158
+ end
159
+
160
+
161
+ ##
162
+ # Get or set the healthcheck state.
163
+ # Returns a response hash (see ListCommand#each_app).
164
+
165
+ def health(*app_names)
166
+ action = app_names.delete_at(0) if Symbol === app_names.first
167
+
168
+ each_app(*app_names) do |server_app|
169
+ server_app.health.send action if action
170
+ server_app.health.status
171
+ end
172
+ end
173
+
174
+
175
+ ##
176
+ # Checks if the apps' pids are present.
177
+ # Returns a response hash (see ListCommand#each_app).
178
+
179
+ def status(*app_names)
180
+ each_app(*app_names) do |server_app|
181
+ server_app.status
182
+ end
183
+ end
184
+
185
+
186
+ ##
187
+ # Runs a command and returns the status for each app_name:
188
+ # status_after_command 'restart', ['app1', 'app2']
189
+
190
+ def status_after_command cmd, app_names
191
+ each_app(*app_names) do |server_app|
192
+
193
+ yield(server_app) if block_given?
194
+
195
+ begin
196
+ server_app.send cmd.to_sym
197
+ server_app.running? ? 'running' : 'down'
198
+
199
+ rescue CmdError => e
200
+ raise "Failed running #{cmd}: #{server_app.status}"
201
+ end
202
+ end
203
+ end
204
+
205
+
206
+ # Do something with each server app and build a response hash:
207
+ # each_app do |server_app|
208
+ # ...
209
+ # end
210
+ #
211
+ # Restrict it to a set of apps if they are present on the server:
212
+ # each_app('app1', 'app2') do |server_app|
213
+ # ...
214
+ # end
215
+ #
216
+ # Returns a response hash:
217
+ # {"app_name" => {:success => true, :data => "somedata"} ...}
218
+
219
+ def each_app(*app_names)
220
+ app_names = @app_list.keys if app_names.empty?
221
+
222
+ response_for_each(*app_names) do |name|
223
+ path = @app_list[name]
224
+
225
+ raise "Application not found." unless path
226
+
227
+ server_app = ServerApp.new name, @shell, :root_path => path
228
+
229
+ yield(server_app) if block_given?
230
+ end
231
+ end
232
+
233
+
234
+ ##
235
+ # Builds a response object for each item passed and returns
236
+ # the result of the passed block as its data value.
237
+
238
+ def response_for_each(*items)
239
+ response = {}
240
+ success = true
241
+
242
+ items.each do |item|
243
+
244
+ begin
245
+ data = yield(item) if block_given?
246
+
247
+ response[item] = self.class.build_response true, data
248
+
249
+ rescue => e
250
+ success = false
251
+ response[item] = self.class.build_response false, e.message
252
+ end
253
+
254
+ end
255
+
256
+ [success, response]
257
+ end
258
+
259
+
260
+ ##
261
+ # Builds a standard response entry:
262
+ # {:success => true, :data => "somedata"}
263
+
264
+ def self.build_response success, data=nil
265
+ {:success => success, :data => data}
266
+ end
267
+
268
+
269
+ ##
270
+ # Load the app list yaml file from the server.
271
+
272
+ def self.load_list server
273
+ list = YAML.load(server.call(Sunshine::READ_LIST_CMD))
274
+ list = {} unless Hash === list
275
+ list
276
+ end
277
+
278
+
279
+ ##
280
+ # Write the app list hash to the remote server.
281
+
282
+ def self.save_list list, server
283
+ path = server.expand_path Sunshine::APP_LIST_PATH
284
+ server.make_file path, list.to_yaml
285
+ end
286
+
287
+
288
+ ##
289
+ # Parses the argv passed to the command
290
+
291
+ def self.parse_args argv
292
+ parse_remote_args(argv) do |opt, options|
293
+ opt.banner = <<-EOF
294
+
295
+ Usage: #{opt.program_name} list app_name [more names...] [options]
296
+
297
+ Arguments:
298
+ app_name Name of an application to list.
299
+ EOF
300
+
301
+ opt.on('-s', '--status',
302
+ 'Check if an app is running.') do
303
+ options['return'] = :status
304
+ end
305
+
306
+ opt.on('-d', '--details',
307
+ 'Get details about the deployed apps.') do
308
+ options['return'] = :details
309
+ end
310
+
311
+
312
+ vals = [:enable, :disable, :remove]
313
+ desc = "Set or get the healthcheck status. (#{vals.join(", ")})"
314
+
315
+ opt.on('-h', '--health [STATUS]', vals, desc) do |status|
316
+ options['health'] = status.to_sym if status
317
+ options['return'] = :health
318
+ end
319
+ end
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,62 @@
1
+ module Sunshine
2
+
3
+ ##
4
+ # Runs the restart script of all specified sunshine apps.
5
+ #
6
+ # Usage: sunshine restart app_name [more names...] [options]
7
+ #
8
+ # Arguments:
9
+ # app_name Name of the application to restart.
10
+ #
11
+ # Options:
12
+ # -f, --format FORMAT Set the output format (txt, yml, json)
13
+ # -u, --user USER User to use for remote login. Use with -r.
14
+ # -r, --remote svr1,svr2 Run on one or more remote servers.
15
+ # -v, --verbose Run in verbose mode.
16
+
17
+ class RestartCommand < ListCommand
18
+
19
+ ##
20
+ # Takes an array and a hash, runs the command and returns:
21
+ # true: success
22
+ # false: failed
23
+ # exitcode:
24
+ # code == 0: success
25
+ # code != 0: failed
26
+ # and optionally an accompanying message.
27
+
28
+ def self.exec names, config
29
+ output = exec_each_server config do |shell|
30
+ new(shell).restart(names)
31
+ end
32
+
33
+ return output
34
+ end
35
+
36
+
37
+ ##
38
+ # Restart specified apps.
39
+
40
+ def restart app_names
41
+ status_after_command :restart, app_names
42
+ end
43
+
44
+
45
+ ##
46
+ # Parses the argv passed to the command
47
+
48
+ def self.parse_args argv
49
+ parse_remote_args(argv) do |opt, options|
50
+ opt.banner = <<-EOF
51
+
52
+ Usage: #{opt.program_name} restart app_name [more names...] [options]
53
+
54
+ Arguments:
55
+ app_name Name of the application to restart.
56
+ EOF
57
+
58
+ end
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,83 @@
1
+ module Sunshine
2
+
3
+ ##
4
+ # Unregister a sunshine app.
5
+ #
6
+ # Usage: sunshine rm app_name [more names...] [options]
7
+ #
8
+ # Arguments:
9
+ # app_name Name of the application to remove.
10
+ #
11
+ # Options:
12
+ # -d, --delete Delete the app directory.
13
+ # -f, --format FORMAT Set the output format (txt, yml, json)
14
+ # -u, --user USER User to use for remote login. Use with -r.
15
+ # -r, --remote svr1,svr2 Run on one or more remote servers.
16
+ # -v, --verbose Run in verbose mode.
17
+
18
+ class RmCommand < ListCommand
19
+
20
+ ##
21
+ # Takes an array and a hash, runs the command and returns:
22
+ # true: success
23
+ # false: failed
24
+ # exitcode:
25
+ # code == 0: success
26
+ # code != 0: failed
27
+ # and optionally an accompanying message.
28
+
29
+
30
+ def self.exec names, config
31
+ delete_dir = config['delete_dir']
32
+
33
+ output = exec_each_server config do |shell|
34
+ server_command = new(shell)
35
+ results = server_command.remove(names, delete_dir)
36
+
37
+ self.save_list server_command.app_list, shell
38
+
39
+ results
40
+ end
41
+
42
+ return output
43
+ end
44
+
45
+
46
+ ##
47
+ # Remove a registered app on a given deploy server
48
+
49
+ def remove app_names, delete_dir=false
50
+ each_app(*app_names) do |server_app|
51
+ if delete_dir
52
+ server_app.stop rescue nil
53
+ server_app.shell.call "rm -rf #{server_app.root_path}"
54
+
55
+ server_app.crontab.delete!
56
+ end
57
+
58
+ @app_list.delete server_app.name
59
+ end
60
+ end
61
+
62
+
63
+ ##
64
+ # Parses the argv passed to the command
65
+
66
+ def self.parse_args argv
67
+ parse_remote_args(argv) do |opt, options|
68
+ opt.banner = <<-EOF
69
+
70
+ Usage: #{opt.program_name} rm app_name [more names...] [options]
71
+
72
+ Arguments:
73
+ app_name Name of the application to remove.
74
+ EOF
75
+
76
+ opt.on('-d', '--delete',
77
+ 'Delete the app directory.') do
78
+ options['delete_dir'] = true
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,151 @@
1
+ module Sunshine
2
+
3
+ ##
4
+ # Run one or more sunshine scripts.
5
+ #
6
+ # Usage: sunshine run [run_file] [options]
7
+ #
8
+ # Arguments:
9
+ # run_file Load a script or app path. Defaults to ./Sunshine
10
+ #
11
+ # Options:
12
+ # -l, --level LEVEL Set trace level. Defaults to info.
13
+ # -e, --env DEPLOY_ENV Sets the deploy env. Defaults to development.
14
+ # -a, --auto Non-interactive - automate or fail.
15
+ # --no-trace Don't trace any output.
16
+
17
+ class RunCommand < DefaultCommand
18
+
19
+ ##
20
+ # Takes an array and a hash, runs the command and returns:
21
+ # true: success
22
+ # false: failed
23
+ # exitcode:
24
+ # code == 0: success
25
+ # code != 0: failed
26
+ # and optionally an accompanying message.
27
+
28
+ def self.exec run_files, config
29
+
30
+ run_files.each do |run_file|
31
+
32
+ run_file = run_file_from run_file
33
+
34
+ with_load_path File.dirname(run_file) do
35
+
36
+ puts "Running #{run_file}"
37
+
38
+ get_file_data run_file
39
+
40
+ require run_file
41
+ end
42
+ end
43
+
44
+ return true
45
+ end
46
+
47
+
48
+ ##
49
+ # Tries to infer what run file to used based on a given path:
50
+ # run_file_from "path/to/some/dir"
51
+ # #=> "path/to/some/dir/Sunshine"
52
+ # run_file_from nil
53
+ # #=> "Sunshine"
54
+ # run_file_from "path/to/run_script.rb"
55
+ # #=> "path/to/run_script.rb"
56
+
57
+ def self.run_file_from run_file
58
+ run_file = File.join(run_file, "Sunshine") if
59
+ run_file && File.directory?(run_file)
60
+
61
+ run_file ||= "Sunshine"
62
+
63
+ File.expand_path run_file
64
+ end
65
+
66
+
67
+ ##
68
+ # Adds a directory to the ruby load path and runs the passed block.
69
+ # Useful for scripts to be able to reference their own dirs.
70
+
71
+ def self.with_load_path path
72
+ path = File.expand_path path
73
+
74
+ # TODO: Find a better way to make file path accessible to App objects.
75
+ Sunshine.send :remove_const, "PATH" if defined?(Sunshine::PATH)
76
+ Sunshine.const_set "PATH", path
77
+
78
+ added = unless $:.include? path
79
+ $: << path && true
80
+ end
81
+
82
+ yield
83
+
84
+ Sunshine.send :remove_const, "PATH"
85
+
86
+ $:.delete path if added
87
+ end
88
+
89
+
90
+ ##
91
+ # Returns file data in a run file as a File IO object.
92
+
93
+ def self.get_file_data run_file
94
+ # TODO: Find a better way to make file data accessible to App objects.
95
+ Sunshine.send :remove_const, "DATA" if defined?(Sunshine::DATA)
96
+ data_marker = "__END__\n"
97
+ line = nil
98
+
99
+ Sunshine.const_set("DATA", File.open(run_file, 'r'))
100
+
101
+ until line == data_marker || Sunshine::DATA.eof?
102
+ line = Sunshine::DATA.gets
103
+ end
104
+ end
105
+
106
+
107
+ ##
108
+ # Parses the argv passed to the command
109
+
110
+ def self.parse_args argv
111
+ options = {'trace' => true}
112
+
113
+ opts = opt_parser(options) do |opt|
114
+ opt.banner = <<-EOF
115
+
116
+ Usage: #{opt.program_name} run [run_file] [options]
117
+
118
+ Arguments:
119
+ run_file Load a script or app path. Defaults to ./Sunshine
120
+ EOF
121
+
122
+ opt.separator nil
123
+ opt.separator "Options:"
124
+
125
+ opt.on('-l', '--level LEVEL',
126
+ 'Set trace level. Defaults to info.') do |value|
127
+ options['level'] = value
128
+ end
129
+
130
+ opt.on('-e', '--env DEPLOY_ENV',
131
+ 'Sets the deploy env. Defaults to development.') do |value|
132
+ options['deploy_env'] = value
133
+ end
134
+
135
+ opt.on('-a', '--auto',
136
+ 'Non-interactive - automate or fail.') do
137
+ options['auto'] = true
138
+ end
139
+
140
+ opt.on('--no-trace',
141
+ "Don't trace any output.") do
142
+ options['trace'] = false
143
+ end
144
+ end
145
+
146
+ opts.parse! argv
147
+
148
+ options
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,72 @@
1
+ module Sunshine
2
+
3
+ ##
4
+ # Runs the start script of all specified sunshine apps.
5
+ #
6
+ # Usage: sunshine start app_name [more names...] [options]
7
+ #
8
+ # Arguments:
9
+ # app_name Name of the application to start.
10
+ #
11
+ # Options:
12
+ # -F, --force Stop apps that are running, then start them.
13
+ # -f, --format FORMAT Set the output format (txt, yml, json)
14
+ # -u, --user USER User to use for remote login. Use with -r.
15
+ # -r, --remote svr1,svr2 Run on one or more remote servers.
16
+ # -v, --verbose Run in verbose mode.
17
+
18
+ class StartCommand < ListCommand
19
+
20
+ ##
21
+ # Takes an array and a hash, runs the command and returns:
22
+ # true: success
23
+ # false: failed
24
+ # exitcode:
25
+ # code == 0: success
26
+ # code != 0: failed
27
+ # and optionally an accompanying message.
28
+
29
+ def self.exec names, config
30
+ force = config['force']
31
+
32
+ output = exec_each_server config do |shell|
33
+ new(shell).start(names, force)
34
+ end
35
+
36
+ return output
37
+ end
38
+
39
+
40
+ ##
41
+ # Start specified apps.
42
+
43
+ def start app_names, force=false
44
+ status_after_command :start, app_names do |server_app|
45
+
46
+ server_app.stop if server_app.running? && force
47
+ end
48
+ end
49
+
50
+
51
+ ##
52
+ # Parses the argv passed to the command
53
+
54
+ def self.parse_args argv
55
+ parse_remote_args(argv) do |opt, options|
56
+ opt.banner = <<-EOF
57
+
58
+ Usage: #{opt.program_name} start app_name [more names...] [options]
59
+
60
+ Arguments:
61
+ app_name Name of the application to start.
62
+ EOF
63
+
64
+ opt.on('-F', '--force',
65
+ 'Stop apps that are running before starting them again.') do
66
+ options['force'] = true
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+