heroku_hatchet 5.0.2 → 7.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +21 -1
  3. data/.gitignore +2 -0
  4. data/CHANGELOG.md +36 -1
  5. data/README.md +790 -164
  6. data/bin/hatchet +19 -7
  7. data/etc/ci_setup.rb +16 -12
  8. data/etc/setup_heroku.sh +0 -2
  9. data/hatchet.gemspec +4 -6
  10. data/hatchet.json +6 -2
  11. data/hatchet.lock +12 -8
  12. data/lib/hatchet.rb +1 -2
  13. data/lib/hatchet/api_rate_limit.rb +13 -24
  14. data/lib/hatchet/app.rb +150 -41
  15. data/lib/hatchet/config.rb +1 -1
  16. data/lib/hatchet/git_app.rb +28 -1
  17. data/lib/hatchet/reaper.rb +159 -56
  18. data/lib/hatchet/reaper/app_age.rb +49 -0
  19. data/lib/hatchet/reaper/reaper_throttle.rb +55 -0
  20. data/lib/hatchet/shell_throttle.rb +71 -0
  21. data/lib/hatchet/test_run.rb +16 -9
  22. data/lib/hatchet/version.rb +1 -1
  23. data/{test → repo_fixtures}/different-folder-for-checked-in-repos/default_ruby/Gemfile +0 -0
  24. data/spec/hatchet/allow_failure_git_spec.rb +55 -0
  25. data/spec/hatchet/app_spec.rb +226 -0
  26. data/spec/hatchet/ci_spec.rb +67 -0
  27. data/spec/hatchet/config_spec.rb +34 -0
  28. data/spec/hatchet/edit_repo_spec.rb +17 -0
  29. data/spec/hatchet/git_spec.rb +9 -0
  30. data/spec/hatchet/heroku_api_spec.rb +30 -0
  31. data/spec/hatchet/local_repo_spec.rb +26 -0
  32. data/spec/hatchet/lock_spec.rb +81 -0
  33. data/spec/spec_helper.rb +25 -0
  34. data/spec/unit/reaper_spec.rb +169 -0
  35. data/spec/unit/shell_throttle.rb +28 -0
  36. metadata +43 -87
  37. data/.travis.yml +0 -16
  38. data/test/fixtures/buildpacks/null-buildpack/bin/compile +0 -4
  39. data/test/fixtures/buildpacks/null-buildpack/bin/detect +0 -5
  40. data/test/fixtures/buildpacks/null-buildpack/bin/release +0 -3
  41. data/test/fixtures/buildpacks/null-buildpack/hatchet.json +0 -4
  42. data/test/fixtures/buildpacks/null-buildpack/readme.md +0 -41
  43. data/test/hatchet/allow_failure_git_test.rb +0 -16
  44. data/test/hatchet/app_test.rb +0 -96
  45. data/test/hatchet/ci_four_test.rb +0 -19
  46. data/test/hatchet/ci_test.rb +0 -11
  47. data/test/hatchet/ci_three_test.rb +0 -20
  48. data/test/hatchet/ci_too_test.rb +0 -19
  49. data/test/hatchet/config_test.rb +0 -51
  50. data/test/hatchet/edit_repo_test.rb +0 -20
  51. data/test/hatchet/git_test.rb +0 -16
  52. data/test/hatchet/heroku_api_test.rb +0 -30
  53. data/test/hatchet/labs_test.rb +0 -20
  54. data/test/hatchet/local_repo_test.rb +0 -26
  55. data/test/hatchet/lock_test.rb +0 -9
  56. data/test/hatchet/multi_cmd_runner_test.rb +0 -30
  57. data/test/test_helper.rb +0 -28
@@ -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].present?
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
- commit = commit_at_directory(directory)
79
- lock_hash[directory] = commit
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
- raise "No such file found `hatchet.lock` please run `$ bundle exec hatchet lock`"
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} master --quiet")
167
+ cmd("cd #{path} && git pull --rebase #{git_repo} --quiet")
156
168
  end
157
169
 
158
170
  def clone(path, git_repo, quiet: true)
@@ -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
- `chmod 0600 "$HOME/.netrc"`
25
+ run_cmd 'chmod 0600 "$HOME/.netrc"'
18
26
  end
19
27
  end
20
28
 
21
- [
22
- "bundle exec hatchet ci:install_heroku",
23
- "bundle exec hatchet install",
24
- "git config --get user.email > /dev/null || git config --global user.email #{ENV.fetch('HEROKU_API_USER').shellescape}",
25
- "git config --get user.name > /dev/null || git config --global user.name 'BuildpackTester'",
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
+
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
3
  set -euo pipefail
4
- sudo apt-get -qq update
5
- sudo apt-get install software-properties-common
6
4
  curl --fail --retry 3 --retry-delay 1 --connect-timeout 3 --max-time 30 https://cli-assets.heroku.com/install-ubuntu.sh | sh
@@ -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", "~> 2"
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 "minitest", ">= 5.1"
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 "parallel_tests", ">= 2"
30
+ gem.add_development_dependency "parallel_split_test"
33
31
  gem.add_development_dependency "travis", ">= 1"
34
- gem.add_development_dependency "m"
32
+ gem.add_development_dependency "rspec-retry"
35
33
  end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "hatchet": {
3
- "directory": "test/fixtures"
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": ["sharpstone/lock_fail"]
13
+ "lock": [
14
+ "sharpstone/lock_fail",
15
+ "sharpstone/lock_fail_master",
16
+ "sharpstone/lock_fail_main"
17
+ ]
14
18
  }
@@ -1,15 +1,19 @@
1
1
  ---
2
- - - test/fixtures/repos/bundler/no_lockfile
2
+ - - repo_fixtures/repos/bundler/no_lockfile
3
3
  - 1947ce9a9c276d5df1c323b2ad78d1d85c7ab4c0
4
- - - test/fixtures/repos/ci/rails5_ci_fails_no_database
4
+ - - repo_fixtures/repos/ci/rails5_ci_fails_no_database
5
5
  - 3044f05febdfbbe656f0f5113cf5968ca07e34fd
6
- - - test/fixtures/repos/ci/rails5_ruby_schema_format
7
- - 3e63c3e13f435cf4ab11265e9abd161cc28cc552
8
- - - test/fixtures/repos/default/default_ruby
6
+ - - repo_fixtures/repos/ci/rails5_ruby_schema_format
7
+ - d76be86c66ae8f45ec611fb2c4d8eb3adac0ad4b
8
+ - - repo_fixtures/repos/default/default_ruby
9
9
  - 6e642963acec0ff64af51bd6fba8db3c4176ed6e
10
- - - test/fixtures/repos/lock/lock_fail
10
+ - - repo_fixtures/repos/lock/lock_fail
11
11
  - da748a59340be8b950e7bbbfb32077eb67d70c3c
12
- - - test/fixtures/repos/rails2/rails2blog
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
- - - test/fixtures/repos/rails3/rails3_mri_193
18
+ - - repo_fixtures/repos/rails3/rails3_mri_193
15
19
  - 88c5d0d067cfd11e4452633994a85b04627ae8c7
@@ -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
- APP_PREFIX = (ENV['HATCHET_APP_PREFIX'] || "hatchet-t-")
10
+ APP_PREFIX = (ENV['HATCHET_APP_PREFIX'] || "hatchet-t-")
12
11
  end
13
12
 
14
13
  require 'hatchet/version'
@@ -1,14 +1,11 @@
1
- # Wraps platform-api and adds API rate limits
1
+ # Legacy class
2
2
  #
3
- # Instead of:
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
- @called = 0
32
- @capacity = @platform_api.rate_limit.info["remaining"]
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
@@ -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
- def initialize(app, output)
16
- msg = "Could not deploy '#{app.name}' (#{app.repo_name}) using '#{app.class}' at path: '#{app.directory}'\n" <<
17
- " if this was expected add `allow_failure: true` to your deploy hash.\n" <<
18
- "output:\n" <<
19
- "#{output}"
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
- Hatchet::RETRIES.times.retry do
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
- if block_given?
142
- STDERR.puts "Using App#run with a block is deprecated, support for ReplRunner is being removed.\n#{caller}"
143
- # When we deprecated this we can get rid of the "cmd_type" from the method signature
144
- require 'repl_runner'
145
- ReplRunner.new(cmd_type, heroku_command, options).run(&block)
146
- else
147
- `#{heroku_command}`
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
- api_rate_limit.call.app.create(hash)
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
- if debugging?
223
- puts "Debugging App:#{name}"
224
- return false
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
- @reaper.cycle
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
- max_retries = @allow_failure ? 1 : RETRIES
284
- max_retries.times.retry do |attempt|
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, max_retries)
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, max_retries)
405
+ def retry_error_message(error, attempt)
298
406
  attempt += 1
299
- return "" if attempt == max_retries
300
- msg = "\nRetrying failed Attempt ##{attempt}/#{max_retries} to push for '#{name}' due to error: \n"<<
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
- Hatchet::RETRIES.times.retry do
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
- Hatchet::RETRIES.times.retry do
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
- Hatchet::RETRIES.times.retry do
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'