heroku_hatchet 5.0.2 → 7.1.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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +21 -1
- data/.gitignore +2 -0
- data/CHANGELOG.md +36 -1
- data/README.md +790 -164
- data/bin/hatchet +19 -7
- data/etc/ci_setup.rb +16 -12
- data/etc/setup_heroku.sh +0 -2
- data/hatchet.gemspec +4 -6
- 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 +150 -41
- data/lib/hatchet/config.rb +1 -1
- data/lib/hatchet/git_app.rb +28 -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 +55 -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 +81 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/unit/reaper_spec.rb +169 -0
- data/spec/unit/shell_throttle.rb +28 -0
- metadata +43 -87
- 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 -20
- 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(create_if_does_not_exist: true)
|
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
|
|
@@ -111,10 +118,15 @@ class HatchetCLI < Thor
|
|
111
118
|
end
|
112
119
|
|
113
120
|
private
|
114
|
-
def load_lockfile
|
121
|
+
def load_lockfile(create_if_does_not_exist: false)
|
115
122
|
return YAML.safe_load(File.read('hatchet.lock')).to_h
|
116
123
|
rescue Errno::ENOENT
|
117
|
-
|
124
|
+
if create_if_does_not_exist
|
125
|
+
FileUtils.touch('hatchet.lock')
|
126
|
+
{}
|
127
|
+
else
|
128
|
+
raise "No such file found `hatchet.lock` please run `$ bundle exec hatchet lock`"
|
129
|
+
end
|
118
130
|
end
|
119
131
|
|
120
132
|
def bad_repo?(url)
|
@@ -144,7 +156,7 @@ class HatchetCLI < Thor
|
|
144
156
|
end
|
145
157
|
|
146
158
|
def checkout_commit(directory, commit)
|
147
|
-
cmd("cd #{directory} && git reset --hard #{commit}")
|
159
|
+
cmd("cd #{directory} && git fetch origin #{commit} && git checkout #{commit} && git checkout - && git reset --hard #{commit}")
|
148
160
|
end
|
149
161
|
|
150
162
|
def commit_at_directory(directory)
|
@@ -152,7 +164,7 @@ class HatchetCLI < Thor
|
|
152
164
|
end
|
153
165
|
|
154
166
|
def pull(path, git_repo, commit: false)
|
155
|
-
cmd("cd #{path} && git pull --rebase #{git_repo}
|
167
|
+
cmd("cd #{path} && git pull --rebase #{git_repo} --quiet")
|
156
168
|
end
|
157
169
|
|
158
170
|
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"
|
@@ -14,18 +22,14 @@ machine api.heroku.com
|
|
14
22
|
login #{ENV.fetch('HEROKU_API_USER')}
|
15
23
|
password #{ENV.fetch('HEROKU_API_KEY')}
|
16
24
|
EOF
|
17
|
-
|
25
|
+
run_cmd 'chmod 0600 "$HOME/.netrc"'
|
18
26
|
end
|
19
27
|
end
|
20
28
|
|
21
|
-
|
22
|
-
"bundle exec hatchet
|
23
|
-
"
|
24
|
-
"git config --get user.
|
25
|
-
|
26
|
-
].each do |command|
|
27
|
-
puts "== Running: #{command}"
|
28
|
-
result = `#{command}`
|
29
|
-
raise "Command failed: #{command.inspect}\nResult: #{result}" unless $?.success?
|
30
|
-
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
|
+
|
31
34
|
puts "== Done =="
|
35
|
+
|
data/etc/setup_heroku.sh
CHANGED
data/hatchet.gemspec
CHANGED
@@ -18,18 +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"
|
34
|
-
gem.add_development_dependency "
|
32
|
+
gem.add_development_dependency "rspec-retry"
|
35
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, :max_retries_count
|
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,8 @@ module Hatchet
|
|
34
54
|
buildpacks: nil,
|
35
55
|
buildpack_url: nil,
|
36
56
|
before_deploy: nil,
|
57
|
+
run_multi: ENV["HATCHET_RUN_MULTI"],
|
58
|
+
retries: RETRIES,
|
37
59
|
config: {}
|
38
60
|
)
|
39
61
|
@repo_name = repo_name
|
@@ -46,6 +68,13 @@ module Hatchet
|
|
46
68
|
@buildpacks = buildpack || buildpacks || buildpack_url || self.class.default_buildpack
|
47
69
|
@buildpacks = Array(@buildpacks)
|
48
70
|
@buildpacks.map! {|b| b == :default ? self.class.default_buildpack : b}
|
71
|
+
@run_multi = run_multi
|
72
|
+
@max_retries_count = retries
|
73
|
+
|
74
|
+
if run_multi && !ENV["HATCHET_EXPENSIVE_MODE"]
|
75
|
+
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"
|
76
|
+
end
|
77
|
+
@run_multi_array = []
|
49
78
|
@already_in_dir = nil
|
50
79
|
@app_is_setup = nil
|
51
80
|
|
@@ -102,7 +131,7 @@ module Hatchet
|
|
102
131
|
end
|
103
132
|
|
104
133
|
def add_database(plan_name = 'heroku-postgresql:dev', match_val = "HEROKU_POSTGRESQL_[A-Z]+_URL")
|
105
|
-
|
134
|
+
max_retries_count.times.retry do
|
106
135
|
# heroku.post_addon(name, plan_name)
|
107
136
|
api_rate_limit.call.addon.create(name, plan: plan_name )
|
108
137
|
_, value = get_config.detect {|k, v| k.match(/#{match_val}/) }
|
@@ -127,6 +156,22 @@ module Hatchet
|
|
127
156
|
else
|
128
157
|
command = command.to_s
|
129
158
|
end
|
159
|
+
|
160
|
+
heroku_command = build_heroku_command(command, options)
|
161
|
+
|
162
|
+
allow_run_multi! if @run_multi
|
163
|
+
|
164
|
+
output = ""
|
165
|
+
|
166
|
+
ShellThrottle.new(platform_api: @platform_api).call do |throttle|
|
167
|
+
output = `#{heroku_command}`
|
168
|
+
throw(:throttle) if output.match?(/reached the API rate limit/)
|
169
|
+
end
|
170
|
+
|
171
|
+
return output
|
172
|
+
end
|
173
|
+
|
174
|
+
private def build_heroku_command(command, options = {})
|
130
175
|
command = command.shellescape unless options.delete(:raw)
|
131
176
|
|
132
177
|
default_options = { "app" => name, "exit-code" => nil }
|
@@ -136,16 +181,77 @@ module Hatchet
|
|
136
181
|
arg << "=#{v.to_s.shellescape}" unless v.nil? # nil means we include the option without an argument
|
137
182
|
arg
|
138
183
|
end.join(" ")
|
139
|
-
heroku_command = "heroku run #{heroku_options} -- #{command}"
|
140
184
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
185
|
+
"heroku run #{heroku_options} -- #{command}"
|
186
|
+
end
|
187
|
+
|
188
|
+
private def allow_run_multi!
|
189
|
+
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
|
190
|
+
|
191
|
+
@run_multi_is_setup ||= platform_api.formation.update(name, "web", {"size" => "Standard-1X"})
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
# Allows multiple commands to be run concurrently in the background.
|
196
|
+
#
|
197
|
+
# WARNING! Using the feature requres that the underlying app is not on the "free" Heroku
|
198
|
+
# tier. This requires scaling up the dyno which is not free. If an app is
|
199
|
+
# scaled up and left in that state it can incur large costs.
|
200
|
+
#
|
201
|
+
# Enabling this feature should be done with extreme caution.
|
202
|
+
#
|
203
|
+
# Example:
|
204
|
+
#
|
205
|
+
# Hatchet::Runner.new("default_ruby", run_multi: true)
|
206
|
+
# app.run_multi("ls") { |out| expect(out).to include("Gemfile") }
|
207
|
+
# app.run_multi("ruby -v") { |out| expect(out).to include("ruby") }
|
208
|
+
# end
|
209
|
+
#
|
210
|
+
# This example will run `heroku run ls` as well as `ruby -v` at the same time in the background.
|
211
|
+
# The return result will be yielded to the block after they finish running.
|
212
|
+
#
|
213
|
+
# Order of execution is not guaranteed.
|
214
|
+
#
|
215
|
+
# If you need to assert a command was successful, you can yield a second status object like this:
|
216
|
+
#
|
217
|
+
# Hatchet::Runner.new("default_ruby", run_multi: true)
|
218
|
+
# app.run_multi("ls") do |out, status|
|
219
|
+
# expect(status.success?).to be_truthy
|
220
|
+
# expect(out).to include("Gemfile")
|
221
|
+
# end
|
222
|
+
# app.run_multi("ruby -v") do |out, status|
|
223
|
+
# expect(status.success?).to be_truthy
|
224
|
+
# expect(out).to include("ruby")
|
225
|
+
# end
|
226
|
+
# end
|
227
|
+
def run_multi(command, options = {}, &block)
|
228
|
+
raise "Block required" if block.nil?
|
229
|
+
allow_run_multi!
|
230
|
+
|
231
|
+
run_thread = Thread.new do
|
232
|
+
heroku_command = build_heroku_command(command, options)
|
233
|
+
|
234
|
+
out = nil
|
235
|
+
status = nil
|
236
|
+
ShellThrottle.new(platform_api: @platform_api).call do |throttle|
|
237
|
+
out = `#{heroku_command}`
|
238
|
+
throw(:throttle) if output.match?(/reached the API rate limit/)
|
239
|
+
status = $?
|
240
|
+
end
|
241
|
+
|
242
|
+
yield out, status
|
243
|
+
|
244
|
+
# if block.arity == 1
|
245
|
+
# block.call(out)
|
246
|
+
# else
|
247
|
+
# block.call(out, status)
|
248
|
+
# end
|
148
249
|
end
|
250
|
+
run_thread.abort_on_exception = true
|
251
|
+
|
252
|
+
@run_multi_array << run_thread
|
253
|
+
|
254
|
+
true
|
149
255
|
end
|
150
256
|
|
151
257
|
# set debug: true when creating app if you don't want it to be
|
@@ -162,24 +268,26 @@ module Hatchet
|
|
162
268
|
alias :no_debug? :not_debugging?
|
163
269
|
|
164
270
|
def deployed?
|
165
|
-
# !heroku.get_ps(name).body.detect {|ps| ps["process"].include?("web") }.nil?
|
166
271
|
api_rate_limit.call.formation.list(name).detect {|ps| ps["type"] == "web"}
|
167
272
|
end
|
168
273
|
|
169
274
|
def create_app
|
170
275
|
3.times.retry do
|
171
276
|
begin
|
172
|
-
# heroku.post_app({ name: name, stack: stack }.delete_if {|k,v| v.nil? })
|
173
277
|
hash = { name: name, stack: stack }
|
174
278
|
hash.delete_if { |k,v| v.nil? }
|
175
|
-
|
279
|
+
heroku_api_create_app(hash)
|
176
280
|
rescue => e
|
177
|
-
@reaper.cycle
|
281
|
+
@reaper.cycle(app_exception_message: e.message)
|
178
282
|
raise e
|
179
283
|
end
|
180
284
|
end
|
181
285
|
end
|
182
286
|
|
287
|
+
private def heroku_api_create_app(hash)
|
288
|
+
api_rate_limit.call.app.create(hash)
|
289
|
+
end
|
290
|
+
|
183
291
|
def update_stack(stack_name)
|
184
292
|
@stack = stack_name
|
185
293
|
api_rate_limit.call.app.update(name, build_stack: @stack)
|
@@ -210,7 +318,7 @@ module Hatchet
|
|
210
318
|
end
|
211
319
|
|
212
320
|
def commit!
|
213
|
-
local_cmd_exec!('git add .; git commit -m next')
|
321
|
+
local_cmd_exec!('git add .; git commit --allow-empty -m next')
|
214
322
|
end
|
215
323
|
|
216
324
|
def push_without_retry!
|
@@ -219,11 +327,15 @@ module Hatchet
|
|
219
327
|
|
220
328
|
def teardown!
|
221
329
|
return false unless @app_is_setup
|
222
|
-
|
223
|
-
|
224
|
-
|
330
|
+
|
331
|
+
if @run_multi_is_setup
|
332
|
+
@run_multi_array.map(&:join)
|
333
|
+
platform_api.formation.update(name, "web", {"size" => "free"})
|
225
334
|
end
|
226
|
-
|
335
|
+
|
336
|
+
ensure
|
337
|
+
@app_update_info = platform_api.app.update(name, { maintenance: true }) if @app_is_setup
|
338
|
+
@reaper.cycle if @app_is_setup
|
227
339
|
end
|
228
340
|
|
229
341
|
def in_directory(directory = self.directory)
|
@@ -265,10 +377,6 @@ module Hatchet
|
|
265
377
|
end
|
266
378
|
end
|
267
379
|
|
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
380
|
def deploy(&block)
|
273
381
|
in_directory do
|
274
382
|
self.setup!
|
@@ -276,16 +384,16 @@ module Hatchet
|
|
276
384
|
block.call(self, api_rate_limit.call, output) if block_given?
|
277
385
|
end
|
278
386
|
ensure
|
279
|
-
self.teardown!
|
387
|
+
self.teardown! if block_given?
|
280
388
|
end
|
281
389
|
|
282
390
|
def push
|
283
|
-
|
284
|
-
|
391
|
+
retry_count = allow_failure? ? 1 : max_retries_count
|
392
|
+
retry_count.times.retry do |attempt|
|
285
393
|
begin
|
286
394
|
@output = self.push_without_retry!
|
287
395
|
rescue StandardError => error
|
288
|
-
puts retry_error_message(error, attempt
|
396
|
+
puts retry_error_message(error, attempt) unless retry_count == 1
|
289
397
|
raise error
|
290
398
|
end
|
291
399
|
end
|
@@ -294,10 +402,10 @@ module Hatchet
|
|
294
402
|
alias :push_with_retry :push
|
295
403
|
alias :push_with_retry! :push_with_retry
|
296
404
|
|
297
|
-
def retry_error_message(error, attempt
|
405
|
+
def retry_error_message(error, attempt)
|
298
406
|
attempt += 1
|
299
|
-
return "" if attempt ==
|
300
|
-
msg = "\nRetrying failed Attempt ##{attempt}/#{
|
407
|
+
return "" if attempt == max_retries_count
|
408
|
+
msg = "\nRetrying failed Attempt ##{attempt}/#{max_retries_count} to push for '#{name}' due to error: \n"<<
|
301
409
|
"#{error.class} #{error.message}\n #{error.backtrace.join("\n ")}"
|
302
410
|
return msg
|
303
411
|
end
|
@@ -316,7 +424,7 @@ module Hatchet
|
|
316
424
|
|
317
425
|
def run_ci(timeout: 300, &block)
|
318
426
|
in_directory do
|
319
|
-
|
427
|
+
max_retries_count.times.retry do
|
320
428
|
result = create_pipeline
|
321
429
|
@pipeline_id = result["id"]
|
322
430
|
end
|
@@ -327,7 +435,7 @@ module Hatchet
|
|
327
435
|
# that's why we create an app explictly (or maybe it already exists), and then associate it with with the pipeline
|
328
436
|
# the app will be auto cleaned up later
|
329
437
|
self.setup!
|
330
|
-
|
438
|
+
max_retries_count.times.retry do
|
331
439
|
couple_pipeline(@name, @pipeline_id)
|
332
440
|
end
|
333
441
|
|
@@ -340,12 +448,13 @@ module Hatchet
|
|
340
448
|
api_rate_limit: api_rate_limit
|
341
449
|
)
|
342
450
|
|
343
|
-
|
451
|
+
max_retries_count.times.retry do
|
344
452
|
test_run.create_test_run
|
345
453
|
end
|
346
454
|
test_run.wait!(&block)
|
347
455
|
end
|
348
456
|
ensure
|
457
|
+
teardown! if block_given?
|
349
458
|
delete_pipeline(@pipeline_id) if @pipeline_id
|
350
459
|
@pipeline_id = nil
|
351
460
|
end
|
@@ -381,7 +490,6 @@ module Hatchet
|
|
381
490
|
end
|
382
491
|
|
383
492
|
def platform_api
|
384
|
-
puts "Deprecated: use `api_rate_limit.call` instead of platform_api"
|
385
493
|
api_rate_limit
|
386
494
|
return @platform_api
|
387
495
|
end
|
@@ -433,3 +541,4 @@ module Hatchet
|
|
433
541
|
end
|
434
542
|
end
|
435
543
|
|
544
|
+
require_relative 'shell_throttle.rb'
|