paratrooper 2.4.1 → 3.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 84bd6c187cb422b1e72b4dc30aa60012f46600b3
|
4
|
+
data.tar.gz: 3d14dd42f2b029cf97418d1508a84b250e6750c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c29d2b06b09efe69089e18e791509d30affa865e0bc9a22dc1a8ed7db3fba3bd30142c54238fa31761f433b8039e793e99d0a1bed8566e65c8ff800e590250d5
|
7
|
+
data.tar.gz: e05a117d41e9811b65621b54ca91501573b7a4503a645ec7fa618bfdeb2c37798c353ca4245b47fab73c1c2737a66bf5d3ada33238c3277d55f760bdc480bcac
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 3.0.0.beta1
|
4
|
+
|
5
|
+
- Moved all state into configuration object
|
6
|
+
- Updated interface to start a deploy Ex: `Paratrooper.deploy('appname')`
|
7
|
+
- If any exception is thrown, the deploy process is aborted
|
8
|
+
- Stop deploy process if there is no access to Heroku
|
9
|
+
|
3
10
|
## 2.4.1
|
4
11
|
|
5
|
-
- Fix Deploy#app_url for wildcard domains
|
12
|
+
- Fix `Deploy#app_url` for wildcard domains
|
6
13
|
|
7
14
|
## 2.4.0
|
8
15
|
|
9
16
|
- Maintenance mode only runs around migrations now
|
10
|
-
- README updates around maintenance_mode
|
17
|
+
- README updates around `maintenance_mode=`
|
11
18
|
|
12
19
|
## 2.3.0
|
13
20
|
|
data/README.md
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
![Paratrooper](http://f.cl.ly/items/0Z1v1P1l1B1h1k1l2q0E/paratrooper_header.png)
|
2
2
|
|
3
|
-
[![Gem Version](
|
4
|
-
[![Build Status](
|
5
|
-
[![Code Climate](
|
6
|
-
[![Gitter chat](https://badges.gitter.im/mattpolito/paratrooper.png)](https://gitter.im/mattpolito/paratrooper)
|
3
|
+
[![Gem Version](http://img.shields.io/gem/v/paratrooper.svg?style=flat)](http://badge.fury.io/rb/paratrooper)
|
4
|
+
[![Build Status](http://img.shields.io/travis/mattpolito/paratrooper/master.svg?style=flat)](https://travis-ci.org/mattpolito/paratrooper)
|
5
|
+
[![Code Climate](http://img.shields.io/codeclimate/github/mattpolito/paratrooper.svg?style=flat)](https://codeclimate.com/github/mattpolito/paratrooper)
|
7
6
|
|
8
7
|
Simplify your [Heroku][] deploy with quick and concise deployment rake tasks.
|
9
8
|
|
data/lib/paratrooper.rb
CHANGED
@@ -14,14 +14,14 @@ module Paratrooper
|
|
14
14
|
@callbacks ||= Hash.new { |hash, key| hash[key] = [] }
|
15
15
|
end
|
16
16
|
|
17
|
-
private
|
18
|
-
|
19
17
|
def build_callback(name, context = nil, &block)
|
20
18
|
execute_callback("before_#{name}".to_sym, context)
|
21
19
|
block.call if block_given?
|
22
20
|
execute_callback("after_#{name}".to_sym, context)
|
23
21
|
end
|
24
22
|
|
23
|
+
private
|
24
|
+
|
25
25
|
def execute_callback(name, context)
|
26
26
|
callbacks[name].each { |c| c.call(context) }
|
27
27
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'paratrooper/heroku_wrapper'
|
2
|
+
require 'paratrooper/http_client_wrapper'
|
3
|
+
require 'paratrooper/system_caller'
|
4
|
+
require 'paratrooper/callbacks'
|
5
|
+
require 'paratrooper/pending_migration_check'
|
6
|
+
require 'paratrooper/source_control'
|
7
|
+
require 'paratrooper/notifiers/screen_notifier'
|
8
|
+
|
9
|
+
module Paratrooper
|
10
|
+
class Configuration
|
11
|
+
include Callbacks
|
12
|
+
|
13
|
+
attr_accessor :branch_name, :app_name, :api_key
|
14
|
+
attr_writer :protocol, :heroku, :migration_check,
|
15
|
+
:system_caller, :deployment_host, :http_client, :screen_notifier,
|
16
|
+
:source_control
|
17
|
+
|
18
|
+
alias :branch= :branch_name=
|
19
|
+
|
20
|
+
def attributes=(attrs)
|
21
|
+
attrs.each do |method, value|
|
22
|
+
public_send("#{method}=", value)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def branch_name?
|
27
|
+
!branch_name.to_s.strip.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def migration_check
|
31
|
+
@migration_check ||= PendingMigrationCheck.new(source_control.deployment_sha, heroku, system_caller)
|
32
|
+
end
|
33
|
+
|
34
|
+
def heroku
|
35
|
+
@heroku ||= HerokuWrapper.new(app_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def screen_notifier
|
39
|
+
@screen_notifier ||= Notifiers::ScreenNotifier.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def notifiers=(notifers)
|
43
|
+
@notifiers = Array(notifers)
|
44
|
+
end
|
45
|
+
|
46
|
+
def notifiers
|
47
|
+
@notifiers ||= [@screen_notifier]
|
48
|
+
end
|
49
|
+
|
50
|
+
def protocol
|
51
|
+
@protocol ||= 'http'
|
52
|
+
end
|
53
|
+
|
54
|
+
def deployment_host
|
55
|
+
@deployment_host ||= 'heroku.com'
|
56
|
+
end
|
57
|
+
|
58
|
+
def http_client
|
59
|
+
@http_client ||= HttpClientWrapper.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def maintenance
|
63
|
+
@maintenance ||= false
|
64
|
+
end
|
65
|
+
|
66
|
+
def maintenance=(val)
|
67
|
+
@maintenance = !!val
|
68
|
+
end
|
69
|
+
|
70
|
+
def maintenance?
|
71
|
+
@maintenance
|
72
|
+
end
|
73
|
+
|
74
|
+
def force_push=(val)
|
75
|
+
@force_push= !!val
|
76
|
+
end
|
77
|
+
|
78
|
+
def force_push
|
79
|
+
@force_push ||= false
|
80
|
+
end
|
81
|
+
|
82
|
+
def force_push?
|
83
|
+
@force_push
|
84
|
+
end
|
85
|
+
|
86
|
+
def system_caller
|
87
|
+
@system_caller ||= SystemCaller.new
|
88
|
+
end
|
89
|
+
|
90
|
+
def source_control
|
91
|
+
@source_control ||= SourceControl.new(self)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/paratrooper/deploy.rb
CHANGED
@@ -1,24 +1,22 @@
|
|
1
|
-
require '
|
2
|
-
require 'paratrooper/
|
3
|
-
require 'paratrooper/
|
4
|
-
require 'paratrooper/pending_migration_check'
|
5
|
-
require 'paratrooper/callbacks'
|
6
|
-
require 'paratrooper/http_client_wrapper'
|
1
|
+
require 'forwardable'
|
2
|
+
require 'paratrooper/configuration'
|
3
|
+
require 'paratrooper/error'
|
7
4
|
|
8
5
|
module Paratrooper
|
9
6
|
|
10
7
|
# Public: Entry point into the library.
|
11
8
|
#
|
12
9
|
class Deploy
|
13
|
-
|
10
|
+
extend Forwardable
|
11
|
+
delegate [:system_caller, :migration_check, :notifiers,
|
12
|
+
:deloyment_host, :heroku, :source_control, :screen_notifier
|
13
|
+
] => :config
|
14
14
|
|
15
|
-
|
16
|
-
:match_tag_name, :protocol, :deployment_host, :migration_check, :debug,
|
17
|
-
:screen_notifier, :branch_name, :http_client, :maintenance
|
15
|
+
attr_writer :config
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
def self.call(app_name, options = {}, &block)
|
18
|
+
new(app_name, options, &block).deploy
|
19
|
+
end
|
22
20
|
|
23
21
|
# Public: Initializes a Deploy
|
24
22
|
#
|
@@ -30,14 +28,12 @@ module Paratrooper
|
|
30
28
|
# notified of steps in deployment process
|
31
29
|
# (optional).
|
32
30
|
# :heroku - Object wrapper around heroku-api (optional).
|
33
|
-
# :tag - String name to be used as a git reference
|
34
|
-
# point for deploying from specific tag
|
35
|
-
# (optional).
|
36
|
-
# :match_tag - String name of git reference point to match
|
37
|
-
# :tag to (optional).
|
38
31
|
# :branch - String name to be used as a git reference
|
39
|
-
# point for deploying from specific branch
|
32
|
+
# point for deploying from specific branch.
|
33
|
+
# Use :head to deploy from current branch
|
40
34
|
# (optional).
|
35
|
+
# :force - Force deploy using (-f flag) on deploy
|
36
|
+
# (optional, default: false)
|
41
37
|
# :system_caller - Object responsible for calling system
|
42
38
|
# commands (optional).
|
43
39
|
# :protocol - String web protocol to be used when pinging
|
@@ -55,22 +51,12 @@ module Paratrooper
|
|
55
51
|
# (optional).
|
56
52
|
#
|
57
53
|
def initialize(app_name, options = {}, &block)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@heroku = options[:heroku] || HerokuWrapper.new(app_name, options)
|
62
|
-
@tag_name = options[:tag]
|
63
|
-
@branch_name = options[:branch]
|
64
|
-
@match_tag_name = options[:match_tag] || 'master'
|
65
|
-
@system_caller = options[:system_caller] || SystemCaller.new(debug)
|
66
|
-
@protocol = options[:protocol] || 'http'
|
67
|
-
@deployment_host = options[:deployment_host] || 'heroku.com'
|
68
|
-
@debug = options[:debug] || false
|
69
|
-
@migration_check = options[:migration_check] || PendingMigrationCheck.new(match_tag_name, heroku, system_caller)
|
70
|
-
@http_client = options[:http_client] || HttpClientWrapper.new
|
71
|
-
@maintenance = options[:maintenance] || false
|
54
|
+
config.attributes = options.merge(app_name: app_name)
|
55
|
+
block.call(config) if block_given?
|
56
|
+
end
|
72
57
|
|
73
|
-
|
58
|
+
def config
|
59
|
+
@config ||= Configuration.new
|
74
60
|
end
|
75
61
|
|
76
62
|
# Public: Hook method called first in the deploy process.
|
@@ -93,7 +79,7 @@ module Paratrooper
|
|
93
79
|
# Public: Activates Heroku maintenance mode.
|
94
80
|
#
|
95
81
|
def activate_maintenance_mode
|
96
|
-
return unless
|
82
|
+
return unless maintenance_necessary?
|
97
83
|
callback(:activate_maintenance_mode) do
|
98
84
|
notify(:activate_maintenance_mode)
|
99
85
|
heroku.app_maintenance_on
|
@@ -103,41 +89,22 @@ module Paratrooper
|
|
103
89
|
# Public: Deactivates Heroku maintenance mode.
|
104
90
|
#
|
105
91
|
def deactivate_maintenance_mode
|
106
|
-
return unless
|
92
|
+
return unless maintenance_necessary?
|
107
93
|
callback(:deactivate_maintenance_mode) do
|
108
94
|
notify(:deactivate_maintenance_mode)
|
109
95
|
heroku.app_maintenance_off
|
110
96
|
end
|
111
97
|
end
|
112
98
|
|
113
|
-
def maintenance_mode(&block)
|
114
|
-
activate_maintenance_mode
|
115
|
-
block.call if block_given?
|
116
|
-
deactivate_maintenance_mode
|
117
|
-
end
|
118
|
-
|
119
|
-
# Public: Creates a git tag and pushes it to repository.
|
120
|
-
#
|
121
|
-
def update_repo_tag
|
122
|
-
unless tag_name.nil? || tag_name.empty?
|
123
|
-
callback(:update_repo_tag) do
|
124
|
-
notify(:update_repo_tag)
|
125
|
-
system_call "git tag #{tag_name} #{match_tag_name} -f"
|
126
|
-
system_call "git push -f origin #{tag_name}"
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
99
|
# Public: Pushes repository to Heroku.
|
132
100
|
#
|
133
101
|
# Based on the following precedence:
|
134
|
-
# branch_name /
|
102
|
+
# branch_name / 'master'
|
135
103
|
#
|
136
104
|
def push_repo
|
137
|
-
reference_point = git_branch_name || git_tag_name || 'master'
|
138
105
|
callback(:push_repo) do
|
139
|
-
notify(:push_repo
|
140
|
-
|
106
|
+
notify(:push_repo)
|
107
|
+
source_control.push_to_deploy
|
141
108
|
end
|
142
109
|
end
|
143
110
|
|
@@ -161,41 +128,26 @@ module Paratrooper
|
|
161
128
|
end
|
162
129
|
end
|
163
130
|
|
164
|
-
# Public: cURL for application URL to start your Heroku dyno.
|
165
|
-
#
|
166
|
-
# wait_time - Integer length of time (seconds) to wait before making call
|
167
|
-
# to app
|
168
|
-
#
|
169
|
-
def warm_instance(wait_time = 3)
|
170
|
-
callback(:warm_instance) do
|
171
|
-
notify(:warm_instance)
|
172
|
-
sleep wait_time
|
173
|
-
http_client.get("#{protocol}://#{app_url}")
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
131
|
# Public: Execute common deploy steps.
|
178
132
|
#
|
179
133
|
# Default deploy consists of:
|
180
134
|
# * Activating maintenance page
|
181
|
-
# * Updating repository tag
|
182
135
|
# * Pushing repository to Heroku
|
183
136
|
# * Running database migrations
|
184
137
|
# * Restarting application on Heroku
|
185
138
|
# * Deactivating maintenance page
|
186
|
-
# * Accessing application URL to warm Heroku dyno
|
187
139
|
#
|
188
140
|
# Alias: #deploy
|
189
141
|
def default_deploy
|
190
142
|
setup
|
191
|
-
update_repo_tag
|
192
143
|
push_repo
|
193
144
|
maintenance_mode do
|
194
145
|
run_migrations
|
195
146
|
app_restart
|
196
147
|
end
|
197
|
-
warm_instance
|
198
148
|
teardown
|
149
|
+
rescue Paratrooper::Error => e
|
150
|
+
abort(e.message)
|
199
151
|
end
|
200
152
|
alias_method :deploy, :default_deploy
|
201
153
|
|
@@ -208,40 +160,33 @@ module Paratrooper
|
|
208
160
|
end
|
209
161
|
|
210
162
|
private
|
211
|
-
def
|
212
|
-
|
163
|
+
def maintenance_mode(&block)
|
164
|
+
activate_maintenance_mode
|
165
|
+
block.call if block_given?
|
166
|
+
deactivate_maintenance_mode
|
167
|
+
end
|
168
|
+
|
169
|
+
def maintenance_necessary?
|
170
|
+
config.maintenance? && pending_migrations?
|
213
171
|
end
|
214
172
|
|
215
173
|
def callback(name, &block)
|
216
|
-
build_callback(name, screen_notifier, &block)
|
174
|
+
config.build_callback(name, screen_notifier, &block)
|
217
175
|
end
|
218
176
|
|
219
177
|
# Internal: Payload data to be sent with notifications
|
220
178
|
#
|
221
179
|
def default_payload
|
222
180
|
{
|
223
|
-
app_name: app_name,
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
match_tag: match_tag_name
|
181
|
+
app_name: config.app_name,
|
182
|
+
deployment_remote: source_control.remote,
|
183
|
+
force_push: config.force_push,
|
184
|
+
reference_point: source_control.reference_point,
|
228
185
|
}
|
229
186
|
end
|
230
187
|
|
231
|
-
def git_remote(host, name)
|
232
|
-
"git@#{host}:#{name}.git"
|
233
|
-
end
|
234
|
-
|
235
|
-
def git_branch_name
|
236
|
-
"refs/heads/#{branch_name}" if branch_name
|
237
|
-
end
|
238
|
-
|
239
|
-
def git_tag_name
|
240
|
-
"refs/tags/#{tag_name}" if tag_name
|
241
|
-
end
|
242
|
-
|
243
188
|
def deployment_remote
|
244
|
-
|
189
|
+
source_control.remote
|
245
190
|
end
|
246
191
|
|
247
192
|
# Internal: Notifies other objects that an event has occurred
|
@@ -256,19 +201,11 @@ module Paratrooper
|
|
256
201
|
end
|
257
202
|
|
258
203
|
def pending_migrations?
|
259
|
-
migration_check.migrations_waiting?
|
204
|
+
@pending_migrations ||= migration_check.migrations_waiting?
|
260
205
|
end
|
261
206
|
|
262
207
|
def restart_required?
|
263
208
|
pending_migrations?
|
264
209
|
end
|
265
|
-
|
266
|
-
# Internal: Calls commands meant to go to system
|
267
|
-
#
|
268
|
-
# call - String version of system command
|
269
|
-
#
|
270
|
-
def system_call(call)
|
271
|
-
system_caller.execute(call)
|
272
|
-
end
|
273
210
|
end
|
274
211
|
end
|
@@ -1,9 +1,17 @@
|
|
1
1
|
require 'heroku-api'
|
2
2
|
require 'rendezvous'
|
3
3
|
require 'paratrooper/local_api_key_extractor'
|
4
|
+
require 'paratrooper/error'
|
4
5
|
|
5
6
|
module Paratrooper
|
6
7
|
class HerokuWrapper
|
8
|
+
class ErrorNoAccess < Paratrooper::Error
|
9
|
+
def initialize(name)
|
10
|
+
msg = "It appears that you may not have access to #{name}"
|
11
|
+
super(msg)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
7
15
|
attr_reader :api_key, :app_name, :heroku_api, :key_extractor, :rendezvous
|
8
16
|
|
9
17
|
def initialize(app_name, options = {})
|
@@ -15,7 +23,7 @@ module Paratrooper
|
|
15
23
|
end
|
16
24
|
|
17
25
|
def app_restart
|
18
|
-
|
26
|
+
client(:post_ps_restart, app_name)
|
19
27
|
end
|
20
28
|
|
21
29
|
def app_maintenance_off
|
@@ -26,44 +34,30 @@ module Paratrooper
|
|
26
34
|
app_maintenance('1')
|
27
35
|
end
|
28
36
|
|
29
|
-
def app_url
|
30
|
-
app_domain_name
|
31
|
-
end
|
32
|
-
|
33
37
|
def run_migrations
|
34
38
|
run_task('rake db:migrate')
|
35
39
|
end
|
36
40
|
|
37
41
|
def run_task(task_name)
|
38
|
-
data =
|
42
|
+
data = client(:post_ps, app_name, task_name, attach: 'true').body
|
39
43
|
rendezvous.start(url: data['rendezvous_url'])
|
40
44
|
end
|
41
45
|
|
42
46
|
def last_deploy_commit
|
43
|
-
data =
|
47
|
+
data = client(:get_releases, app_name).body
|
44
48
|
return nil if data.empty?
|
45
49
|
data.last['commit']
|
46
50
|
end
|
47
51
|
|
48
52
|
private
|
49
|
-
def app_domain_name
|
50
|
-
if custom_domain_response
|
51
|
-
custom_domain_response['domain']
|
52
|
-
else
|
53
|
-
default_domain_name
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
53
|
def app_maintenance(flag)
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
def default_domain_name
|
62
|
-
heroku_api.get_app(app_name).body['domain_name']['domain']
|
54
|
+
client(:post_app_maintenance, app_name, flag)
|
63
55
|
end
|
64
56
|
|
65
|
-
def
|
66
|
-
|
57
|
+
def client(method, *args)
|
58
|
+
heroku_api.public_send(method, *args)
|
59
|
+
rescue Heroku::API::Errors::Forbidden => e
|
60
|
+
raise ErrorNoAccess.new(app_name)
|
67
61
|
end
|
68
62
|
end
|
69
63
|
end
|