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.
@@ -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.
@@ -19,7 +19,7 @@ module Sunshine
19
19
 
20
20
  ##
21
21
  # Sunshine version.
22
- VERSION = '1.1.0'
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
- def self.dependencies
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
@@ -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
- with_server_apps options,
407
- :msg => "Checking out codebase",
408
- :send => [:checkout_repo, @repo]
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
- @post_user_lambdas.each{|l| l.call self}
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
 
@@ -1,8 +1,7 @@
1
1
  ##
2
2
  # Defines Sunshine deploy server dependencies.
3
3
 
4
- #class Sunshine::Dependencies < Settler
5
- Sunshine.dependencies.instance_eval do
4
+ Sunshine.dependencies do
6
5
 
7
6
  apt 'svn', :pkg => 'subversion'
8
7
  yum 'svn', :pkg => 'subversion'
@@ -15,7 +15,8 @@ module Sunshine
15
15
 
16
16
  ##
17
17
  # The loop to keep the ssh connection open.
18
- LOGIN_LOOP = "echo connected; echo ready; for (( ; ; )); do sleep 10; done"
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 ssh_cmd(command_str, options), &block
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 @pid if connected?
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(cmd.join(" "))
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 string, options=nil
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
@@ -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
- repo = "#{repo_type.to_s.capitalize}Repo"
23
- Sunshine.const_get(repo).new(url, options)
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
- if SvnRepo.valid? path
41
- info = SvnRepo.get_info path, shell
42
- SvnRepo.new info[:url], info
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 server, "some/path"
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
- Sunshine.logger.info @scm,
79
- "Checking out to #{shell.host} #{path}" do
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
- do_checkout path, shell
85
- get_repo_info path, shell
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,
@@ -12,7 +12,7 @@ module Sunshine
12
12
 
13
13
  def initialize url, options={}
14
14
  super
15
- @flags << "-r"
15
+ @flags << '-r' << '--exclude .svn/' << '--exclude .git/'
16
16
  @url << "/" unless @url[-1..-1] == "/"
17
17
  end
18
18
 
@@ -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
- @info[:scm] = repo.checkout_to self.checkout_path, @shell
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
@@ -172,12 +172,19 @@ module Sunshine
172
172
 
173
173
 
174
174
  ##
175
- # Build an sh -c command
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
- ["sh", "-c", "'#{string}'"]
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
@@ -1,5 +1,6 @@
1
1
  :default:
2
2
  :name: other_app
3
+ :remote_checkout: true
3
4
  :repo:
4
5
  :type: svn
5
6
  :url: svn://subversion/path/to/other_app/tags/release001
@@ -107,7 +107,7 @@ fi
107
107
 
108
108
 
109
109
  def assert_ssh_call expected, ds=@remote_shell, options={}
110
- expected = ds.send(:ssh_cmd, expected, options).join(" ")
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")}"
@@ -1,9 +1,7 @@
1
1
  module MockOpen4
2
2
 
3
- LOGIN_CMD = "echo ready;"
4
-
5
3
  CMD_RETURN = {
6
- LOGIN_CMD => [:out, "ready\n"]
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
- key = ssh_cmd(key, options).join(" ") if Sunshine::RemoteShell === self
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
@@ -13,7 +13,7 @@ unless defined? TEST_APP_CONFIG_FILE
13
13
  end
14
14
 
15
15
 
16
- Sunshine.setup({}, true)
16
+ Sunshine.setup({'remote_checkouts' => true}, true)
17
17
 
18
18
  unless MockObject === Sunshine.shell
19
19
  Sunshine.shell.extend MockObject
@@ -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 = {:name => "app_name",
10
- :repo => {:type => "svn", :url => @svn_url},
11
- :remote_shells => ["user@some_server.com"],
12
- :root_path => "/usr/local/my_user/app_name"}
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? :deployed?
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
- assert_ssh_call \
21
- "echo connected; echo ready; for (( ; ; )); do sleep 10; done"
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.send(:ssh_cmd, cmd).join(" ")
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
 
@@ -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 "", @svn_url
17
- assert_equal Sunshine::Repo, repo.class
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
- - 0
9
- version: 1.1.0
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-02 00:00:00 -07:00
17
+ date: 2010-04-06 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency