heroku_hatchet 7.2.0 → 7.3.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2dd4a304828fca43576fed5a71b7be305142c3220c7dd020bff44b17a4a070a0
4
- data.tar.gz: c0dee0a8ffb794f10a3dbb96beeafa26dd5f95b489eb9638a40bf408dce44c11
3
+ metadata.gz: a45ee1ab2021838074b4f288d70aab769a71961cbae7cd12af67866e5558bb64
4
+ data.tar.gz: 0c99b826bfc797632924e151b796590f1ddbd07ff50e9eea2dc816b9991fb69d
5
5
  SHA512:
6
- metadata.gz: f6fa0cef6167e9b4985b08f01e42f33410632d4dfb083551d2641e7009745a079044f606cc4456ed8b8f0dfb0ec087b9b3197d1dabb9a0c41cd7f96ee978e22c
7
- data.tar.gz: 50689c87372d4df2fc7d354ccc0f6f870285283869a190f3272f76d0d238e2d3cd13ca485ca6436916a6976c847fbd2be057fbe4e99ed1aa8ac97abdfef9eeb9
6
+ metadata.gz: 940afbe59938c35c5bee92ac43dabe66e0e68f7cefbc35b662667eaecec343f04cc83e93418e7302a47ce2f955b2d7269073e167614a475dc91d69e6bee94d82
7
+ data.tar.gz: 204098d3744d64717141ca4d8f754324e761981b3d2d3bc9710e3b18e5532c97a1028813e1d7e2d295d35fb36e1b09ab78761a370af6490849de991ca50045ce
data/.circleci/config.yml CHANGED
@@ -29,9 +29,9 @@ references:
29
29
  bundle clean
30
30
 
31
31
  jobs:
32
- "ruby-2.5":
32
+ "ruby-2.2":
33
33
  docker:
34
- - image: circleci/ruby:2.5
34
+ - image: circleci/ruby:2.2
35
35
  steps:
36
36
  - checkout
37
37
  - <<: *restore
@@ -39,9 +39,9 @@ jobs:
39
39
  - <<: *hatchet_setup
40
40
  - <<: *unit
41
41
  - <<: *save
42
- "ruby-2.6":
42
+ "ruby-2.5":
43
43
  docker:
44
- - image: circleci/ruby:2.6
44
+ - image: circleci/ruby:2.5
45
45
  steps:
46
46
  - checkout
47
47
  - <<: *restore
@@ -64,6 +64,6 @@ workflows:
64
64
  version: 2
65
65
  build:
66
66
  jobs:
67
+ - "ruby-2.2"
67
68
  - "ruby-2.5"
68
- - "ruby-2.6"
69
69
  - "ruby-2.7"
data/CHANGELOG.md CHANGED
@@ -1,6 +1,51 @@
1
1
  ## HEAD
2
2
 
3
- ## 7.1.4
3
+ ## 7.3.4
4
+
5
+ - Memoize `Hatchet::App.default_buildpack` (https://github.com/heroku/hatchet/pull/183)
6
+ - Fix repository path lookup when custom Hatchet directory set (https://github.com/heroku/hatchet/issues/181)
7
+ - Handle additional variations of rate limit error messages (https://github.com/heroku/hatchet/pull/182)
8
+ - Add `HATCHET_DEFAULT_STACK` for configuring the default stack (https://github.com/heroku/hatchet/pull/184)
9
+ - Fix typo in the reaper `"Duplicate destroy attempted"` message (https://github.com/heroku/hatchet/pull/175)
10
+ - Set `init.defaultBranch` in ci:setup to suppress `git init` warning in Git 2.30+ (https://github.com/heroku/hatchet/issues/172)
11
+ - Switch `heroku ci:install_heroku` to the Heroku CLI standalone installer rather than the APT install method (https://github.com/heroku/hatchet/issues/171)
12
+
13
+ ## 7.3.3
14
+
15
+ - Quiet personal tokens (https://github.com/heroku/hatchet/pull/148)
16
+
17
+ ## 7.3.2
18
+
19
+ - Fix App#in_directory_fork not receiving debugging output when an error is raised (https://github.com/heroku/hatchet/pull/146)
20
+ - Do not create CI tarball inside cwd to prevent tar "file changed as we read it" warnings.
21
+
22
+ ## 7.3.1
23
+
24
+ - Fix Ruby incompatibility introduced by using `&.` and `rescue` without
25
+ `begin`/`end` without a `required_ruby_version` in hatchet.gemspec.
26
+ (https://github.com/heroku/hatchet/pull/139)
27
+
28
+ ## 7.3.0
29
+
30
+ - Deprecations
31
+ - Deprecation: Calling `App#before_deploy` as a way to clear/replace the existing block should now be done with `App#before_deploy(:replace)` (https://github.com/heroku/hatchet/pull/126)
32
+ - Deprecation: HATCHET_BUILDPACK_BASE default (https://github.com/heroku/hatchet/pull/133)
33
+ - Deprecation: App#directory (https://github.com/heroku/hatchet/pull/135)
34
+
35
+ - Flappy test improvements
36
+ - Increase CI timeout limit to 900 seconds (15 minutes) (https://github.com/heroku/hatchet/pull/137)
37
+ - Empty string returns from App#run now trigger retries (https://github.com/heroku/hatchet/pull/132)
38
+ - Rescue 403 on pipeline delete (https://github.com/heroku/hatchet/pull/130)
39
+ - Additional rate throttle cases handled (https://github.com/heroku/hatchet/pull/128)
40
+
41
+ - Usability
42
+ - Annotate rspec expectation failures inside of deploy blocks with hatchet debug information (https://github.com/heroku/hatchet/pull/136)
43
+ - Hatchet#new raises a helpful error when no source code location is provided (https://github.com/heroku/hatchet/pull/134)
44
+ - Lazy evaluation of HATCHET_BUILDPACK_BASE env var (https://github.com/heroku/hatchet/pull/133)
45
+ - Allow multiple `App#before_deploy` blocks to be set and called (https://github.com/heroku/hatchet/pull/126)
46
+ - Performance improvement when running without an explicit HEROKU_API_KEY set (https://github.com/heroku/hatchet/pull/128)
47
+
48
+ ## 7.2.0
4
49
 
5
50
  - App#setup! no longer modifies files on disk. (https://github.com/heroku/hatchet/pull/125)
6
51
  - Add `$ hatchet init` command for bootstrapping new projects (https://github.com/heroku/hatchet/pull/123)
@@ -309,7 +354,6 @@
309
354
 
310
355
  after_script: bundle exec rake hatchet:teardown_travis
311
356
 
312
-
313
357
  ## 0.1.1
314
358
 
315
359
  - Allow auto retries of pushes by setting environment variable `HATCHET_RETRIES=3`
data/README.md CHANGED
@@ -504,7 +504,7 @@ The `Hatchet::Runner.new` takes several arguments.
504
504
 
505
505
  ### Init options
506
506
 
507
- - stack (String): The stack you want to deploy to on Heroku.
507
+ - stack (String): The Heroku [stack](https://devcenter.heroku.com/articles/stack) to use for the app. If this is not set, the stack will be determined from `HATCHET_DEFAULT_STACK`, or else the Heroku platform's [default stack](https://devcenter.heroku.com/articles/stack#default-stack).
508
508
 
509
509
  ```ruby
510
510
  Hatchet::Runner.new("default_ruby", stack: "heroku-16").deploy do |app|
@@ -525,7 +525,7 @@ end
525
525
 
526
526
  In this example, the app would use the nodejs buildpack, and then `:default` gets replaced by your Git url and branch name.
527
527
 
528
- - before_deploy (Block): Instead of using the `tap` syntax you can provide a block directly to hatchet app initialization:
528
+ - before_deploy (Block): Instead of using the `tap` syntax you can provide a block directly to hatchet app initialization. Example:
529
529
 
530
530
  ```ruby
531
531
  Hatchet::Runner.new("default_ruby", before_deploy: ->{ FileUtils.touch("foo.txt")}).deploy do
@@ -598,6 +598,7 @@ app.get_config("DEPLOY_TASKS") # => "run:bloop"
598
598
 
599
599
  - `app.set_lab()`: Enables the specified lab/feature on the app
600
600
  - `app.add_database()`: adds a database to the app, defaults to the "dev" database
601
+ - `app.update_stack()`: Change the app's stack to that specified (for example `"heroku-20"`). Will take effect on the next build.
601
602
  - `app.run()`: Runs a `heroku run bash` session with the arguments, covered above.
602
603
  - `app.run_multi()`: Runs a `heroku run bash` session in the background and yields the results. This requires the `run_multi` flag of the app to be set to `true`, which will charge your application (the `HATCHET_EXPENSIVE_MODE` env var must also be set to use this feature). Example above.
603
604
  - `app.create_app`: Can be used to manually create the app without deploying it (You probably want `setup!` though)
@@ -630,6 +631,23 @@ Hatchet::Runner.new("default_ruby", before_deploy: before_deploy_proc).deploy do
630
631
  end
631
632
  ```
632
633
 
634
+ You can call multiple blocks by specifying (`:prepend` or `:append`):
635
+
636
+ ```ruby
637
+ Hatchet::Runner.new("default_ruby").tap do |app|
638
+ app.before_deploy do
639
+ FileUtils.touch("foo.txt")
640
+ end
641
+
642
+ app.before_deploy(:append) do
643
+ FileUtils.touch("bar.txt")
644
+ end
645
+ app.deploy do
646
+ end
647
+ end
648
+ ```
649
+
650
+
633
651
  - `app.commit!`: Will updates the contents of your local git dir if you've modified files on disk
634
652
 
635
653
  ```ruby
@@ -660,7 +678,7 @@ end
660
678
  > Note: If you want to execute tests in this temp directory, you likely want to use `in_directory_fork` otherwise, you might accidentally contaminate the current environment's variables if you modify them.
661
679
 
662
680
  - `app.in_directory_fork`: Runs the given block in a temp directory and inside of a forked process, an example given above.
663
- - `app.directory`: Returns the directory of the example application on disk, this is NOT the temp directory that you're currently executing against. It's probably not what you want.
681
+ - `app.original_source_code_directory`: Returns the directory of the example application on disk, this is NOT the temp directory that you're currently executing against. It's probably not what you want.
664
682
  - `app.deploy`: Your main method takes a block to execute after the deploy is successful. If no block is provided, you must manually call `app.teardown!` (see below for an example).
665
683
  - `app.output`: The output contents of the deploy
666
684
  - `app.platform_api`: Returns an instance of the [platform-api Heroku client](https://github.com/heroku/platform-api). If Hatchet doesn't give you access to a part of Heroku that you need, you can likely do it with the platform-api client.
@@ -710,6 +728,7 @@ HATCHET_ALIVE_TTL_MINUTES=7
710
728
  - `HATCHET_APP_LIMIT`: The maximum number of **hatchet** apps that Hatchet will allow in the given account before running the reaper. For local execution, keep this low as you don't want your account dominated by hatchet apps. For CI, you want it to be much larger, 80-100 since it's not competing with non-hatchet apps. Your test runner account needs to be a dedicated account.
711
729
  - `HEROKU_API_KEY`: The API key of your test account user. If you run locally without this set, it will use your personal credentials.
712
730
  - `HEROKU_API_USER`: The email address of your user account. If you run locally without this set, it will use your personal credentials.
731
+ - `HATCHET_DEFAULT_STACK`: The default Heroku stack to be used when an explicit `stack` is not passed to `App.new`. If this is not set, apps will instead use Heroku platform's [default stack](https://devcenter.heroku.com/articles/stack#default-stack).
713
732
  - `HATCHET_RUN_MULTI`: If enabled, this will scale up deployed apps to "standard-1x" once deployed instead of running on the free tier. This enables the `run_multi` method capability, however scaling up is not free. WARNING: Setting this env var will incur charges to your Heroku account. We recommended never to enable this setting unless you work for Heroku. To use this you must also set `HATCHET_EXPENSIVE_MODE=1`
714
733
  - `HATCHET_EXPENSIVE_MODE`: This is intended to be a "safety" environment variable. If it is not set, Hatchet will prevent you from using the `run_multi: true` setting or the `HATCHET_RUN_MULTI` environment variables. There are still ways to incur charges without this feature, but unless you're absolutely confident your test setup will not leave "orphan" apps that are billing you, do not enable this setting. Even then, only set this value if you work for Heroku. To recap WARNING: setting this is expensive.
715
734
 
data/bin/hatchet CHANGED
@@ -52,7 +52,7 @@ class HatchetCLI < Thor
52
52
  Threaded.later do
53
53
  commit = lock_hash[directory]
54
54
  directory = File.expand_path(directory)
55
- if !Dir[directory]&.empty?
55
+ if !(Dir[directory] && Dir[directory].empty?)
56
56
  puts "== pulling '#{git_repo}' into '#{directory}'\n"
57
57
  pull(directory, git_repo)
58
58
  else
data/etc/ci_setup.rb CHANGED
@@ -30,6 +30,8 @@ run_cmd "bundle exec hatchet ci:install_heroku"
30
30
  run_cmd "bundle exec hatchet install"
31
31
  run_cmd "git config --get user.email > /dev/null || git config --global user.email #{ENV.fetch('HEROKU_API_USER').shellescape}"
32
32
  run_cmd "git config --get user.name > /dev/null || git config --global user.name 'BuildpackTester'"
33
+ # Suppress the `git init` warning in Git 2.30+ when no default branch name is set.
34
+ run_cmd "git config --global init.defaultBranch main"
33
35
 
34
36
  puts "== Done =="
35
37
 
data/etc/setup_heroku.sh CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
3
  set -euo pipefail
4
- curl --fail --retry 3 --retry-delay 1 --connect-timeout 3 --max-time 30 https://cli-assets.heroku.com/install-ubuntu.sh | sh
4
+ curl --fail --retry 3 --retry-delay 1 --connect-timeout 3 --max-time 30 https://cli-assets.heroku.com/install.sh | sh
data/hatchet.gemspec CHANGED
@@ -17,17 +17,17 @@ Gem::Specification.new do |gem|
17
17
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
+ gem.required_ruby_version = '>= 2.2.0'
20
21
 
21
22
  gem.add_dependency "platform-api", "~> 3"
22
23
  gem.add_dependency "rrrretry", "~> 1"
23
24
  gem.add_dependency "excon", "~> 0"
24
- gem.add_dependency "thor", "~> 0"
25
+ gem.add_dependency "thor", "~> 1"
25
26
  gem.add_dependency "threaded", "~> 0"
26
27
 
27
28
  gem.add_development_dependency "rspec"
28
29
  gem.add_development_dependency "rake", ">= 10"
29
30
  gem.add_development_dependency "mocha", ">= 1"
30
31
  gem.add_development_dependency "parallel_split_test"
31
- gem.add_development_dependency "travis", ">= 1"
32
32
  gem.add_development_dependency "rspec-retry"
33
33
  end
data/lib/hatchet.rb CHANGED
@@ -19,6 +19,7 @@ require 'hatchet/git_app'
19
19
  require 'hatchet/config'
20
20
  require 'hatchet/api_rate_limit'
21
21
  require 'hatchet/init_project'
22
+ require 'hatchet/heroku_run'
22
23
 
23
24
  module Hatchet
24
25
  RETRIES = Integer(ENV['HATCHET_RETRIES'] || 1)
@@ -45,3 +46,22 @@ module Hatchet
45
46
  end
46
47
  end
47
48
  end
49
+
50
+ unless ::String.instance_methods.include?(:strip_heredoc)
51
+ # We can get rid of this when all rubies can support <<~ syntax
52
+ class ::String
53
+ def strip_heredoc
54
+ gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze)
55
+ end
56
+ end
57
+ end
58
+
59
+ unless ::String.instance_methods.include?(:match?)
60
+ # We can get rid of this when all rubies can support String#match? method
61
+ class ::String
62
+ def match?(value)
63
+ self =~ value
64
+ end
65
+ end
66
+ end
67
+
data/lib/hatchet/app.rb CHANGED
@@ -5,11 +5,15 @@ require 'tmpdir'
5
5
 
6
6
  module Hatchet
7
7
  class App
8
- HATCHET_BUILDPACK_BASE = (ENV['HATCHET_BUILDPACK_BASE'] || "https://github.com/heroku/heroku-buildpack-ruby.git")
8
+ HATCHET_BUILDPACK_BASE = -> {
9
+ ENV.fetch('HATCHET_BUILDPACK_BASE') {
10
+ warn "ENV HATCHET_BUILDPACK_BASE is not set. It currently defaults to the ruby buildpack. In the future this env var will be required"
11
+ "https://github.com/heroku/heroku-buildpack-ruby.git"
12
+ }
13
+ }
9
14
  HATCHET_BUILDPACK_BRANCH = -> { ENV['HATCHET_BUILDPACK_BRANCH'] || ENV['HEROKU_TEST_RUN_BRANCH'] || Hatchet.git_branch }
10
- BUILDPACK_URL = "https://github.com/heroku/heroku-buildpack-ruby.git"
11
15
 
12
- attr_reader :name, :stack, :directory, :repo_name, :app_config, :buildpacks, :reaper, :max_retries_count
16
+ attr_reader :name, :stack, :repo_name, :app_config, :buildpacks, :reaper, :max_retries_count
13
17
 
14
18
  class FailedDeploy < StandardError; end
15
19
 
@@ -18,7 +22,7 @@ module Hatchet
18
22
 
19
23
  def initialize(app, message, output: )
20
24
  @output = output
21
- msg = "Could not deploy '#{app.name}' (#{app.repo_name}) using '#{app.class}' at path: '#{app.directory}'\n"
25
+ msg = "Could not deploy '#{app.name}' (#{app.repo_name}) using '#{app.class}' at path: '#{app.original_source_code_directory}'\n"
22
26
  msg << "if this was expected add `allow_failure: true` to your deploy hash.\n"
23
27
  msg << "#{message}\n"
24
28
  msg << "output:\n"
@@ -32,7 +36,7 @@ module Hatchet
32
36
 
33
37
  def initialize(app, message, output: )
34
38
  @output = output
35
- msg = "Could not release '#{app.name}' (#{app.repo_name}) using '#{app.class}' at path: '#{app.directory}'\n"
39
+ msg = "Could not release '#{app.name}' (#{app.repo_name}) using '#{app.class}' at path: '#{app.original_source_code_directory}'\n"
36
40
  msg << "if this was expected add `allow_failure: true` to your deploy hash.\n"
37
41
  msg << "#{message}\n"
38
42
  msg << "output:\n"
@@ -42,9 +46,10 @@ module Hatchet
42
46
  end
43
47
 
44
48
  SkipDefaultOption = Object.new
49
+ DEFAULT_REPO_NAME = Object.new
45
50
 
46
- def initialize(repo_name,
47
- stack: "",
51
+ def initialize(repo_name = DEFAULT_REPO_NAME,
52
+ stack: ENV["HATCHET_DEFAULT_STACK"],
48
53
  name: default_name,
49
54
  debug: nil,
50
55
  debugging: nil,
@@ -58,6 +63,7 @@ module Hatchet
58
63
  retries: RETRIES,
59
64
  config: {}
60
65
  )
66
+ raise "You tried creating a Hatchet::App instance without source code, pass in a path to an app to deploy or the name of an app in your hatchet.json" if repo_name == DEFAULT_REPO_NAME
61
67
  @repo_name = repo_name
62
68
  @directory = self.config.path_for_name(@repo_name)
63
69
  @name = name
@@ -78,13 +84,37 @@ module Hatchet
78
84
  @already_in_dir = nil
79
85
  @app_is_setup = nil
80
86
 
81
- @before_deploy = before_deploy
87
+ @before_deploy_array = []
88
+ @before_deploy_array << before_deploy if before_deploy
82
89
  @app_config = config
83
90
  @reaper = Reaper.new(api_rate_limit: api_rate_limit)
84
91
  end
85
92
 
93
+ private def test_failure_classes
94
+ class_array = []
95
+ class_array << RSpec::Expectations::ExpectationNotMetError if defined?(RSpec::Expectations::ExpectationNotMetError)
96
+ class_array
97
+ end
98
+
99
+ def annotate_failures
100
+ yield
101
+ rescue *test_failure_classes => e
102
+ raise e, "App: #{name} (#{@repo_name})\n#{e.message}"
103
+ end
104
+
105
+ def directory
106
+ warn "Calling App#directory returns the original location of the app's source code that should not be modified, if this is really what you want use `original_source_code_directory` instead."
107
+ warn caller
108
+ @directory
109
+ end
110
+
111
+ def original_source_code_directory
112
+ @directory
113
+ end
114
+
115
+ @default_buildpack = nil
86
116
  def self.default_buildpack
87
- [HATCHET_BUILDPACK_BASE, HATCHET_BUILDPACK_BRANCH.call].join("#")
117
+ @default_buildpack ||= [HATCHET_BUILDPACK_BASE.call, HATCHET_BUILDPACK_BRANCH.call].join("#")
88
118
  end
89
119
 
90
120
  def allow_failure?
@@ -157,32 +187,17 @@ module Hatchet
157
187
  command = command.to_s
158
188
  end
159
189
 
160
- heroku_command = build_heroku_command(command, options)
161
-
162
190
  allow_run_multi! if @run_multi
163
191
 
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 = {})
175
- command = command.shellescape unless options.delete(:raw)
176
-
177
- default_options = { "app" => name, "exit-code" => nil }
178
- heroku_options = (default_options.merge(options.delete(:heroku) || {})).map do |k,v|
179
- next if v == Hatchet::App::SkipDefaultOption # for forcefully removing e.g. --exit-code, a user can pass this
180
- arg = "--#{k.to_s.shellescape}"
181
- arg << "=#{v.to_s.shellescape}" unless v.nil? # nil means we include the option without an argument
182
- arg
183
- end.join(" ")
192
+ run_obj = Hatchet::HerokuRun.new(
193
+ command,
194
+ app: self,
195
+ retry_on_empty: options.fetch(:retry_on_empty, !ENV["HATCHET_DISABLE_EMPTY_RUN_RETRY"]),
196
+ heroku: options[:heroku],
197
+ raw: options[:raw]
198
+ ).call
184
199
 
185
- "heroku run #{heroku_options} -- #{command}"
200
+ return run_obj.output
186
201
  end
187
202
 
188
203
  private def allow_run_multi!
@@ -191,7 +206,6 @@ module Hatchet
191
206
  @run_multi_is_setup ||= platform_api.formation.update(name, "web", {"size" => "Standard-1X"})
192
207
  end
193
208
 
194
-
195
209
  # Allows multiple commands to be run concurrently in the background.
196
210
  #
197
211
  # WARNING! Using the feature requres that the underlying app is not on the "free" Heroku
@@ -229,23 +243,15 @@ module Hatchet
229
243
  allow_run_multi!
230
244
 
231
245
  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
246
+ run_obj = Hatchet::HerokuRun.new(
247
+ command,
248
+ app: self,
249
+ retry_on_empty: options.fetch(:retry_on_empty, !ENV["HATCHET_DISABLE_EMPTY_RUN_RETRY"]),
250
+ heroku: options[:heroku],
251
+ raw: options[:raw]
252
+ ).call
253
+
254
+ yield run_obj.output, run_obj.status
249
255
  end
250
256
  run_thread.abort_on_exception = true
251
257
 
@@ -318,9 +324,25 @@ module Hatchet
318
324
  end
319
325
  end
320
326
 
321
- def before_deploy(&block)
327
+ def before_deploy(behavior = :default, &block)
322
328
  raise "block required" unless block
323
- @before_deploy = block
329
+
330
+ case behavior
331
+ when :default, :replace
332
+ if @before_deploy_array.any? && behavior == :default
333
+ STDERR.puts "Calling App#before_deploy multiple times will overwrite the contents. If you intended this: use `App#before_deploy(:replace)`"
334
+ STDERR.puts "In the future, calling this method with no arguements will default to `App#before_deploy(:append)` behavior.\n#{caller.join("\n")}"
335
+ end
336
+
337
+ @before_deploy_array.clear
338
+ @before_deploy_array << block
339
+ when :prepend
340
+ @before_deploy_array = [block] + @before_deploy_array
341
+ when :append
342
+ @before_deploy_array << block
343
+ else
344
+ raise "Unrecognized behavior: #{behavior.inspect}, valid inputs are :append, :prepend, and :replace"
345
+ end
324
346
 
325
347
  self
326
348
  end
@@ -346,14 +368,14 @@ module Hatchet
346
368
  @reaper.cycle if @app_is_setup
347
369
  end
348
370
 
349
- def in_directory(directory = self.directory)
350
- yield directory and return if @already_in_dir
371
+ def in_directory
372
+ yield and return if @already_in_dir
351
373
 
352
374
  Dir.mktmpdir do |tmpdir|
353
- FileUtils.cp_r("#{directory}/.", "#{tmpdir}/.")
375
+ FileUtils.cp_r("#{original_source_code_directory}/.", "#{tmpdir}/.")
354
376
  Dir.chdir(tmpdir) do
355
377
  @already_in_dir = true
356
- yield directory
378
+ yield
357
379
  @already_in_dir = false
358
380
  end
359
381
  end
@@ -366,8 +388,8 @@ module Hatchet
366
388
  def in_directory_fork(&block)
367
389
  Tempfile.create("stdout") do |tmp_file|
368
390
  pid = fork do
369
- $stdout.reopen(tmp_file, "w")
370
- $stderr.reopen(tmp_file, "w")
391
+ $stdout.reopen(tmp_file, "a")
392
+ $stderr.reopen(tmp_file, "a")
371
393
  $stdout.sync = true
372
394
  $stderr.sync = true
373
395
  in_directory do |dir|
@@ -378,7 +400,7 @@ module Hatchet
378
400
  Process.waitpid(pid)
379
401
 
380
402
  if $?.success?
381
- puts File.read(tmp_file)
403
+ print File.read(tmp_file)
382
404
  else
383
405
  raise File.read(tmp_file)
384
406
  end
@@ -387,9 +409,11 @@ module Hatchet
387
409
 
388
410
  def deploy(&block)
389
411
  in_directory do
390
- in_dir_setup!
391
- self.push_with_retry!
392
- block.call(self, api_rate_limit.call, output) if block_given?
412
+ annotate_failures do
413
+ in_dir_setup!
414
+ self.push_with_retry!
415
+ block.call(self, api_rate_limit.call, output) if block_given?
416
+ end
393
417
  end
394
418
  ensure
395
419
  self.teardown! if block_given?
@@ -423,21 +447,21 @@ module Hatchet
423
447
  end
424
448
 
425
449
  def api_key
426
- @api_key ||= ENV['HEROKU_API_KEY'] || `heroku auth:token`.chomp
450
+ @api_key ||= ENV['HEROKU_API_KEY'] ||= `heroku auth:token 2> /dev/null`.chomp
427
451
  end
428
452
 
429
453
  def heroku
430
454
  raise "Not supported, use `platform_api` instead."
431
455
  end
432
456
 
433
- def run_ci(timeout: 300, &block)
457
+ def run_ci(timeout: 900, &block)
434
458
  in_directory do
435
459
  max_retries_count.times.retry do
436
460
  result = create_pipeline
437
461
  @pipeline_id = result["id"]
438
462
  end
439
463
 
440
- # when the CI run finishes, the associated ephemeral app created for the test run internally gets removed almost immediately
464
+ # When the CI run finishes, the associated ephemeral app created for the test run internally gets removed almost immediately
441
465
  # the system then sees a pipeline with no apps, and deletes it, also almost immediately
442
466
  # that would, with bad timing, mean our test run info poll in wait! would 403, and/or the delete_pipeline at the end
443
467
  # that's why we create an app explictly (or maybe it already exists), and then associate it with with the pipeline
@@ -495,6 +519,9 @@ module Hatchet
495
519
 
496
520
  def delete_pipeline(pipeline_id)
497
521
  api_rate_limit.call.pipeline.delete(pipeline_id)
522
+ rescue Excon::Error::Forbidden
523
+ warn "Error deleting pipeline id: #{pipeline_id.inspect}, status: 403"
524
+ # Means the pipeline likely doesn't exist, not sure why though
498
525
  end
499
526
 
500
527
  def platform_api
@@ -534,14 +561,17 @@ module Hatchet
534
561
  end
535
562
 
536
563
  private def call_before_deploy
537
- return unless @before_deploy
538
- raise "before_deploy: #{@before_deploy.inspect} must respond to :call" unless @before_deploy.respond_to?(:call)
539
- raise "before_deploy: #{@before_deploy.inspect} must respond to :arity" unless @before_deploy.respond_to?(:arity)
564
+ return unless @before_deploy_array.any?
540
565
 
541
- if @before_deploy.arity == 1
542
- @before_deploy.call(self)
543
- else
544
- @before_deploy.call
566
+ @before_deploy_array.each do |block|
567
+ raise "before_deploy: #{block.inspect} must respond to :call" unless block.respond_to?(:call)
568
+ raise "before_deploy: #{block.inspect} must respond to :arity" unless block.respond_to?(:arity)
569
+
570
+ if block.arity == 1
571
+ block.call(self)
572
+ else
573
+ block.call
574
+ end
545
575
  end
546
576
 
547
577
  commit! if needs_commit?