captivate 0.0.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.
Files changed (163) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +79 -0
  5. data/Rakefile +45 -0
  6. data/VERSION +1 -0
  7. data/bin/captivate +4 -0
  8. data/captivate-0.0.0.gem +0 -0
  9. data/captivate.gemspec +263 -0
  10. data/lib/captivate.rb +199 -0
  11. data/spec/acceptance_spec.rb +55 -0
  12. data/spec/captivate_spec.rb +269 -0
  13. data/spec/fixtures/rails_apps/multi_stage/Capfile +4 -0
  14. data/spec/fixtures/rails_apps/multi_stage/README +243 -0
  15. data/spec/fixtures/rails_apps/multi_stage/Rakefile +10 -0
  16. data/spec/fixtures/rails_apps/multi_stage/app/controllers/application_controller.rb +10 -0
  17. data/spec/fixtures/rails_apps/multi_stage/app/helpers/application_helper.rb +3 -0
  18. data/spec/fixtures/rails_apps/multi_stage/config/boot.rb +110 -0
  19. data/spec/fixtures/rails_apps/multi_stage/config/captivate.yml +4 -0
  20. data/spec/fixtures/rails_apps/multi_stage/config/database.yml +22 -0
  21. data/spec/fixtures/rails_apps/multi_stage/config/deploy.rb +27 -0
  22. data/spec/fixtures/rails_apps/multi_stage/config/deploy/staging.rb +0 -0
  23. data/spec/fixtures/rails_apps/multi_stage/config/environment.rb +41 -0
  24. data/spec/fixtures/rails_apps/multi_stage/config/environments/development.rb +17 -0
  25. data/spec/fixtures/rails_apps/multi_stage/config/environments/production.rb +28 -0
  26. data/spec/fixtures/rails_apps/multi_stage/config/environments/test.rb +28 -0
  27. data/spec/fixtures/rails_apps/multi_stage/config/initializers/backtrace_silencers.rb +7 -0
  28. data/spec/fixtures/rails_apps/multi_stage/config/initializers/inflections.rb +10 -0
  29. data/spec/fixtures/rails_apps/multi_stage/config/initializers/mime_types.rb +5 -0
  30. data/spec/fixtures/rails_apps/multi_stage/config/initializers/new_rails_defaults.rb +21 -0
  31. data/spec/fixtures/rails_apps/multi_stage/config/initializers/session_store.rb +15 -0
  32. data/spec/fixtures/rails_apps/multi_stage/config/locales/en.yml +5 -0
  33. data/spec/fixtures/rails_apps/multi_stage/config/routes.rb +43 -0
  34. data/spec/fixtures/rails_apps/multi_stage/db/seeds.rb +7 -0
  35. data/spec/fixtures/rails_apps/multi_stage/doc/README_FOR_APP +2 -0
  36. data/spec/fixtures/rails_apps/multi_stage/log/development.log +0 -0
  37. data/spec/fixtures/rails_apps/multi_stage/log/production.log +0 -0
  38. data/spec/fixtures/rails_apps/multi_stage/log/server.log +0 -0
  39. data/spec/fixtures/rails_apps/multi_stage/log/test.log +0 -0
  40. data/spec/fixtures/rails_apps/multi_stage/public/404.html +30 -0
  41. data/spec/fixtures/rails_apps/multi_stage/public/422.html +30 -0
  42. data/spec/fixtures/rails_apps/multi_stage/public/500.html +30 -0
  43. data/spec/fixtures/rails_apps/multi_stage/public/favicon.ico +0 -0
  44. data/spec/fixtures/rails_apps/multi_stage/public/images/rails.png +0 -0
  45. data/spec/fixtures/rails_apps/multi_stage/public/index.html +275 -0
  46. data/spec/fixtures/rails_apps/multi_stage/public/javascripts/application.js +2 -0
  47. data/spec/fixtures/rails_apps/multi_stage/public/javascripts/controls.js +963 -0
  48. data/spec/fixtures/rails_apps/multi_stage/public/javascripts/dragdrop.js +973 -0
  49. data/spec/fixtures/rails_apps/multi_stage/public/javascripts/effects.js +1128 -0
  50. data/spec/fixtures/rails_apps/multi_stage/public/javascripts/prototype.js +4320 -0
  51. data/spec/fixtures/rails_apps/multi_stage/public/robots.txt +5 -0
  52. data/spec/fixtures/rails_apps/multi_stage/script/about +4 -0
  53. data/spec/fixtures/rails_apps/multi_stage/script/console +3 -0
  54. data/spec/fixtures/rails_apps/multi_stage/script/dbconsole +3 -0
  55. data/spec/fixtures/rails_apps/multi_stage/script/destroy +3 -0
  56. data/spec/fixtures/rails_apps/multi_stage/script/generate +3 -0
  57. data/spec/fixtures/rails_apps/multi_stage/script/performance/benchmarker +3 -0
  58. data/spec/fixtures/rails_apps/multi_stage/script/performance/profiler +3 -0
  59. data/spec/fixtures/rails_apps/multi_stage/script/plugin +3 -0
  60. data/spec/fixtures/rails_apps/multi_stage/script/runner +3 -0
  61. data/spec/fixtures/rails_apps/multi_stage/script/server +3 -0
  62. data/spec/fixtures/rails_apps/multi_stage/test/performance/browsing_test.rb +9 -0
  63. data/spec/fixtures/rails_apps/multi_stage/test/test_helper.rb +38 -0
  64. data/spec/fixtures/rails_apps/no_cap/README +243 -0
  65. data/spec/fixtures/rails_apps/no_cap/Rakefile +10 -0
  66. data/spec/fixtures/rails_apps/no_cap/app/controllers/application_controller.rb +10 -0
  67. data/spec/fixtures/rails_apps/no_cap/app/helpers/application_helper.rb +3 -0
  68. data/spec/fixtures/rails_apps/no_cap/config/boot.rb +110 -0
  69. data/spec/fixtures/rails_apps/no_cap/config/database.yml +22 -0
  70. data/spec/fixtures/rails_apps/no_cap/config/environment.rb +41 -0
  71. data/spec/fixtures/rails_apps/no_cap/config/environments/development.rb +17 -0
  72. data/spec/fixtures/rails_apps/no_cap/config/environments/production.rb +28 -0
  73. data/spec/fixtures/rails_apps/no_cap/config/environments/test.rb +28 -0
  74. data/spec/fixtures/rails_apps/no_cap/config/initializers/backtrace_silencers.rb +7 -0
  75. data/spec/fixtures/rails_apps/no_cap/config/initializers/inflections.rb +10 -0
  76. data/spec/fixtures/rails_apps/no_cap/config/initializers/mime_types.rb +5 -0
  77. data/spec/fixtures/rails_apps/no_cap/config/initializers/new_rails_defaults.rb +21 -0
  78. data/spec/fixtures/rails_apps/no_cap/config/initializers/session_store.rb +15 -0
  79. data/spec/fixtures/rails_apps/no_cap/config/locales/en.yml +5 -0
  80. data/spec/fixtures/rails_apps/no_cap/config/routes.rb +43 -0
  81. data/spec/fixtures/rails_apps/no_cap/db/seeds.rb +7 -0
  82. data/spec/fixtures/rails_apps/no_cap/doc/README_FOR_APP +2 -0
  83. data/spec/fixtures/rails_apps/no_cap/log/development.log +0 -0
  84. data/spec/fixtures/rails_apps/no_cap/log/production.log +0 -0
  85. data/spec/fixtures/rails_apps/no_cap/log/server.log +0 -0
  86. data/spec/fixtures/rails_apps/no_cap/log/test.log +0 -0
  87. data/spec/fixtures/rails_apps/no_cap/public/404.html +30 -0
  88. data/spec/fixtures/rails_apps/no_cap/public/422.html +30 -0
  89. data/spec/fixtures/rails_apps/no_cap/public/500.html +30 -0
  90. data/spec/fixtures/rails_apps/no_cap/public/favicon.ico +0 -0
  91. data/spec/fixtures/rails_apps/no_cap/public/images/rails.png +0 -0
  92. data/spec/fixtures/rails_apps/no_cap/public/index.html +275 -0
  93. data/spec/fixtures/rails_apps/no_cap/public/javascripts/application.js +2 -0
  94. data/spec/fixtures/rails_apps/no_cap/public/javascripts/controls.js +963 -0
  95. data/spec/fixtures/rails_apps/no_cap/public/javascripts/dragdrop.js +973 -0
  96. data/spec/fixtures/rails_apps/no_cap/public/javascripts/effects.js +1128 -0
  97. data/spec/fixtures/rails_apps/no_cap/public/javascripts/prototype.js +4320 -0
  98. data/spec/fixtures/rails_apps/no_cap/public/robots.txt +5 -0
  99. data/spec/fixtures/rails_apps/no_cap/script/about +4 -0
  100. data/spec/fixtures/rails_apps/no_cap/script/console +3 -0
  101. data/spec/fixtures/rails_apps/no_cap/script/dbconsole +3 -0
  102. data/spec/fixtures/rails_apps/no_cap/script/destroy +3 -0
  103. data/spec/fixtures/rails_apps/no_cap/script/generate +3 -0
  104. data/spec/fixtures/rails_apps/no_cap/script/performance/benchmarker +3 -0
  105. data/spec/fixtures/rails_apps/no_cap/script/performance/profiler +3 -0
  106. data/spec/fixtures/rails_apps/no_cap/script/plugin +3 -0
  107. data/spec/fixtures/rails_apps/no_cap/script/runner +3 -0
  108. data/spec/fixtures/rails_apps/no_cap/script/server +3 -0
  109. data/spec/fixtures/rails_apps/no_cap/test/performance/browsing_test.rb +9 -0
  110. data/spec/fixtures/rails_apps/no_cap/test/test_helper.rb +38 -0
  111. data/spec/fixtures/rails_apps/single_stage/Capfile +4 -0
  112. data/spec/fixtures/rails_apps/single_stage/README +243 -0
  113. data/spec/fixtures/rails_apps/single_stage/Rakefile +10 -0
  114. data/spec/fixtures/rails_apps/single_stage/app/controllers/application_controller.rb +10 -0
  115. data/spec/fixtures/rails_apps/single_stage/app/helpers/application_helper.rb +3 -0
  116. data/spec/fixtures/rails_apps/single_stage/config/boot.rb +110 -0
  117. data/spec/fixtures/rails_apps/single_stage/config/captivate.yml +2 -0
  118. data/spec/fixtures/rails_apps/single_stage/config/database.yml +22 -0
  119. data/spec/fixtures/rails_apps/single_stage/config/deploy.rb +24 -0
  120. data/spec/fixtures/rails_apps/single_stage/config/environment.rb +41 -0
  121. data/spec/fixtures/rails_apps/single_stage/config/environments/development.rb +17 -0
  122. data/spec/fixtures/rails_apps/single_stage/config/environments/production.rb +28 -0
  123. data/spec/fixtures/rails_apps/single_stage/config/environments/test.rb +28 -0
  124. data/spec/fixtures/rails_apps/single_stage/config/initializers/backtrace_silencers.rb +7 -0
  125. data/spec/fixtures/rails_apps/single_stage/config/initializers/inflections.rb +10 -0
  126. data/spec/fixtures/rails_apps/single_stage/config/initializers/mime_types.rb +5 -0
  127. data/spec/fixtures/rails_apps/single_stage/config/initializers/new_rails_defaults.rb +21 -0
  128. data/spec/fixtures/rails_apps/single_stage/config/initializers/session_store.rb +15 -0
  129. data/spec/fixtures/rails_apps/single_stage/config/locales/en.yml +5 -0
  130. data/spec/fixtures/rails_apps/single_stage/config/routes.rb +43 -0
  131. data/spec/fixtures/rails_apps/single_stage/db/seeds.rb +7 -0
  132. data/spec/fixtures/rails_apps/single_stage/doc/README_FOR_APP +2 -0
  133. data/spec/fixtures/rails_apps/single_stage/log/development.log +0 -0
  134. data/spec/fixtures/rails_apps/single_stage/log/production.log +0 -0
  135. data/spec/fixtures/rails_apps/single_stage/log/server.log +0 -0
  136. data/spec/fixtures/rails_apps/single_stage/log/test.log +0 -0
  137. data/spec/fixtures/rails_apps/single_stage/public/404.html +30 -0
  138. data/spec/fixtures/rails_apps/single_stage/public/422.html +30 -0
  139. data/spec/fixtures/rails_apps/single_stage/public/500.html +30 -0
  140. data/spec/fixtures/rails_apps/single_stage/public/favicon.ico +0 -0
  141. data/spec/fixtures/rails_apps/single_stage/public/images/rails.png +0 -0
  142. data/spec/fixtures/rails_apps/single_stage/public/index.html +275 -0
  143. data/spec/fixtures/rails_apps/single_stage/public/javascripts/application.js +2 -0
  144. data/spec/fixtures/rails_apps/single_stage/public/javascripts/controls.js +963 -0
  145. data/spec/fixtures/rails_apps/single_stage/public/javascripts/dragdrop.js +973 -0
  146. data/spec/fixtures/rails_apps/single_stage/public/javascripts/effects.js +1128 -0
  147. data/spec/fixtures/rails_apps/single_stage/public/javascripts/prototype.js +4320 -0
  148. data/spec/fixtures/rails_apps/single_stage/public/robots.txt +5 -0
  149. data/spec/fixtures/rails_apps/single_stage/script/about +4 -0
  150. data/spec/fixtures/rails_apps/single_stage/script/console +3 -0
  151. data/spec/fixtures/rails_apps/single_stage/script/dbconsole +3 -0
  152. data/spec/fixtures/rails_apps/single_stage/script/destroy +3 -0
  153. data/spec/fixtures/rails_apps/single_stage/script/generate +3 -0
  154. data/spec/fixtures/rails_apps/single_stage/script/performance/benchmarker +3 -0
  155. data/spec/fixtures/rails_apps/single_stage/script/performance/profiler +3 -0
  156. data/spec/fixtures/rails_apps/single_stage/script/plugin +3 -0
  157. data/spec/fixtures/rails_apps/single_stage/script/runner +3 -0
  158. data/spec/fixtures/rails_apps/single_stage/script/server +3 -0
  159. data/spec/fixtures/rails_apps/single_stage/test/performance/browsing_test.rb +9 -0
  160. data/spec/fixtures/rails_apps/single_stage/test/test_helper.rb +38 -0
  161. data/spec/spec.opts +1 -0
  162. data/spec/spec_helper.rb +8 -0
  163. metadata +294 -0
@@ -0,0 +1,199 @@
1
+ require 'tempfile'
2
+ module Captivate
3
+ class Command
4
+ attr_reader :path
5
+
6
+ def initialize(args = nil)
7
+ @args = args
8
+ end
9
+
10
+ def invoke
11
+ config = Config.new(config_options).to_hash
12
+ @host = config['host']
13
+ @path = config['working_path']
14
+ @remote_dir = RemoteDir.new(:host => @host, :path => @path)
15
+ @remote_dir.upload('Capfile')
16
+ @remote_config_dir = @remote_dir.new_subdir('config')
17
+ @remote_config_dir.upload('config/deploy.rb')
18
+ @remote_config_dir.upload('config/deploy') if File.directory?('config/deploy')
19
+ @remote_script = RemoteScript.new(:command => command, :remote_path => @path)
20
+ remote_script_path = @remote_script.local_path
21
+ File.chmod(0555, remote_script_path)
22
+ @remote_dir.upload(remote_script_path, 'captivate-remote.sh')
23
+ SystemCall.new(%{ssh -t #{@host} screen #{@path}/captivate-remote.sh})
24
+ end
25
+
26
+ private
27
+
28
+ def self.remote_script_path
29
+ File.join(File.dirname(__FILE__), '/../bin/captivate-remote.sh')
30
+ end
31
+
32
+ def command
33
+ @args * ' '
34
+ end
35
+
36
+ def config_options
37
+ {:command => command, :rails_root => rails_root}
38
+ end
39
+
40
+ def rails_root
41
+ FileUtils.pwd
42
+ end
43
+ end
44
+
45
+ class Config
46
+ def initialize(options = {})
47
+ @command = options[:command] or raise(ConfigError, "Missing 'command' option")
48
+ @rails_root = options[:rails_root] or raise(ConfigError, "Missing 'rails_root' option")
49
+ end
50
+
51
+ def to_hash
52
+ validate_yaml_config
53
+ { 'working_path' => working_path }.merge(yaml_config[environment])
54
+ end
55
+
56
+ private
57
+
58
+ def working_path
59
+ '/tmp/captivate.%s' % [timestamp, app_name, environment, user].join('.')
60
+ end
61
+
62
+ def timestamp
63
+ Time.now.to_i
64
+ end
65
+
66
+ def app_name
67
+ File.basename(@rails_root)
68
+ end
69
+
70
+ def environment
71
+ if config_stages.include?(commands.first) && multistage_stages.include?(commands.first)
72
+ commands.first
73
+ else
74
+ 'production'
75
+ end
76
+ end
77
+
78
+ def config_stages
79
+ yaml_config.keys
80
+ end
81
+
82
+ def multistage_stages
83
+ if multistage?
84
+ set_stages_code = deploy_file_contents[/set :stages,\s?([^\n]+)\n/, 1] or
85
+ raise(ConfigError, "'set :stages' definition not found")
86
+ stages_array = eval(set_stages_code)
87
+ else
88
+ []
89
+ end
90
+ end
91
+
92
+ def deploy_file_contents
93
+ File.read(deploy_file_path)
94
+ end
95
+
96
+ def deploy_file_path
97
+ "#{@rails_root}/config/deploy.rb"
98
+ end
99
+
100
+ def multistage?
101
+ deploy_file_contents.match(%r(\A\s*require (['"])capistrano/ext/multistage\1)) ||
102
+ deploy_file_contents.match(%r(\n\s*require (['"])capistrano/ext/multistage\1))
103
+ end
104
+
105
+ def commands
106
+ @command.split(/\s+/)
107
+ end
108
+
109
+ def user
110
+ Etc.getlogin
111
+ end
112
+
113
+ def validate_yaml_config
114
+ raise(ConfigError, yaml_config_path) unless File.exists?(yaml_config_path)
115
+ raise(ConfigError, "Missing #{environment} environment") unless yaml_config.keys.include?(environment)
116
+ unless yaml_config[environment].keys.include?('host')
117
+ raise(ConfigError, "Missing 'host' for #{environment} environment")
118
+ end
119
+ end
120
+
121
+ def yaml_config
122
+ @yaml_config ||= YAML.load_file(yaml_config_path)
123
+ end
124
+
125
+ def yaml_config_path
126
+ File.join(@rails_root, 'config/captivate.yml')
127
+ end
128
+ end
129
+
130
+ class RemoteDir
131
+ def initialize(options)
132
+ @host, @path = options[:host], options[:path]
133
+ SystemCall.new("ssh #{@host} mkdir #{@path}")
134
+ end
135
+
136
+ def upload(file_path, dest_filename = file_path)
137
+ raise(Errno::ENOENT, file_path) unless File.exists?(file_path)
138
+ if File.directory?(file_path)
139
+ SystemCall.new("scp -r #{file_path} #{@host}:#{@path}/#{File.basename(dest_filename)}")
140
+ else
141
+ SystemCall.new("scp #{file_path} #{@host}:#{@path}/#{File.basename(dest_filename)}")
142
+ end
143
+ end
144
+
145
+ def new_subdir(dirname)
146
+ self.class.new(:host => @host, :path => "#{@path}/#{dirname}")
147
+ end
148
+ end
149
+
150
+ class RemoteScript
151
+ attr_reader :local_path, :remote_path, :command
152
+ def initialize(options)
153
+ @command = options[:command]
154
+ @remote_path = options[:remote_path]
155
+ Tempfile.open('captivate_remote_script') do |f|
156
+ @local_path = f.path
157
+ f.write(converted_template)
158
+ end
159
+ end
160
+
161
+ private
162
+
163
+ def converted_template
164
+ template.gsub('### CAP COMMANDS ###', command).gsub('### REMOTE PATH ###', remote_path)
165
+ end
166
+
167
+ def template
168
+ <<-EOS
169
+ #!/usr/bin/env bash
170
+ # Deploys app. Executed remotely from deploy host.
171
+ if [ -f ~/.bashrc ]; then
172
+ . ~/.bashrc
173
+ fi
174
+ if [ -f ~/.profile ]; then
175
+ . ~/.profile
176
+ fi
177
+ if [ -f ~/.bash_profile ]; then
178
+ . ~/.bash_profile
179
+ fi
180
+ trap 'echo "--> Aborted. Press Enter to continue..."; read; exit' ERR
181
+ cd ### REMOTE PATH ###
182
+ echo "--> Executing: "
183
+ echo "cap ### CAP COMMANDS ###"
184
+ cap ### CAP COMMANDS ###
185
+ echo "--> Completed. Press Enter to continue..."
186
+ read
187
+ EOS
188
+ end
189
+ end
190
+
191
+ class SystemCall
192
+ def initialize(command)
193
+ system(command)
194
+ end
195
+ end
196
+
197
+ class ConfigError < StandardError
198
+ end
199
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Captivate do
4
+ FIXTURES_PATH = File.expand_path(File.join(File.dirname(__FILE__), '/fixtures'))
5
+
6
+ before do
7
+ # FIXME: FileUtils.rm_rf isn't deleting.
8
+ # FileUtils.rm_rf('/tmp/captivate*')
9
+ system('rm -fr /tmp/captivate*')
10
+ @file_to_appear = '/tmp/captivate-test-file'
11
+ @touched_file_exists = lambda { File.exists?(@file_to_appear) }
12
+ @command = Captivate::Command.new([%{ROLES=app COMMAND='touch #{@file_to_appear}' invoke}])
13
+ end
14
+
15
+ context 'when non-multistage rails app' do
16
+ before do
17
+ @rails_root = File.join(FIXTURES_PATH, 'rails_apps/single_stage')
18
+ end
19
+
20
+ it "should execute cap task and write log file" do
21
+ lambda do
22
+ FileUtils.cd(@rails_root)
23
+ @command.invoke
24
+ end.should change(@touched_file_exists, :call).from(false).to(true)
25
+ end
26
+
27
+ it "should write log file" do
28
+ pending
29
+ FileUtils.cd(@rails_root)
30
+ @command.invoke
31
+ log_file_path = File.join(@command.path, 'captivate.log')
32
+ File.exists?(log_file_path).should be_true
33
+ end
34
+ end
35
+
36
+ context 'when multistage rails app' do
37
+ before do
38
+ @rails_root = File.join(FIXTURES_PATH, 'rails_apps/multi_stage')
39
+ end
40
+
41
+ it "should fail when attempting cap task to nonexistent server" do
42
+ lambda do
43
+ FileUtils.cd(@rails_root)
44
+ Captivate::Command.new([%{ROLES=nonexistent COMMAND='touch #{@file_to_appear}' invoke}]).invoke
45
+ end.should_not raise_error
46
+ end
47
+
48
+ it "should execute cap task via screen" do
49
+ lambda do
50
+ FileUtils.cd(@rails_root)
51
+ Captivate::Command.new([%{staging ROLES=app COMMAND='touch #{@file_to_appear}' invoke}]).invoke
52
+ end.should change(@touched_file_exists, :call).from(false).to(true)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,269 @@
1
+ require 'spec_helper'
2
+
3
+ describe Captivate::Command do
4
+ before do
5
+ Captivate::Config.stub!(:new).and_return('host' => 'the_host', 'working_path' => 'the_path')
6
+ @remote_dir = mock('remote_dir')
7
+ @remote_dir.stub!(:upload)
8
+ @remote_subdir = mock('subdir')
9
+ @remote_subdir.stub!(:upload)
10
+ @remote_dir.stub!(:new_subdir).and_return(@remote_subdir)
11
+ Captivate::RemoteDir.stub!(:new).and_return(@remote_dir)
12
+ Captivate::SystemCall.stub!(:new)
13
+ @remote_script = mock('remote_script')
14
+ @remote_script.stub!(:local_path)
15
+ Captivate::RemoteScript.stub!(:new).and_return(@remote_script)
16
+ @command = Captivate::Command.new(['some cap commands'])
17
+ File.stub!(:chmod)
18
+ end
19
+
20
+ it "should pass rails_root and command to config" do
21
+ FileUtils.stub!(:pwd).and_return('path_to_my_app')
22
+ Captivate::Config.should_receive(:new).with(:command => 'some cap commands', :rails_root => 'path_to_my_app')
23
+ @command.invoke
24
+ end
25
+
26
+ it "should create a remote working directory" do
27
+ Captivate::Config.should_receive(:new).and_return('host' => 'the_host', 'working_path' => 'the_path')
28
+ Captivate::RemoteDir.should_receive(:new).with(:host => 'the_host', :path => 'the_path').and_return(@remote_dir)
29
+ @command.invoke
30
+ end
31
+
32
+ it "should upload Capfile" do
33
+ @remote_dir.should_receive(:upload).with('Capfile')
34
+ @command.invoke
35
+ end
36
+
37
+ it "should create a config subdirectory" do
38
+ @remote_dir.should_receive(:new_subdir).with('config')
39
+ @command.invoke
40
+ end
41
+
42
+ it "should upload deploy.rb to config" do
43
+ @remote_subdir.should_receive(:upload).with('config/deploy.rb')
44
+ @command.invoke
45
+ end
46
+
47
+ it "shouldn't upload config/deploy if doesn't exist" do
48
+ File.should_receive(:directory?).with('config/deploy').and_return(false)
49
+ @remote_subdir.should_not_receive(:upload).with('config/deploy')
50
+ @command.invoke
51
+ end
52
+
53
+ it "should upload config/deploy if exists" do
54
+ File.should_receive(:directory?).with('config/deploy').and_return(true)
55
+ @remote_subdir.should_receive(:upload).with('config/deploy')
56
+ @command.invoke
57
+ end
58
+
59
+ it "should write remote script locally" do
60
+ Captivate::RemoteScript.should_receive(:new).with(:command => 'some cap commands', :remote_path => 'the_path')
61
+ @command.invoke
62
+ end
63
+
64
+ it "should upload remote script" do
65
+ @remote_script.should_receive(:local_path).and_return('path_to_local_script')
66
+ @remote_dir.should_receive(:upload).with('path_to_local_script', 'captivate-remote.sh')
67
+ @command.invoke
68
+ end
69
+
70
+ it "should invoke remote script" do
71
+ Captivate::SystemCall.should_receive(:new).with(%{ssh -t the_host screen the_path/captivate-remote.sh})
72
+ @command.invoke
73
+ end
74
+
75
+ it "should make remote script executable" do
76
+ @remote_script.should_receive(:local_path).and_return('some_path')
77
+ File.should_receive(:chmod).with(0555, 'some_path')
78
+ @command.invoke
79
+ end
80
+ end
81
+
82
+ describe Captivate::Config do
83
+ before do
84
+ @rails_root = 'path/to/my_app'
85
+ @config = Captivate::Config.new(:command => 'some cap commands', :rails_root => @rails_root)
86
+ @yaml_config_path = 'path/to/my_app/config/captivate.yml'
87
+ end
88
+
89
+ it "should raise an error when 'command' not passed in" do
90
+ lambda do
91
+ Captivate::Config.new(:rails_root => 'the_path').invoke.should
92
+ end.should raise_error(Captivate::ConfigError, "Missing 'command' option")
93
+ end
94
+
95
+ it "should raise an error when 'rails_root' not passed in" do
96
+ lambda do
97
+ Captivate::Config.new(:command => 'the_command').invoke.should
98
+ end.should raise_error(Captivate::ConfigError, "Missing 'rails_root' option")
99
+ end
100
+
101
+ context 'without YAML' do
102
+ it "should raise error" do
103
+ lambda { @config.to_hash }.should raise_error(Captivate::ConfigError, @yaml_config_path)
104
+ end
105
+ end
106
+
107
+ context 'with YAML' do
108
+ before do
109
+ File.should_receive(:exists?).with(@yaml_config_path).and_return(true)
110
+ @multistage_config = {
111
+ 'production' => {'host' => 'the_host'},
112
+ 'staging' => {'host' => 'another_host'},
113
+ }
114
+ YAML.stub!(:load_file).with(@yaml_config_path).and_return(@multistage_config)
115
+ end
116
+
117
+ it "should return a hash" do
118
+ @config.to_hash.should be_a(Hash)
119
+ end
120
+
121
+ it "should retrieve host from YAML config, defaulting to production" do
122
+ @config.to_hash['host'].should == 'the_host'
123
+ end
124
+
125
+ it "should not accept non-production stages if multistage gem not present" do
126
+ @config = Captivate::Config.new(:command => 'staging some cap commands', :rails_root => @rails_root)
127
+ @config.stub!(:deploy_file_contents).and_return('deploy_config')
128
+ @config.to_hash['host'].should == 'the_host'
129
+ end
130
+
131
+ it "should raise error if multistage gem present but stages not found" do
132
+ @config = Captivate::Config.new(:command => 'staging some cap commands', :rails_root => @rails_root)
133
+ @config.stub!(:deploy_file_contents).and_return(<<-EOS)
134
+ require 'capistrano/ext/multistage'
135
+ some cap commands
136
+ EOS
137
+ lambda { @config.to_hash }.should raise_error(Captivate::ConfigError, "'set :stages' definition not found")
138
+ end
139
+
140
+ it "should detect valid stage as first parameter if valid stage and multistage gem present" do
141
+ @config = Captivate::Config.new(:command => 'staging some cap commands', :rails_root => @rails_root)
142
+ @config.stub!(:deploy_file_contents).and_return(<<-EOS)
143
+ require 'capistrano/ext/multistage'
144
+ set :stages, %w(production staging showcase)
145
+ some cap commands
146
+ EOS
147
+ @config.to_hash['host'].should == 'another_host'
148
+ end
149
+
150
+ it "should detect valid stage as first parameter if valid stage and multistage gem present but commented out" do
151
+ @config = Captivate::Config.new(:command => 'staging some cap commands', :rails_root => @rails_root)
152
+ @config.stub!(:deploy_file_contents).and_return(<<-EOS)
153
+ # require 'capistrano/ext/multistage'
154
+ some cap commands
155
+ EOS
156
+ @config.to_hash['host'].should == 'the_host'
157
+ end
158
+
159
+ # Not defined in YAML config (@multistage_config['showcase'])
160
+ it "should ignore invalid stage as first parameter" do
161
+ @config = Captivate::Config.new(:command => 'showcase some cap commands', :rails_root => @rails_root)
162
+ @config.to_hash['host'].should == 'the_host'
163
+ end
164
+
165
+ it "should contain timestamp, app_name, environment, user in remote working directory name" do
166
+ Etc.stub!(:getlogin).and_return('the_user')
167
+ @config.to_hash['working_path'].should == "/tmp/captivate.#{Time.now.to_i}.my_app.production.the_user"
168
+ end
169
+
170
+ it "should merge YAML config" do
171
+ @config.stub!(:yaml_config).and_return('production' => {'host' => 'the_host', 'working_path' => 'custom_path'})
172
+ @config.to_hash['working_path'].should == 'custom_path'
173
+ end
174
+
175
+ it "should raise an error if missing config for environment" do
176
+ YAML.stub!(:load_file).with(@yaml_config_path).and_return({})
177
+ lambda do
178
+ @config.to_hash
179
+ end.should raise_error(Captivate::ConfigError, 'Missing production environment')
180
+ end
181
+
182
+ it "should raise an error if host missing from config" do
183
+ YAML.stub!(:load_file).with(@yaml_config_path).and_return('production' => {})
184
+ lambda do
185
+ @config.to_hash
186
+ end.should raise_error(Captivate::ConfigError, "Missing 'host' for production environment")
187
+ end
188
+ end
189
+ end
190
+
191
+ describe Captivate::RemoteDir do
192
+ before do
193
+ Captivate::SystemCall.stub!(:new)
194
+ end
195
+
196
+ it "should create a directory" do
197
+ Captivate::SystemCall.should_receive(:new).with('ssh the_host mkdir the_path')
198
+ Captivate::RemoteDir.new(:host => 'the_host', :path => 'the_path')
199
+ end
200
+
201
+ it "should raise an error if missing file" do
202
+ @remote_dir = Captivate::RemoteDir.new(:host => 'the_host', :path => 'the_path')
203
+ lambda do
204
+ @remote_dir.upload('local_path/a_file')
205
+ end.should raise_error(Errno::ENOENT, 'No such file or directory - local_path/a_file')
206
+ end
207
+
208
+ it "should create a subdirectory" do
209
+ @remote_dir = Captivate::RemoteDir.new(:host => 'the_host', :path => 'the_path')
210
+ Captivate::RemoteDir.should_receive(:new).with(:host => 'the_host', :path => 'the_path/a_dir')
211
+ @remote_dir.new_subdir('a_dir')
212
+ end
213
+
214
+ context 'when uploading file which exists' do
215
+ before do
216
+ File.stub!(:exists?).and_return(true)
217
+ end
218
+
219
+ it "should upload a file" do
220
+ @remote_dir = Captivate::RemoteDir.new(:host => 'the_host', :path => 'the_path')
221
+ Captivate::SystemCall.should_receive(:new).with('scp local_path/a_file the_host:the_path/a_file')
222
+ @remote_dir.upload('local_path/a_file')
223
+ end
224
+
225
+ it "should upload a file and rename" do
226
+ @remote_dir = Captivate::RemoteDir.new(:host => 'the_host', :path => 'the_path')
227
+ Captivate::SystemCall.should_receive(:new).with('scp a_file the_host:the_path/new_name')
228
+ @remote_dir.upload('a_file', 'new_name')
229
+ end
230
+
231
+ it "should upload a directory" do
232
+ @remote_dir = Captivate::RemoteDir.new(:host => 'the_host', :path => 'the_path')
233
+ Captivate::SystemCall.should_receive(:new).with('scp -r local_path/a_dir the_host:the_path/a_dir')
234
+ File.should_receive(:directory?).with('local_path/a_dir').and_return(true)
235
+ @remote_dir.upload('local_path/a_dir')
236
+ end
237
+ end
238
+ end
239
+
240
+ describe Captivate::RemoteScript do
241
+ before do
242
+ @file = mock('file')
243
+ @file.stub!(:path)
244
+ @file.stub!(:write)
245
+ Tempfile.should_receive(:open).and_yield(@file)
246
+ @remote_script = lambda do
247
+ Captivate::RemoteScript.new(:command => 'some cap commands', :remote_path => 'the_remote_path')
248
+ end
249
+ end
250
+
251
+ it "should contain cap command in script" do
252
+ @file.should_receive(:write) do |shell_script|
253
+ shell_script.should include('some cap commands')
254
+ end
255
+ @remote_script.call
256
+ end
257
+
258
+ it "should contain remote script path in script" do
259
+ @file.should_receive(:write) do |shell_script|
260
+ shell_script.should include('the_remote_path')
261
+ end
262
+ @remote_script.call
263
+ end
264
+
265
+ it "should return path" do
266
+ @file.should_receive(:path).and_return('some_path')
267
+ @remote_script.call
268
+ end
269
+ end