sunshine 1.1.0 → 1.1.1
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.
- 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
|