sunshine 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +12 -0
- data/README.txt +3 -0
- data/lib/sunshine.rb +24 -5
- data/lib/sunshine/app.rb +25 -4
- data/lib/sunshine/dependencies.rb +1 -2
- data/lib/sunshine/remote_shell.rb +20 -14
- data/lib/sunshine/repo.rb +35 -19
- data/lib/sunshine/repos/rsync_repo.rb +1 -1
- data/lib/sunshine/server_app.rb +18 -2
- data/lib/sunshine/shell.rb +12 -4
- data/test/fixtures/app_configs/test_app.yml +1 -0
- data/test/helper_methods.rb +1 -1
- data/test/mocks/mock_open4.rb +4 -4
- data/test/test_helper.rb +1 -1
- data/test/unit/test_app.rb +7 -5
- data/test/unit/test_remote_shell.rb +6 -3
- data/test/unit/test_repo.rb +10 -2
- metadata +3 -3
data/History.txt
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
=== 1.1.1 / 2010-04-05
|
2
|
+
|
3
|
+
* Improvements:
|
4
|
+
|
5
|
+
* Added Repo subclass registration for greater expandability.
|
6
|
+
|
7
|
+
* Added support for checking out codebases locally then rsync-ing them.
|
8
|
+
|
9
|
+
* Bugfixes:
|
10
|
+
|
11
|
+
* Fixed RemoteShell login script lingering after disconnect.
|
12
|
+
|
1
13
|
=== 1.1.0 / 2010-04-02
|
2
14
|
|
3
15
|
* Improvements:
|
data/README.txt
CHANGED
@@ -492,6 +492,9 @@ defaults to :development.
|
|
492
492
|
'max_deploy_versions' -> The maximum number of deploys to keep on a server;
|
493
493
|
defaults to 5.
|
494
494
|
|
495
|
+
'remote_checkouts' -> Use remote servers to checkout the codebase;
|
496
|
+
defaults to false.
|
497
|
+
|
495
498
|
'require' -> Require external ruby libs or gems; defaults to nil.
|
496
499
|
|
497
500
|
'trace' -> Show detailed output messages; defaults to false.
|
data/lib/sunshine.rb
CHANGED
@@ -19,7 +19,7 @@ module Sunshine
|
|
19
19
|
|
20
20
|
##
|
21
21
|
# Sunshine version.
|
22
|
-
VERSION = '1.1.
|
22
|
+
VERSION = '1.1.1'
|
23
23
|
|
24
24
|
##
|
25
25
|
# Path to the list of installed sunshine apps.
|
@@ -41,7 +41,8 @@ module Sunshine
|
|
41
41
|
'auto' => false,
|
42
42
|
'max_deploy_versions' => 5,
|
43
43
|
'web_directory' => '/var/www',
|
44
|
-
'auto_dependencies' => true
|
44
|
+
'auto_dependencies' => true,
|
45
|
+
'remote_checkouts' => false
|
45
46
|
}
|
46
47
|
|
47
48
|
##
|
@@ -88,11 +89,20 @@ module Sunshine
|
|
88
89
|
@config['auto_dependencies']
|
89
90
|
end
|
90
91
|
|
91
|
-
##
|
92
|
-
# Returns the main Sunshine dependencies library.
|
93
92
|
|
94
|
-
|
93
|
+
##
|
94
|
+
# Returns the main Sunshine dependencies library. If passed a block,
|
95
|
+
# evaluates the block within the dependency lib instance:
|
96
|
+
#
|
97
|
+
# Sunshine.dependencies do
|
98
|
+
# yum 'new_dep'
|
99
|
+
# gem 'commander'
|
100
|
+
# end
|
101
|
+
|
102
|
+
def self.dependencies(&block)
|
95
103
|
@dependency_lib ||= DependencyLib.new
|
104
|
+
@dependency_lib.instance_eval(&block) if block_given?
|
105
|
+
@dependency_lib
|
96
106
|
end
|
97
107
|
|
98
108
|
|
@@ -140,6 +150,15 @@ module Sunshine
|
|
140
150
|
end
|
141
151
|
|
142
152
|
|
153
|
+
##
|
154
|
+
# Check if the codebase should be checked out remotely, or checked out
|
155
|
+
# locally and rsynced up. Overridden in the ~/.sunshine config file.
|
156
|
+
|
157
|
+
def self.remote_checkouts?
|
158
|
+
@config['remote_checkouts']
|
159
|
+
end
|
160
|
+
|
161
|
+
|
143
162
|
##
|
144
163
|
# Check if trace log should be output at all.
|
145
164
|
# This value can be assigned by default in ~/.sunshine
|
data/lib/sunshine/app.rb
CHANGED
@@ -151,6 +151,7 @@ module Sunshine
|
|
151
151
|
attr_reader :name, :repo, :server_apps, :sudo
|
152
152
|
attr_reader :root_path, :checkout_path, :current_path, :deploys_path
|
153
153
|
attr_reader :shared_path, :log_path, :deploy_name, :deploy_env
|
154
|
+
attr_accessor :remote_checkout
|
154
155
|
|
155
156
|
##
|
156
157
|
# App instantiation can be done in several ways:
|
@@ -183,6 +184,8 @@ module Sunshine
|
|
183
184
|
|
184
185
|
@server_apps = server_apps_from_config options[:remote_shells]
|
185
186
|
|
187
|
+
@remote_checkout = options[:remote_checkout] || Sunshine.remote_checkouts?
|
188
|
+
|
186
189
|
self.sudo = options[:sudo] || Sunshine.sudo
|
187
190
|
|
188
191
|
@shell_env = {
|
@@ -401,11 +404,27 @@ module Sunshine
|
|
401
404
|
|
402
405
|
##
|
403
406
|
# Checks out the app's codebase to one or all deploy servers.
|
407
|
+
# Supports all App#find options, plus:
|
408
|
+
# :copy:: Bool - Checkout locally and rsync; defaults to false.
|
404
409
|
|
405
410
|
def checkout_codebase options=nil
|
406
|
-
|
407
|
-
|
408
|
-
|
411
|
+
copy_option = options && options.has_key?(:copy) && options[:copy]
|
412
|
+
|
413
|
+
if @remote_checkout && !copy_option
|
414
|
+
with_server_apps options,
|
415
|
+
:msg => "Checking out codebase (remotely)",
|
416
|
+
:send => [:checkout_repo, @repo]
|
417
|
+
|
418
|
+
else
|
419
|
+
Sunshine.logger.info :app, "Checking out codebase (locally)" do
|
420
|
+
|
421
|
+
tmp_path = File.join Sunshine::TMP_DIR, "#{@name}_checkout"
|
422
|
+
scm_info = @repo.checkout_to tmp_path
|
423
|
+
|
424
|
+
with_server_apps options,
|
425
|
+
:send => [:upload_codebase, tmp_path, scm_info]
|
426
|
+
end
|
427
|
+
end
|
409
428
|
|
410
429
|
rescue => e
|
411
430
|
raise CriticalDeployError, e
|
@@ -691,7 +710,9 @@ module Sunshine
|
|
691
710
|
# See #after_user_script.
|
692
711
|
|
693
712
|
def run_post_user_lambdas
|
694
|
-
|
713
|
+
Sunshine.logger.info :app, "Running post deploy lambdas" do
|
714
|
+
@post_user_lambdas.each{|l| l.call self}
|
715
|
+
end
|
695
716
|
end
|
696
717
|
|
697
718
|
|
@@ -15,7 +15,8 @@ module Sunshine
|
|
15
15
|
|
16
16
|
##
|
17
17
|
# The loop to keep the ssh connection open.
|
18
|
-
LOGIN_LOOP = "echo
|
18
|
+
LOGIN_LOOP = "echo ok; echo ready; "+
|
19
|
+
"for (( ; ; )); do kill -0 $PPID && sleep 10 || exit; done;"
|
19
20
|
|
20
21
|
LOGIN_TIMEOUT = 30
|
21
22
|
|
@@ -38,7 +39,7 @@ module Sunshine
|
|
38
39
|
end
|
39
40
|
|
40
41
|
|
41
|
-
attr_reader :host, :user
|
42
|
+
attr_reader :host, :user, :pid
|
42
43
|
attr_accessor :ssh_flags, :rsync_flags
|
43
44
|
|
44
45
|
|
@@ -82,7 +83,7 @@ module Sunshine
|
|
82
83
|
|
83
84
|
def call command_str, options={}, &block
|
84
85
|
Sunshine.logger.info @host, "Running: #{command_str}" do
|
85
|
-
execute
|
86
|
+
execute build_remote_cmd(command_str, options), &block
|
86
87
|
end
|
87
88
|
end
|
88
89
|
|
@@ -91,11 +92,11 @@ module Sunshine
|
|
91
92
|
# Connect to host via SSH and return process pid
|
92
93
|
|
93
94
|
def connect
|
94
|
-
return
|
95
|
+
return true if connected?
|
95
96
|
|
96
|
-
cmd = ssh_cmd LOGIN_LOOP, :sudo => false
|
97
|
+
cmd = ssh_cmd quote_cmd(LOGIN_LOOP), :sudo => false
|
97
98
|
|
98
|
-
@pid, @inn, @out, @err = popen4
|
99
|
+
@pid, @inn, @out, @err = popen4 cmd.join(" ")
|
99
100
|
@inn.sync = true
|
100
101
|
|
101
102
|
data = ""
|
@@ -132,13 +133,11 @@ module Sunshine
|
|
132
133
|
# Disconnect from host
|
133
134
|
|
134
135
|
def disconnect
|
135
|
-
return unless connected?
|
136
|
-
|
137
136
|
@inn.close rescue nil
|
138
137
|
@out.close rescue nil
|
139
138
|
@err.close rescue nil
|
140
139
|
|
141
|
-
kill_process @pid, "HUP"
|
140
|
+
kill_process @pid, "HUP" rescue nil
|
142
141
|
|
143
142
|
@pid = nil
|
144
143
|
end
|
@@ -191,6 +190,17 @@ module Sunshine
|
|
191
190
|
end
|
192
191
|
|
193
192
|
|
193
|
+
##
|
194
|
+
# Builds an ssh command with permissions, env, etc.
|
195
|
+
|
196
|
+
def build_remote_cmd cmd, options={}
|
197
|
+
cmd = sh_cmd cmd
|
198
|
+
cmd = env_cmd cmd
|
199
|
+
cmd = sudo_cmd cmd, options
|
200
|
+
cmd = ssh_cmd cmd, options
|
201
|
+
end
|
202
|
+
|
203
|
+
|
194
204
|
##
|
195
205
|
# Uploads a file via rsync
|
196
206
|
|
@@ -237,13 +247,9 @@ module Sunshine
|
|
237
247
|
##
|
238
248
|
# Wraps the command in an ssh call.
|
239
249
|
|
240
|
-
def ssh_cmd
|
250
|
+
def ssh_cmd cmd, options=nil
|
241
251
|
options ||= {}
|
242
252
|
|
243
|
-
cmd = sh_cmd string
|
244
|
-
cmd = env_cmd cmd
|
245
|
-
cmd = sudo_cmd cmd, options
|
246
|
-
|
247
253
|
flags = [*options[:flags]].concat @ssh_flags
|
248
254
|
|
249
255
|
["ssh", flags, @host, cmd].flatten.compact
|
data/lib/sunshine/repo.rb
CHANGED
@@ -13,14 +13,33 @@ module Sunshine
|
|
13
13
|
|
14
14
|
class Repo
|
15
15
|
|
16
|
+
##
|
17
|
+
# Adds subclasses to a repo_types hash for easy
|
18
|
+
|
19
|
+
def self.inherited subclass
|
20
|
+
@@repo_types ||= {}
|
21
|
+
|
22
|
+
# Turn Sunshine::ScmNameRepo into :scm_name
|
23
|
+
class_key = subclass.to_s.split("::").last
|
24
|
+
class_key = $1 if class_key =~ /(\w+)Repo$/
|
25
|
+
class_key.gsub! /([a-z0-9])([A-Z])/, '\1_\2'
|
26
|
+
class_key = class_key.downcase
|
27
|
+
|
28
|
+
@@repo_types[class_key] = subclass
|
29
|
+
end
|
30
|
+
|
31
|
+
|
16
32
|
##
|
17
33
|
# Creates a new repo subclass object:
|
18
34
|
# Repo.new_of_type :svn, "https://path/to/repo/tags/releasetag"
|
19
35
|
# Repo.new_of_type :git, "user@gitbox.com:repo/path"
|
20
36
|
|
21
37
|
def self.new_of_type repo_type, url, options={}
|
22
|
-
|
23
|
-
|
38
|
+
repo_class = @@repo_types[repo_type.to_s]
|
39
|
+
|
40
|
+
raise RepoError, "Invalid type #{repo_type.inspect}" unless repo_class
|
41
|
+
|
42
|
+
repo_class.new(url, options)
|
24
43
|
end
|
25
44
|
|
26
45
|
|
@@ -36,15 +55,16 @@ module Sunshine
|
|
36
55
|
# #=> nil
|
37
56
|
|
38
57
|
def self.detect path=".", shell=nil
|
58
|
+
@@repo_types.values.each do |repo|
|
59
|
+
next if Sunshine::RsyncRepo === repo
|
39
60
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
elsif GitRepo.valid? path
|
45
|
-
info = GitRepo.get_info path, shell
|
46
|
-
GitRepo.new info[:url], info
|
61
|
+
if repo.valid? path
|
62
|
+
info = repo.get_info path, shell
|
63
|
+
return repo.new(info[:url], info)
|
64
|
+
end
|
47
65
|
end
|
66
|
+
|
67
|
+
nil
|
48
68
|
end
|
49
69
|
|
50
70
|
|
@@ -69,21 +89,17 @@ module Sunshine
|
|
69
89
|
|
70
90
|
##
|
71
91
|
# Checkout code to a shell and return an info log hash:
|
72
|
-
# repo.chekout_to
|
92
|
+
# repo.chekout_to "some/path", remote_shell
|
73
93
|
# #=> {:revision => 123, :committer => 'someone', :date => time_obj ...}
|
74
94
|
|
75
95
|
def checkout_to path, shell=nil
|
76
96
|
shell ||= Sunshine.shell
|
77
97
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
shell.call "test -d #{path} && rm -rf #{path} || echo false"
|
82
|
-
shell.call "mkdir -p #{path}"
|
98
|
+
shell.call "test -d #{path} && rm -rf #{path} || echo false"
|
99
|
+
shell.call "mkdir -p #{path}"
|
83
100
|
|
84
|
-
|
85
|
-
|
86
|
-
end
|
101
|
+
do_checkout path, shell
|
102
|
+
get_repo_info path, shell
|
87
103
|
end
|
88
104
|
|
89
105
|
|
@@ -97,7 +113,7 @@ module Sunshine
|
|
97
113
|
|
98
114
|
|
99
115
|
##
|
100
|
-
# Get the name of the specified repo - implemented by subclass
|
116
|
+
# Get the project name of the specified repo - implemented by subclass
|
101
117
|
|
102
118
|
def name
|
103
119
|
raise RepoError,
|
data/lib/sunshine/server_app.rb
CHANGED
@@ -145,9 +145,15 @@ module Sunshine
|
|
145
145
|
##
|
146
146
|
# Checks out the app's codebase to the checkout path.
|
147
147
|
|
148
|
-
def checkout_repo repo
|
148
|
+
def checkout_repo repo, scm_info={}
|
149
149
|
install_deps repo.scm
|
150
|
-
|
150
|
+
|
151
|
+
Sunshine.logger.info repo.scm,
|
152
|
+
"Checking out to #{@shell.host} #{self.checkout_path}" do
|
153
|
+
|
154
|
+
@info[:scm] = repo.checkout_to self.checkout_path, @shell
|
155
|
+
@info[:scm].merge! scm_info
|
156
|
+
end
|
151
157
|
end
|
152
158
|
|
153
159
|
|
@@ -480,6 +486,16 @@ fi
|
|
480
486
|
end
|
481
487
|
|
482
488
|
|
489
|
+
##
|
490
|
+
# Assumes the passed code_dir is the root directory of the checked out
|
491
|
+
# codebase and uploads it to the checkout_path.
|
492
|
+
|
493
|
+
def upload_codebase code_dir, scm_info={}
|
494
|
+
RsyncRepo.new(code_dir).checkout_to self.checkout_path, @shell
|
495
|
+
@info[:scm] = scm_info
|
496
|
+
end
|
497
|
+
|
498
|
+
|
483
499
|
##
|
484
500
|
# Upload common rake tasks from a local path or the sunshine lib.
|
485
501
|
# app.upload_tasks
|
data/lib/sunshine/shell.rb
CHANGED
@@ -172,12 +172,19 @@ module Sunshine
|
|
172
172
|
|
173
173
|
|
174
174
|
##
|
175
|
-
#
|
175
|
+
# Wrap command in quotes and escape as needed.
|
176
|
+
|
177
|
+
def quote_cmd cmd
|
178
|
+
cmd = [*cmd].join(" ")
|
179
|
+
"'#{cmd.gsub(/'/){|s| "'\\''"}}'"
|
180
|
+
end
|
176
181
|
|
177
|
-
def sh_cmd string
|
178
|
-
string = string.gsub(/'/){|s| "'\\''"}
|
179
182
|
|
180
|
-
|
183
|
+
##
|
184
|
+
# Build an sh -c command
|
185
|
+
|
186
|
+
def sh_cmd cmd
|
187
|
+
["sh", "-c", quote_cmd(cmd)]
|
181
188
|
end
|
182
189
|
|
183
190
|
|
@@ -340,6 +347,7 @@ module Sunshine
|
|
340
347
|
"Execution failed with status #{status.exitstatus}: #{[*cmd].join ' '}"
|
341
348
|
end
|
342
349
|
|
350
|
+
|
343
351
|
def password_required? stream_name, data
|
344
352
|
stream_name == :err && data =~ SUDO_PROMPT
|
345
353
|
end
|
data/test/helper_methods.rb
CHANGED
@@ -107,7 +107,7 @@ fi
|
|
107
107
|
|
108
108
|
|
109
109
|
def assert_ssh_call expected, ds=@remote_shell, options={}
|
110
|
-
expected = ds.
|
110
|
+
expected = ds.build_remote_cmd(expected, options).join(" ")
|
111
111
|
|
112
112
|
error_msg = "No such command in remote_shell log [#{ds.host}]\n#{expected}"
|
113
113
|
error_msg << "\n\n#{ds.cmd_log.select{|c| c =~ /^ssh/}.join("\n\n")}"
|
data/test/mocks/mock_open4.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
module MockOpen4
|
2
2
|
|
3
|
-
LOGIN_CMD = "echo ready;"
|
4
|
-
|
5
3
|
CMD_RETURN = {
|
6
|
-
|
4
|
+
Sunshine::RemoteShell::LOGIN_LOOP => [:out, "ready\n"]
|
7
5
|
}
|
8
6
|
|
9
7
|
attr_reader :cmd_log
|
@@ -68,7 +66,9 @@ module MockOpen4
|
|
68
66
|
next
|
69
67
|
end
|
70
68
|
|
71
|
-
|
69
|
+
if Sunshine::RemoteShell === self
|
70
|
+
key = build_remote_cmd(key, options).join(" ")
|
71
|
+
end
|
72
72
|
|
73
73
|
new_stream_vals[key] = (val.dup << code)
|
74
74
|
end
|
data/test/test_helper.rb
CHANGED
data/test/unit/test_app.rb
CHANGED
@@ -6,10 +6,12 @@ class TestApp < Test::Unit::TestCase
|
|
6
6
|
mock_remote_shell_popen4
|
7
7
|
@svn_url = "svn://subversion/path/to/app_name/trunk"
|
8
8
|
|
9
|
-
@config = {
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
@config = {
|
10
|
+
:name => "app_name",
|
11
|
+
:remote_checkout => true,
|
12
|
+
:repo => {:type => "svn", :url => @svn_url},
|
13
|
+
:remote_shells => ["user@some_server.com"],
|
14
|
+
:root_path => "/usr/local/my_user/app_name"}
|
13
15
|
|
14
16
|
@app = Sunshine::App.new @config
|
15
17
|
@app.each do |server_app|
|
@@ -277,7 +279,7 @@ class TestApp < Test::Unit::TestCase
|
|
277
279
|
|
278
280
|
state = true
|
279
281
|
@app.server_apps.each do |sa|
|
280
|
-
assert sa.method_called?
|
282
|
+
assert sa.method_called?(:deployed?)
|
281
283
|
|
282
284
|
set_mock_response_for sa.shell, 0,
|
283
285
|
"cat #{@app.current_path}/info" => [:out,
|
@@ -17,8 +17,11 @@ class TestRemoteShell < Test::Unit::TestCase
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_connect
|
20
|
-
|
21
|
-
|
20
|
+
login_cmd = Sunshine::RemoteShell::LOGIN_LOOP
|
21
|
+
login_cmd = @remote_shell.send :quote_cmd, login_cmd
|
22
|
+
login_cmd = @remote_shell.send :ssh_cmd, login_cmd, :sudo => false
|
23
|
+
|
24
|
+
assert @remote_shell.method_called?(:popen4, :args => [login_cmd.join(" ")])
|
22
25
|
assert @remote_shell.connected?
|
23
26
|
end
|
24
27
|
|
@@ -42,7 +45,7 @@ class TestRemoteShell < Test::Unit::TestCase
|
|
42
45
|
@remote_shell.call cmd
|
43
46
|
raise "Didn't raise CmdError on stderr"
|
44
47
|
rescue Sunshine::CmdError => e
|
45
|
-
ssh_cmd = @remote_shell.
|
48
|
+
ssh_cmd = @remote_shell.build_remote_cmd(cmd).join(" ")
|
46
49
|
assert_equal "Execution failed with status 1: #{ssh_cmd}", e.message
|
47
50
|
end
|
48
51
|
|
data/test/unit/test_repo.rb
CHANGED
@@ -13,8 +13,16 @@ class TestRepo < Test::Unit::TestCase
|
|
13
13
|
assert_equal Sunshine::SvnRepo, repo.class
|
14
14
|
assert_equal @svn_url, repo.url
|
15
15
|
|
16
|
-
repo = Sunshine::Repo.new_of_type
|
17
|
-
assert_equal Sunshine::
|
16
|
+
repo = Sunshine::Repo.new_of_type 'git', @svn_url
|
17
|
+
assert_equal Sunshine::GitRepo, repo.class
|
18
|
+
assert_equal @svn_url, repo.url
|
19
|
+
|
20
|
+
begin
|
21
|
+
repo = Sunshine::Repo.new_of_type "", @svn_url
|
22
|
+
raise "Didn't raise RepoError for invalid repo type"
|
23
|
+
rescue Sunshine::RepoError => e
|
24
|
+
assert_equal "Invalid type \"\"", e.message
|
25
|
+
end
|
18
26
|
end
|
19
27
|
|
20
28
|
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 1
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 1.1.
|
8
|
+
- 1
|
9
|
+
version: 1.1.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Jeremie Castagna
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-04-
|
17
|
+
date: 2010-04-06 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|