heroku_hatchet 5.0.0 → 7.0.0
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/.circleci/config.yml +69 -0
- data/.gitignore +2 -0
- data/CHANGELOG.md +32 -1
- data/Gemfile +0 -1
- data/README.md +772 -205
- data/bin/hatchet +11 -4
- data/etc/ci_setup.rb +21 -15
- data/etc/setup_heroku.sh +0 -2
- data/hatchet.gemspec +4 -5
- data/hatchet.json +6 -2
- data/hatchet.lock +12 -8
- data/lib/hatchet.rb +1 -2
- data/lib/hatchet/api_rate_limit.rb +13 -24
- data/lib/hatchet/app.rb +159 -53
- data/lib/hatchet/config.rb +1 -1
- data/lib/hatchet/git_app.rb +27 -1
- data/lib/hatchet/reaper.rb +159 -56
- data/lib/hatchet/reaper/app_age.rb +49 -0
- data/lib/hatchet/reaper/reaper_throttle.rb +55 -0
- data/lib/hatchet/shell_throttle.rb +71 -0
- data/lib/hatchet/test_run.rb +16 -9
- data/lib/hatchet/version.rb +1 -1
- data/{test → repo_fixtures}/different-folder-for-checked-in-repos/default_ruby/Gemfile +0 -0
- data/spec/hatchet/allow_failure_git_spec.rb +40 -0
- data/spec/hatchet/app_spec.rb +226 -0
- data/spec/hatchet/ci_spec.rb +67 -0
- data/spec/hatchet/config_spec.rb +34 -0
- data/spec/hatchet/edit_repo_spec.rb +17 -0
- data/spec/hatchet/git_spec.rb +9 -0
- data/spec/hatchet/heroku_api_spec.rb +30 -0
- data/spec/hatchet/local_repo_spec.rb +26 -0
- data/spec/hatchet/lock_spec.rb +30 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/unit/reaper_spec.rb +153 -0
- data/spec/unit/shell_throttle.rb +28 -0
- metadata +57 -86
- data/.travis.yml +0 -16
- data/test/fixtures/buildpacks/null-buildpack/bin/compile +0 -4
- data/test/fixtures/buildpacks/null-buildpack/bin/detect +0 -5
- data/test/fixtures/buildpacks/null-buildpack/bin/release +0 -3
- data/test/fixtures/buildpacks/null-buildpack/hatchet.json +0 -4
- data/test/fixtures/buildpacks/null-buildpack/readme.md +0 -41
- data/test/hatchet/allow_failure_git_test.rb +0 -16
- data/test/hatchet/app_test.rb +0 -96
- data/test/hatchet/ci_four_test.rb +0 -19
- data/test/hatchet/ci_test.rb +0 -11
- data/test/hatchet/ci_three_test.rb +0 -9
- data/test/hatchet/ci_too_test.rb +0 -19
- data/test/hatchet/config_test.rb +0 -51
- data/test/hatchet/edit_repo_test.rb +0 -20
- data/test/hatchet/git_test.rb +0 -16
- data/test/hatchet/heroku_api_test.rb +0 -30
- data/test/hatchet/labs_test.rb +0 -20
- data/test/hatchet/local_repo_test.rb +0 -26
- data/test/hatchet/lock_test.rb +0 -9
- data/test/hatchet/multi_cmd_runner_test.rb +0 -30
- data/test/test_helper.rb +0 -28
data/bin/hatchet
CHANGED
@@ -46,7 +46,7 @@ class HatchetCLI < Thor
|
|
46
46
|
Threaded.later do
|
47
47
|
commit = lock_hash[directory]
|
48
48
|
directory = File.expand_path(directory)
|
49
|
-
if Dir[directory]
|
49
|
+
if !Dir[directory]&.empty?
|
50
50
|
puts "== pulling '#{git_repo}' into '#{directory}'\n"
|
51
51
|
pull(directory, git_repo)
|
52
52
|
else
|
@@ -67,6 +67,7 @@ class HatchetCLI < Thor
|
|
67
67
|
desc "locks to specific git commits", "updates hatchet.lock"
|
68
68
|
def lock
|
69
69
|
lock_hash = {}
|
70
|
+
lockfile_hash = load_lockfile
|
70
71
|
dirs.map do |directory, git_repo|
|
71
72
|
Threaded.later do
|
72
73
|
puts "== locking #{directory}"
|
@@ -75,8 +76,14 @@ class HatchetCLI < Thor
|
|
75
76
|
clone(directory, git_repo, quiet: false)
|
76
77
|
end
|
77
78
|
|
78
|
-
|
79
|
-
|
79
|
+
if lockfile_hash[directory] == "master"
|
80
|
+
lock_hash[directory] = "master"
|
81
|
+
elsif lockfile_hash[directory] == "main"
|
82
|
+
lock_hash[directory] = "main"
|
83
|
+
else
|
84
|
+
commit = commit_at_directory(directory)
|
85
|
+
lock_hash[directory] = commit
|
86
|
+
end
|
80
87
|
end
|
81
88
|
end.map(&:join)
|
82
89
|
|
@@ -152,7 +159,7 @@ class HatchetCLI < Thor
|
|
152
159
|
end
|
153
160
|
|
154
161
|
def pull(path, git_repo, commit: false)
|
155
|
-
cmd("cd #{path} && git pull --rebase #{git_repo}
|
162
|
+
cmd("cd #{path} && git pull --rebase #{git_repo} --quiet")
|
156
163
|
end
|
157
164
|
|
158
165
|
def clone(path, git_repo, quiet: true)
|
data/etc/ci_setup.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require 'bundler'
|
3
2
|
require 'shellwords'
|
3
|
+
|
4
|
+
STDOUT.sync = true
|
5
|
+
|
6
|
+
def run_cmd(command)
|
7
|
+
puts "== Running: #{command}"
|
8
|
+
result = `#{command}`
|
9
|
+
raise "Command failed: #{command.inspect}\nResult: #{result}" unless $?.success?
|
10
|
+
end
|
11
|
+
|
4
12
|
puts "== Setting Up CI =="
|
5
13
|
|
6
14
|
netrc_file = "#{ENV['HOME']}/.netrc"
|
@@ -8,22 +16,20 @@ unless File.exists?(netrc_file)
|
|
8
16
|
File.open(netrc_file, 'w') do |file|
|
9
17
|
file.write <<-EOF
|
10
18
|
machine git.heroku.com
|
11
|
-
login #{ENV.fetch('HEROKU_API_USER')}
|
12
|
-
password #{ENV.fetch('HEROKU_API_KEY')}
|
19
|
+
login #{ENV.fetch('HEROKU_API_USER')}
|
20
|
+
password #{ENV.fetch('HEROKU_API_KEY')}
|
21
|
+
machine api.heroku.com
|
22
|
+
login #{ENV.fetch('HEROKU_API_USER')}
|
23
|
+
password #{ENV.fetch('HEROKU_API_KEY')}
|
13
24
|
EOF
|
25
|
+
run_cmd 'chmod 0600 "$HOME/.netrc"'
|
14
26
|
end
|
15
27
|
end
|
16
28
|
|
17
|
-
|
18
|
-
"bundle exec hatchet
|
19
|
-
"
|
20
|
-
"git config --get user.
|
21
|
-
|
22
|
-
].each do |command|
|
23
|
-
puts "== Running: #{command}"
|
24
|
-
Bundler.with_clean_env do
|
25
|
-
result = `#{command}`
|
26
|
-
raise "Command failed: #{command.inspect}\nResult: #{result}" unless $?.success?
|
27
|
-
end
|
28
|
-
end
|
29
|
+
run_cmd "bundle exec hatchet ci:install_heroku"
|
30
|
+
run_cmd "bundle exec hatchet install"
|
31
|
+
run_cmd "git config --get user.email > /dev/null || git config --global user.email #{ENV.fetch('HEROKU_API_USER').shellescape}"
|
32
|
+
run_cmd "git config --get user.name > /dev/null || git config --global user.name 'BuildpackTester'"
|
33
|
+
|
29
34
|
puts "== Done =="
|
35
|
+
|
data/etc/setup_heroku.sh
CHANGED
data/hatchet.gemspec
CHANGED
@@ -18,17 +18,16 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
|
-
gem.add_dependency "platform-api", "~>
|
21
|
+
gem.add_dependency "platform-api", "~> 3"
|
22
22
|
gem.add_dependency "rrrretry", "~> 1"
|
23
23
|
gem.add_dependency "excon", "~> 0"
|
24
24
|
gem.add_dependency "thor", "~> 0"
|
25
|
-
gem.add_dependency "repl_runner", "~> 0.0.3"
|
26
25
|
gem.add_dependency "threaded", "~> 0"
|
27
|
-
gem.add_dependency 'minitest-retry', '~> 0.1.9'
|
28
26
|
|
29
|
-
gem.add_development_dependency "
|
27
|
+
gem.add_development_dependency "rspec"
|
30
28
|
gem.add_development_dependency "rake", ">= 10"
|
31
29
|
gem.add_development_dependency "mocha", ">= 1"
|
32
|
-
gem.add_development_dependency "
|
30
|
+
gem.add_development_dependency "parallel_split_test"
|
33
31
|
gem.add_development_dependency "travis", ">= 1"
|
32
|
+
gem.add_development_dependency "rspec-retry"
|
34
33
|
end
|
data/hatchet.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"hatchet": {
|
3
|
-
"directory": "
|
3
|
+
"directory": "repo_fixtures"
|
4
4
|
},
|
5
5
|
"rails3": ["sharpstone/rails3_mri_193"],
|
6
6
|
"rails2": ["sharpstone/rails2blog"],
|
@@ -10,5 +10,9 @@
|
|
10
10
|
"sharpstone/rails5_ruby_schema_format",
|
11
11
|
"sharpstone/rails5_ci_fails_no_database"
|
12
12
|
],
|
13
|
-
"lock": [
|
13
|
+
"lock": [
|
14
|
+
"sharpstone/lock_fail",
|
15
|
+
"sharpstone/lock_fail_master",
|
16
|
+
"sharpstone/lock_fail_main"
|
17
|
+
]
|
14
18
|
}
|
data/hatchet.lock
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
---
|
2
|
-
- -
|
2
|
+
- - repo_fixtures/repos/bundler/no_lockfile
|
3
3
|
- 1947ce9a9c276d5df1c323b2ad78d1d85c7ab4c0
|
4
|
-
- -
|
4
|
+
- - repo_fixtures/repos/ci/rails5_ci_fails_no_database
|
5
5
|
- 3044f05febdfbbe656f0f5113cf5968ca07e34fd
|
6
|
-
- -
|
7
|
-
-
|
8
|
-
- -
|
6
|
+
- - repo_fixtures/repos/ci/rails5_ruby_schema_format
|
7
|
+
- d76be86c66ae8f45ec611fb2c4d8eb3adac0ad4b
|
8
|
+
- - repo_fixtures/repos/default/default_ruby
|
9
9
|
- 6e642963acec0ff64af51bd6fba8db3c4176ed6e
|
10
|
-
- -
|
10
|
+
- - repo_fixtures/repos/lock/lock_fail
|
11
11
|
- da748a59340be8b950e7bbbfb32077eb67d70c3c
|
12
|
-
- -
|
12
|
+
- - repo_fixtures/repos/lock/lock_fail_main
|
13
|
+
- main
|
14
|
+
- - repo_fixtures/repos/lock/lock_fail_master
|
15
|
+
- master
|
16
|
+
- - repo_fixtures/repos/rails2/rails2blog
|
13
17
|
- b37357a498ae5e8429f5601c5ab9524021dc2aaa
|
14
|
-
- -
|
18
|
+
- - repo_fixtures/repos/rails3/rails3_mri_193
|
15
19
|
- 88c5d0d067cfd11e4452633994a85b04627ae8c7
|
data/lib/hatchet.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'active_support/core_ext/object/blank'
|
2
1
|
require 'rrrretry'
|
3
2
|
|
4
3
|
require 'json'
|
@@ -8,7 +7,7 @@ require 'stringio'
|
|
8
7
|
require 'date'
|
9
8
|
|
10
9
|
module Hatchet
|
11
|
-
|
10
|
+
APP_PREFIX = (ENV['HATCHET_APP_PREFIX'] || "hatchet-t-")
|
12
11
|
end
|
13
12
|
|
14
13
|
require 'hatchet/version'
|
@@ -1,14 +1,11 @@
|
|
1
|
-
#
|
1
|
+
# Legacy class
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# platform_api.pipeline.create(name: @name)
|
6
|
-
#
|
7
|
-
# Use:
|
8
|
-
#
|
9
|
-
# api_rate_limit = ApiRateLimit.new(platform_api)
|
10
|
-
# api_rate_limit.call.pipeline.create(name: @name)
|
3
|
+
# Not needed since rate throttling went directly into the platform-api gem.
|
4
|
+
# This class is effectively now a no-op
|
11
5
|
#
|
6
|
+
# It's being left in as it's interface was public and it's hard-ish to
|
7
|
+
# deprecate/remove. Since it's so small there's not much value in removal
|
8
|
+
# so it's probably fine to keep around for quite some time.
|
12
9
|
class ApiRateLimit
|
13
10
|
def initialize(platform_api)
|
14
11
|
@platform_api = platform_api
|
@@ -16,24 +13,16 @@ class ApiRateLimit
|
|
16
13
|
@called = 0
|
17
14
|
end
|
18
15
|
|
19
|
-
|
20
|
-
# Sleeps for progressively longer when api rate limit capacity
|
21
|
-
# is lower.
|
22
|
-
#
|
23
|
-
# Unfortunatley `@platform_api.rate_limit` is an extra API
|
24
|
-
# call, so by checking our limit, we also are using our limit 😬
|
25
|
-
# to partially mitigate this, only check capacity every 5
|
26
|
-
# api calls, or if the current capacity is under 1000
|
27
16
|
def call
|
28
|
-
@called += 1
|
17
|
+
# @called += 1
|
29
18
|
|
30
|
-
if @called > 5 || @capacity < 1000
|
31
|
-
|
32
|
-
|
33
|
-
end
|
19
|
+
# if @called > 5 || @capacity < 1000
|
20
|
+
# @called = 0
|
21
|
+
# @capacity = @platform_api.rate_limit.info["remaining"]
|
22
|
+
# end
|
34
23
|
|
35
|
-
sleep_time = (60/@capacity) if @capacity > 0.1 # no divide by zero
|
36
|
-
sleep(sleep_time || 60)
|
24
|
+
# sleep_time = (60/@capacity) if @capacity > 0.1 # no divide by zero
|
25
|
+
# sleep(sleep_time || 60)
|
37
26
|
|
38
27
|
return @platform_api
|
39
28
|
end
|
data/lib/hatchet/app.rb
CHANGED
@@ -9,14 +9,34 @@ module Hatchet
|
|
9
9
|
HATCHET_BUILDPACK_BRANCH = -> { ENV['HATCHET_BUILDPACK_BRANCH'] || ENV['HEROKU_TEST_RUN_BRANCH'] || Hatchet.git_branch }
|
10
10
|
BUILDPACK_URL = "https://github.com/heroku/heroku-buildpack-ruby.git"
|
11
11
|
|
12
|
-
attr_reader :name, :stack, :directory, :repo_name, :app_config, :buildpacks
|
13
|
-
|
14
|
-
class FailedDeploy < StandardError
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
attr_reader :name, :stack, :directory, :repo_name, :app_config, :buildpacks, :reaper
|
13
|
+
|
14
|
+
class FailedDeploy < StandardError; end
|
15
|
+
|
16
|
+
class FailedDeployError < FailedDeploy
|
17
|
+
attr_reader :output
|
18
|
+
|
19
|
+
def initialize(app, message, output: )
|
20
|
+
@output = output
|
21
|
+
msg = "Could not deploy '#{app.name}' (#{app.repo_name}) using '#{app.class}' at path: '#{app.directory}'\n"
|
22
|
+
msg << "if this was expected add `allow_failure: true` to your deploy hash.\n"
|
23
|
+
msg << "#{message}\n"
|
24
|
+
msg << "output:\n"
|
25
|
+
msg << "#{output}"
|
26
|
+
super(msg)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class FailedReleaseError < FailedDeploy
|
31
|
+
attr_reader :output
|
32
|
+
|
33
|
+
def initialize(app, message, output: )
|
34
|
+
@output = output
|
35
|
+
msg = "Could not release '#{app.name}' (#{app.repo_name}) using '#{app.class}' at path: '#{app.directory}'\n"
|
36
|
+
msg << "if this was expected add `allow_failure: true` to your deploy hash.\n"
|
37
|
+
msg << "#{message}\n"
|
38
|
+
msg << "output:\n"
|
39
|
+
msg << "#{output}"
|
20
40
|
super(msg)
|
21
41
|
end
|
22
42
|
end
|
@@ -34,6 +54,7 @@ module Hatchet
|
|
34
54
|
buildpacks: nil,
|
35
55
|
buildpack_url: nil,
|
36
56
|
before_deploy: nil,
|
57
|
+
run_multi: ENV["HATCHET_RUN_MULTI"],
|
37
58
|
config: {}
|
38
59
|
)
|
39
60
|
@repo_name = repo_name
|
@@ -46,6 +67,12 @@ module Hatchet
|
|
46
67
|
@buildpacks = buildpack || buildpacks || buildpack_url || self.class.default_buildpack
|
47
68
|
@buildpacks = Array(@buildpacks)
|
48
69
|
@buildpacks.map! {|b| b == :default ? self.class.default_buildpack : b}
|
70
|
+
@run_multi = run_multi
|
71
|
+
|
72
|
+
if run_multi && !ENV["HATCHET_EXPENSIVE_MODE"]
|
73
|
+
raise "You're attempting to enable `run_multi: true` mode, but have not enabled `HATCHET_EXPENSIVE_MODE=1` env var to verify you understand the risks"
|
74
|
+
end
|
75
|
+
@run_multi_array = []
|
49
76
|
@already_in_dir = nil
|
50
77
|
@app_is_setup = nil
|
51
78
|
|
@@ -127,6 +154,22 @@ module Hatchet
|
|
127
154
|
else
|
128
155
|
command = command.to_s
|
129
156
|
end
|
157
|
+
|
158
|
+
heroku_command = build_heroku_command(command, options)
|
159
|
+
|
160
|
+
allow_run_multi! if @run_multi
|
161
|
+
|
162
|
+
output = ""
|
163
|
+
|
164
|
+
ShellThrottle.new(platform_api: @platform_api).call do |throttle|
|
165
|
+
output = `#{heroku_command}`
|
166
|
+
throw(:throttle) if output.match?(/reached the API rate limit/)
|
167
|
+
end
|
168
|
+
|
169
|
+
return output
|
170
|
+
end
|
171
|
+
|
172
|
+
private def build_heroku_command(command, options = {})
|
130
173
|
command = command.shellescape unless options.delete(:raw)
|
131
174
|
|
132
175
|
default_options = { "app" => name, "exit-code" => nil }
|
@@ -136,16 +179,77 @@ module Hatchet
|
|
136
179
|
arg << "=#{v.to_s.shellescape}" unless v.nil? # nil means we include the option without an argument
|
137
180
|
arg
|
138
181
|
end.join(" ")
|
139
|
-
heroku_command = "heroku run #{heroku_options} -- #{command}"
|
140
182
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
183
|
+
"heroku run #{heroku_options} -- #{command}"
|
184
|
+
end
|
185
|
+
|
186
|
+
private def allow_run_multi!
|
187
|
+
raise "Must explicitly enable the `run_multi: true` option. This requires scaling up a dyno and is not free, it may incur charges on your account" unless @run_multi
|
188
|
+
|
189
|
+
@run_multi_is_setup ||= platform_api.formation.update(name, "web", {"size" => "Standard-1X"})
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
# Allows multiple commands to be run concurrently in the background.
|
194
|
+
#
|
195
|
+
# WARNING! Using the feature requres that the underlying app is not on the "free" Heroku
|
196
|
+
# tier. This requires scaling up the dyno which is not free. If an app is
|
197
|
+
# scaled up and left in that state it can incur large costs.
|
198
|
+
#
|
199
|
+
# Enabling this feature should be done with extreme caution.
|
200
|
+
#
|
201
|
+
# Example:
|
202
|
+
#
|
203
|
+
# Hatchet::Runner.new("default_ruby", run_multi: true)
|
204
|
+
# app.run_multi("ls") { |out| expect(out).to include("Gemfile") }
|
205
|
+
# app.run_multi("ruby -v") { |out| expect(out).to include("ruby") }
|
206
|
+
# end
|
207
|
+
#
|
208
|
+
# This example will run `heroku run ls` as well as `ruby -v` at the same time in the background.
|
209
|
+
# The return result will be yielded to the block after they finish running.
|
210
|
+
#
|
211
|
+
# Order of execution is not guaranteed.
|
212
|
+
#
|
213
|
+
# If you need to assert a command was successful, you can yield a second status object like this:
|
214
|
+
#
|
215
|
+
# Hatchet::Runner.new("default_ruby", run_multi: true)
|
216
|
+
# app.run_multi("ls") do |out, status|
|
217
|
+
# expect(status.success?).to be_truthy
|
218
|
+
# expect(out).to include("Gemfile")
|
219
|
+
# end
|
220
|
+
# app.run_multi("ruby -v") do |out, status|
|
221
|
+
# expect(status.success?).to be_truthy
|
222
|
+
# expect(out).to include("ruby")
|
223
|
+
# end
|
224
|
+
# end
|
225
|
+
def run_multi(command, options = {}, &block)
|
226
|
+
raise "Block required" if block.nil?
|
227
|
+
allow_run_multi!
|
228
|
+
|
229
|
+
run_thread = Thread.new do
|
230
|
+
heroku_command = build_heroku_command(command, options)
|
231
|
+
|
232
|
+
out = nil
|
233
|
+
status = nil
|
234
|
+
ShellThrottle.new(platform_api: @platform_api).call do |throttle|
|
235
|
+
out = `#{heroku_command}`
|
236
|
+
throw(:throttle) if output.match?(/reached the API rate limit/)
|
237
|
+
status = $?
|
238
|
+
end
|
239
|
+
|
240
|
+
yield out, status
|
241
|
+
|
242
|
+
# if block.arity == 1
|
243
|
+
# block.call(out)
|
244
|
+
# else
|
245
|
+
# block.call(out, status)
|
246
|
+
# end
|
148
247
|
end
|
248
|
+
run_thread.abort_on_exception = true
|
249
|
+
|
250
|
+
@run_multi_array << run_thread
|
251
|
+
|
252
|
+
true
|
149
253
|
end
|
150
254
|
|
151
255
|
# set debug: true when creating app if you don't want it to be
|
@@ -162,24 +266,26 @@ module Hatchet
|
|
162
266
|
alias :no_debug? :not_debugging?
|
163
267
|
|
164
268
|
def deployed?
|
165
|
-
# !heroku.get_ps(name).body.detect {|ps| ps["process"].include?("web") }.nil?
|
166
269
|
api_rate_limit.call.formation.list(name).detect {|ps| ps["type"] == "web"}
|
167
270
|
end
|
168
271
|
|
169
272
|
def create_app
|
170
273
|
3.times.retry do
|
171
274
|
begin
|
172
|
-
# heroku.post_app({ name: name, stack: stack }.delete_if {|k,v| v.nil? })
|
173
275
|
hash = { name: name, stack: stack }
|
174
276
|
hash.delete_if { |k,v| v.nil? }
|
175
|
-
|
277
|
+
heroku_api_create_app(hash)
|
176
278
|
rescue => e
|
177
|
-
@reaper.cycle
|
279
|
+
@reaper.cycle(app_exception_message: e.message)
|
178
280
|
raise e
|
179
281
|
end
|
180
282
|
end
|
181
283
|
end
|
182
284
|
|
285
|
+
private def heroku_api_create_app(hash)
|
286
|
+
api_rate_limit.call.app.create(hash)
|
287
|
+
end
|
288
|
+
|
183
289
|
def update_stack(stack_name)
|
184
290
|
@stack = stack_name
|
185
291
|
api_rate_limit.call.app.update(name, build_stack: @stack)
|
@@ -219,11 +325,15 @@ module Hatchet
|
|
219
325
|
|
220
326
|
def teardown!
|
221
327
|
return false unless @app_is_setup
|
222
|
-
|
223
|
-
|
224
|
-
|
328
|
+
|
329
|
+
if @run_multi_is_setup
|
330
|
+
@run_multi_array.map(&:join)
|
331
|
+
platform_api.formation.update(name, "web", {"size" => "free"})
|
225
332
|
end
|
226
|
-
|
333
|
+
|
334
|
+
ensure
|
335
|
+
@app_update_info = platform_api.app.update(name, { maintenance: true }) if @app_is_setup
|
336
|
+
@reaper.cycle if @app_is_setup
|
227
337
|
end
|
228
338
|
|
229
339
|
def in_directory(directory = self.directory)
|
@@ -265,10 +375,6 @@ module Hatchet
|
|
265
375
|
end
|
266
376
|
end
|
267
377
|
|
268
|
-
# creates a new app on heroku, "pushes" via anvil or git
|
269
|
-
# then yields to self so you can call self.run or
|
270
|
-
# self.deployed?
|
271
|
-
# Allow deploy failures on CI server by setting ENV['HATCHET_RETRIES']
|
272
378
|
def deploy(&block)
|
273
379
|
in_directory do
|
274
380
|
self.setup!
|
@@ -276,7 +382,7 @@ module Hatchet
|
|
276
382
|
block.call(self, api_rate_limit.call, output) if block_given?
|
277
383
|
end
|
278
384
|
ensure
|
279
|
-
self.teardown!
|
385
|
+
self.teardown! if block_given?
|
280
386
|
end
|
281
387
|
|
282
388
|
def push
|
@@ -315,31 +421,30 @@ module Hatchet
|
|
315
421
|
end
|
316
422
|
|
317
423
|
def run_ci(timeout: 300, &block)
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
424
|
+
in_directory do
|
425
|
+
Hatchet::RETRIES.times.retry do
|
426
|
+
result = create_pipeline
|
427
|
+
@pipeline_id = result["id"]
|
428
|
+
end
|
322
429
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
430
|
+
# when the CI run finishes, the associated ephemeral app created for the test run internally gets removed almost immediately
|
431
|
+
# the system then sees a pipeline with no apps, and deletes it, also almost immediately
|
432
|
+
# that would, with bad timing, mean our test run info poll in wait! would 403, and/or the delete_pipeline at the end
|
433
|
+
# that's why we create an app explictly (or maybe it already exists), and then associate it with with the pipeline
|
434
|
+
# the app will be auto cleaned up later
|
435
|
+
self.setup!
|
436
|
+
Hatchet::RETRIES.times.retry do
|
437
|
+
couple_pipeline(@name, @pipeline_id)
|
438
|
+
end
|
332
439
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
in_directory do
|
342
|
-
call_before_deploy
|
440
|
+
test_run = TestRun.new(
|
441
|
+
token: api_key,
|
442
|
+
buildpacks: @buildpacks,
|
443
|
+
timeout: timeout,
|
444
|
+
app: self,
|
445
|
+
pipeline: @pipeline_id,
|
446
|
+
api_rate_limit: api_rate_limit
|
447
|
+
)
|
343
448
|
|
344
449
|
Hatchet::RETRIES.times.retry do
|
345
450
|
test_run.create_test_run
|
@@ -347,6 +452,7 @@ module Hatchet
|
|
347
452
|
test_run.wait!(&block)
|
348
453
|
end
|
349
454
|
ensure
|
455
|
+
teardown! if block_given?
|
350
456
|
delete_pipeline(@pipeline_id) if @pipeline_id
|
351
457
|
@pipeline_id = nil
|
352
458
|
end
|
@@ -382,7 +488,6 @@ module Hatchet
|
|
382
488
|
end
|
383
489
|
|
384
490
|
def platform_api
|
385
|
-
puts "Deprecated: use `api_rate_limit.call` instead of platform_api"
|
386
491
|
api_rate_limit
|
387
492
|
return @platform_api
|
388
493
|
end
|
@@ -434,3 +539,4 @@ module Hatchet
|
|
434
539
|
end
|
435
540
|
end
|
436
541
|
|
542
|
+
require_relative 'shell_throttle.rb'
|