heroku_hatchet 5.0.0 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|