paratrooper 2.4.1 → 3.0.0.beta1
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.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/CHANGELOG.md +9 -2
- data/README.md +3 -4
- data/lib/paratrooper.rb +3 -0
- data/lib/paratrooper/callbacks.rb +2 -2
- data/lib/paratrooper/configuration.rb +94 -0
- data/lib/paratrooper/deploy.rb +42 -105
- data/lib/paratrooper/error.rb +3 -0
- data/lib/paratrooper/heroku_wrapper.rb +16 -22
- data/lib/paratrooper/local_api_key_extractor.rb +2 -1
- data/lib/paratrooper/notifiers/screen_notifier.rb +6 -5
- data/lib/paratrooper/pending_migration_check.rb +7 -6
- data/lib/paratrooper/source_control.rb +49 -0
- data/lib/paratrooper/system_caller.rb +19 -6
- data/lib/paratrooper/version.rb +1 -1
- data/paratrooper.gemspec +2 -2
- data/spec/paratrooper/configuration_spec.rb +352 -0
- data/spec/paratrooper/deploy_spec.rb +77 -232
- data/spec/paratrooper/heroku_wrapper_spec.rb +8 -38
- data/spec/paratrooper/local_api_key_extractor_spec.rb +2 -3
- data/spec/paratrooper/notifier_spec.rb +1 -1
- data/spec/paratrooper/pending_migration_check_spec.rb +14 -7
- data/spec/paratrooper/source_control_spec.rb +233 -0
- metadata +32 -28
- data/.bundle/config +0 -2
- data/.rspec +0 -2
- data/.ruby-version +0 -1
@@ -48,7 +48,12 @@ module Paratrooper
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def push_repo(options = {})
|
51
|
-
|
51
|
+
desc = "#{options[:reference_point]} to #{options[:app_name]} on Heroku"
|
52
|
+
if options[:force]
|
53
|
+
display("Force pushing #{desc}")
|
54
|
+
else
|
55
|
+
display("Pushing #{desc}")
|
56
|
+
end
|
52
57
|
end
|
53
58
|
|
54
59
|
def run_migrations(options = {})
|
@@ -58,10 +63,6 @@ module Paratrooper
|
|
58
63
|
def app_restart(options = {})
|
59
64
|
display("Restarting application")
|
60
65
|
end
|
61
|
-
|
62
|
-
def warm_instance(options = {})
|
63
|
-
display("Accessing #{options[:app_url]} to warm up your application")
|
64
|
-
end
|
65
66
|
end
|
66
67
|
end
|
67
68
|
end
|
@@ -2,16 +2,17 @@ require 'paratrooper/system_caller'
|
|
2
2
|
|
3
3
|
module Paratrooper
|
4
4
|
class PendingMigrationCheck
|
5
|
-
attr_accessor :diff, :heroku, :
|
5
|
+
attr_accessor :diff, :heroku, :deployment_sha, :system_caller
|
6
6
|
|
7
|
-
def initialize(
|
7
|
+
def initialize(deployment_sha, heroku_wrapper, system_caller)
|
8
8
|
self.heroku = heroku_wrapper
|
9
|
-
self.
|
9
|
+
self.deployment_sha = deployment_sha
|
10
10
|
self.system_caller = system_caller
|
11
11
|
end
|
12
12
|
|
13
13
|
def migrations_waiting?
|
14
|
-
@migrations_waiting
|
14
|
+
defined?(@migrations_waiting) or @migrations_waiting = check_for_pending_migrations
|
15
|
+
@migrations_waiting
|
15
16
|
end
|
16
17
|
|
17
18
|
def last_deployed_commit
|
@@ -21,8 +22,8 @@ module Paratrooper
|
|
21
22
|
private
|
22
23
|
|
23
24
|
def check_for_pending_migrations
|
24
|
-
|
25
|
-
self.diff = system_caller.execute(
|
25
|
+
cmd = %Q[git diff --shortstat #{last_deployed_commit} #{deployment_sha} -- db/migrate]
|
26
|
+
self.diff = system_caller.execute(cmd)
|
26
27
|
!diff.strip.empty?
|
27
28
|
end
|
28
29
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Paratrooper
|
4
|
+
class SourceControl
|
5
|
+
extend Forwardable
|
6
|
+
delegate [:system_caller] => :config
|
7
|
+
|
8
|
+
attr_accessor :config
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
def remote
|
15
|
+
"git@#{config.deployment_host}:#{config.app_name}.git"
|
16
|
+
end
|
17
|
+
|
18
|
+
def branch_name
|
19
|
+
if config.branch_name?
|
20
|
+
branch = config.branch_name.to_s
|
21
|
+
branch.upcase == "HEAD" ? "HEAD" : "refs/heads/#{config.branch_name}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def push_to_deploy
|
26
|
+
system_call("git push #{force_flag}#{remote} #{reference_point}:refs/heads/master", :exit_code)
|
27
|
+
end
|
28
|
+
|
29
|
+
def reference_point
|
30
|
+
branch_name || 'HEAD'
|
31
|
+
end
|
32
|
+
|
33
|
+
def deployment_sha
|
34
|
+
system_call("git rev-parse #{reference_point}").strip
|
35
|
+
end
|
36
|
+
|
37
|
+
def force_flag
|
38
|
+
"-f " if config.force_push
|
39
|
+
end
|
40
|
+
|
41
|
+
# Internal: Calls commands meant to go to system
|
42
|
+
#
|
43
|
+
# cmd - String version of system command
|
44
|
+
#
|
45
|
+
def system_call(cmd, exit_code = false)
|
46
|
+
system_caller.execute(cmd, exit_code)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,19 +1,32 @@
|
|
1
|
+
require 'paratrooper/error'
|
2
|
+
|
1
3
|
module Paratrooper
|
2
4
|
class SystemCaller
|
5
|
+
class ErrorSystemExit < Paratrooper::Error
|
6
|
+
def initialize(cmd)
|
7
|
+
msg = "The system command '#{cmd}' has exited unsuccessfully"
|
8
|
+
super(msg)
|
9
|
+
end
|
10
|
+
end
|
3
11
|
attr_accessor :debug
|
4
12
|
|
5
13
|
def initialize(debug = false)
|
6
|
-
|
14
|
+
@debug = debug
|
7
15
|
end
|
8
16
|
|
9
|
-
def execute(
|
10
|
-
debug_message_for(
|
11
|
-
|
17
|
+
def execute(cmd, exit_code = false)
|
18
|
+
debug_message_for(cmd)
|
19
|
+
result = %x[#{cmd}]
|
20
|
+
if exit_code && $? != 0
|
21
|
+
fail(ErrorSystemExit.new(cmd))
|
22
|
+
end
|
23
|
+
result
|
12
24
|
end
|
13
25
|
|
14
26
|
private
|
15
|
-
|
16
|
-
|
27
|
+
|
28
|
+
def debug_message_for(cmd)
|
29
|
+
puts "DEBUG: #{cmd}" if debug
|
17
30
|
end
|
18
31
|
end
|
19
32
|
end
|
data/lib/paratrooper/version.rb
CHANGED
data/paratrooper.gemspec
CHANGED
@@ -20,10 +20,10 @@ Gem::Specification.new do |gem|
|
|
20
20
|
gem.required_ruby_version = '>= 1.9.2'
|
21
21
|
|
22
22
|
gem.add_development_dependency 'rake'
|
23
|
-
gem.add_development_dependency 'rspec', '~>
|
23
|
+
gem.add_development_dependency 'rspec', '~> 3.0'
|
24
24
|
gem.add_development_dependency 'pry'
|
25
25
|
gem.add_dependency 'heroku-api', '~> 0.3'
|
26
|
-
gem.add_dependency 'rendezvous', '~> 0.
|
26
|
+
gem.add_dependency 'rendezvous', '~> 0.1'
|
27
27
|
gem.add_dependency 'netrc', '~> 0.7'
|
28
28
|
gem.add_dependency 'excon'
|
29
29
|
end
|
@@ -0,0 +1,352 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'paratrooper/configuration'
|
3
|
+
|
4
|
+
describe Paratrooper::Configuration do
|
5
|
+
let(:configuration) { described_class.new }
|
6
|
+
|
7
|
+
describe "attributes=" do
|
8
|
+
it "takes hash of attributes and calls the key as a method setter" do
|
9
|
+
attrs = { app_name: 'APP_NAME', screen_notifier: 'SCREEN_NOTIFIER' }
|
10
|
+
configuration.attributes = attrs
|
11
|
+
|
12
|
+
expect(configuration.app_name).to eq('APP_NAME')
|
13
|
+
expect(configuration.screen_notifier).to eq('SCREEN_NOTIFIER')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "app_name" do
|
18
|
+
context "with passed value" do
|
19
|
+
it "returns passed value" do
|
20
|
+
configuration.app_name = "APP_NAME"
|
21
|
+
expect(configuration.app_name).to eq("APP_NAME")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with no value passed" do
|
26
|
+
it "returns nil" do
|
27
|
+
expect(configuration.app_name).to be_nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "branch_name?" do
|
33
|
+
context "when branch_name is a valid string" do
|
34
|
+
it "returns true" do
|
35
|
+
configuration.branch_name = 'BRANCH_NAME'
|
36
|
+
expect(configuration.branch_name?).to eq(true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when branch_name is an invalid string" do
|
41
|
+
it "returns false" do
|
42
|
+
configuration.branch_name = " "
|
43
|
+
expect(configuration.branch_name?).to eq(false)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when branch_name is nil" do
|
48
|
+
it "returns false" do
|
49
|
+
configuration.branch_name = nil
|
50
|
+
expect(configuration.branch_name?).to eq(false)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "branch_name" do
|
56
|
+
context "with passed value" do
|
57
|
+
it "returns passed value" do
|
58
|
+
configuration.branch_name = "TAG_NAME"
|
59
|
+
expect(configuration.branch_name).to eq("TAG_NAME")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "with no value passed" do
|
64
|
+
it "returns nil" do
|
65
|
+
expect(configuration.branch_name).to be_nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "branch=" do
|
71
|
+
specify "branch is set and @branch_name holds value" do
|
72
|
+
configuration.branch = "branch_name"
|
73
|
+
|
74
|
+
expect(configuration.branch_name).to eq("branch_name")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "protocol" do
|
79
|
+
context "with passed value" do
|
80
|
+
specify "passed value is returned" do
|
81
|
+
configuration.protocol = 'https'
|
82
|
+
|
83
|
+
expect(configuration.protocol).to eq('https')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "no value passed" do
|
88
|
+
specify "default value of 'http' is returned" do
|
89
|
+
expect(configuration.protocol).to eq('http')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "deployment_host" do
|
95
|
+
context "with passed value" do
|
96
|
+
it "returns the value passed" do
|
97
|
+
configuration.deployment_host = 'HOST_NAME'
|
98
|
+
expect(configuration.deployment_host).to eq('HOST_NAME')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "with no passed value" do
|
103
|
+
it "returns the default value of 'heroku.com'" do
|
104
|
+
expect(configuration.deployment_host).to eq('heroku.com')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "api_key" do
|
110
|
+
context "with passed value" do
|
111
|
+
it "returns the value passed" do
|
112
|
+
configuration.api_key = 'API_KEY'
|
113
|
+
expect(configuration.api_key).to eq('API_KEY')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "with no passed value" do
|
118
|
+
it "returns nil" do
|
119
|
+
expect(configuration.api_key).to be_nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "maintenance" do
|
125
|
+
context "with passed value" do
|
126
|
+
it "returns the value passed" do
|
127
|
+
configuration.maintenance = 'true'
|
128
|
+
expect(configuration.maintenance).to eq(true)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "with no passed value" do
|
133
|
+
it "returns false" do
|
134
|
+
expect(configuration.maintenance).to eq(false)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "maintenance?" do
|
140
|
+
context "when #maintenance is truthy" do
|
141
|
+
it "returns true" do
|
142
|
+
configuration.maintenance = "YUP"
|
143
|
+
expect(configuration.maintenance?).to eq(true)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "when #maintenance is falsey" do
|
148
|
+
it "returns false" do
|
149
|
+
configuration.maintenance = false
|
150
|
+
expect(configuration.maintenance?).to eq(false)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "force_push" do
|
156
|
+
context "with passed value" do
|
157
|
+
it "returns the value passed" do
|
158
|
+
configuration.force_push = "true"
|
159
|
+
expect(configuration.force_push).to eq(true)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "with no passed value" do
|
164
|
+
it "returns false" do
|
165
|
+
expect(configuration.force_push).to eq(false)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "force_push?" do
|
171
|
+
context "when #force_push is truthy" do
|
172
|
+
it "returns true" do
|
173
|
+
configuration.force_push = "YUP"
|
174
|
+
expect(configuration.force_push?).to eq(true)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context "when #force_push is falsey" do
|
179
|
+
it "returns false" do
|
180
|
+
configuration.force_push = false
|
181
|
+
expect(configuration.force_push?).to eq(false)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "heroku" do
|
187
|
+
context "with passed value" do
|
188
|
+
it "returns passed value" do
|
189
|
+
heroku = double(:heroku)
|
190
|
+
configuration.heroku = heroku
|
191
|
+
|
192
|
+
expect(configuration.heroku).to eq(heroku)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
context "with no value passed" do
|
197
|
+
it "returns internal heroku wrapper" do
|
198
|
+
heroku = class_double("HerokuWrapper")
|
199
|
+
stub_const("Paratrooper::HerokuWrapper", heroku)
|
200
|
+
wrapper_instance = double(:heroku)
|
201
|
+
expect(heroku).to receive(:new).with("APP_NAME").and_return(wrapper_instance)
|
202
|
+
|
203
|
+
configuration.app_name = "APP_NAME"
|
204
|
+
expect(configuration.heroku).to eq(wrapper_instance)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe "migration_check" do
|
210
|
+
context "with passed value" do
|
211
|
+
it "returns passed value" do
|
212
|
+
migration_check = double(:pending_migration_check)
|
213
|
+
configuration.migration_check = migration_check
|
214
|
+
|
215
|
+
expect(configuration.migration_check).to eq(migration_check)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context "with no value passed" do
|
220
|
+
it "returns the default pending migration check object" do
|
221
|
+
migration_check_class = class_double("PendingMigrationCheck")
|
222
|
+
stub_const("Paratrooper::PendingMigrationCheck", migration_check_class)
|
223
|
+
migration_check = double(:heroku)
|
224
|
+
source_control = double(:source_control, deployment_sha: "SHA")
|
225
|
+
|
226
|
+
configuration.heroku = "HEROKU"
|
227
|
+
configuration.system_caller = "SYSTEM"
|
228
|
+
configuration.source_control = source_control
|
229
|
+
|
230
|
+
expect(migration_check_class).to receive(:new).with("SHA", "HEROKU", "SYSTEM")
|
231
|
+
.and_return(migration_check)
|
232
|
+
expect(configuration.migration_check).to eq(migration_check)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe "system_caller" do
|
238
|
+
context "with passed value" do
|
239
|
+
it "returns passed value" do
|
240
|
+
system_caller = double(:system_caller)
|
241
|
+
configuration.system_caller = system_caller
|
242
|
+
|
243
|
+
expect(configuration.system_caller).to eq(system_caller)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context "with no value passed" do
|
248
|
+
it "returns default system_caller" do
|
249
|
+
system_caller = class_double("SystemCaller")
|
250
|
+
stub_const("Paratrooper::SystemCaller", system_caller)
|
251
|
+
wrapper_instance = double(:system_caller)
|
252
|
+
expect(system_caller).to receive(:new).and_return(wrapper_instance)
|
253
|
+
|
254
|
+
expect(configuration.system_caller).to eq(wrapper_instance)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe "http_client" do
|
260
|
+
context "with passed value" do
|
261
|
+
it "returns passed value" do
|
262
|
+
http_client = double(:http_client)
|
263
|
+
configuration.http_client = http_client
|
264
|
+
|
265
|
+
expect(configuration.http_client).to eq(http_client)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
context "with no value passed" do
|
270
|
+
it "returns internal http_client wrapper" do
|
271
|
+
http_client = class_double("HttpClientWrapper")
|
272
|
+
stub_const("Paratrooper::HttpClientWrapper", http_client)
|
273
|
+
wrapper_instance = double(:http_client)
|
274
|
+
expect(http_client).to receive(:new).and_return(wrapper_instance)
|
275
|
+
|
276
|
+
expect(configuration.http_client).to eq(wrapper_instance)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
describe "screen_notifier" do
|
282
|
+
context "with passed value" do
|
283
|
+
it "returns passed value" do
|
284
|
+
screen_notifier = double(:screen_notifier)
|
285
|
+
configuration.screen_notifier = screen_notifier
|
286
|
+
|
287
|
+
expect(configuration.screen_notifier).to eq(screen_notifier)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
context "with no value passed" do
|
292
|
+
it "returns default screen_notifier" do
|
293
|
+
screen_notifier = class_double("Notifiers::ScreenNotifier")
|
294
|
+
stub_const("Paratrooper::Notifiers::ScreenNotifier", screen_notifier)
|
295
|
+
notifier = double(:screen_notifier)
|
296
|
+
expect(screen_notifier).to receive(:new).and_return(notifier)
|
297
|
+
|
298
|
+
expect(configuration.screen_notifier).to eq(notifier)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
describe "notifiers" do
|
304
|
+
context "with passed value as array" do
|
305
|
+
it "returns passed value" do
|
306
|
+
notifier = double(:notifier)
|
307
|
+
configuration.notifiers = [notifier]
|
308
|
+
|
309
|
+
expect(configuration.notifiers).to eq([notifier])
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
context "with passed value as single item" do
|
314
|
+
it "returns array containing passed value" do
|
315
|
+
notifier = double(:notifier)
|
316
|
+
configuration.notifiers = notifier
|
317
|
+
|
318
|
+
expect(configuration.notifiers).to eq([notifier])
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
context "with no value passed" do
|
323
|
+
it "returns array containing screen notifier" do
|
324
|
+
configuration.screen_notifier = "SCREEN_NOTIFIER"
|
325
|
+
|
326
|
+
expect(configuration.notifiers).to eq(["SCREEN_NOTIFIER"])
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
describe "source_control" do
|
332
|
+
context "with passed value" do
|
333
|
+
it "returns passed value" do
|
334
|
+
source_control = double(:source_control)
|
335
|
+
configuration.source_control = source_control
|
336
|
+
|
337
|
+
expect(configuration.source_control).to eq(source_control)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
context "with no value passed" do
|
342
|
+
it "returns default source_control" do
|
343
|
+
source_control = class_double("SourceControl")
|
344
|
+
stub_const("Paratrooper::SourceControl", source_control)
|
345
|
+
controller = double(:source_control)
|
346
|
+
expect(source_control).to receive(:new).with(configuration).and_return(controller)
|
347
|
+
|
348
|
+
expect(configuration.source_control).to eq(controller)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|