heroku_hatchet 7.1.2 → 7.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1896d0a09cfae27ff55b130431dcab677ab7ee5a11851c52d23f32abb1d773db
4
- data.tar.gz: 50827926cffb6d14894c4fac985bdaf9ac88645a4640e8d3e203cad6ba405e90
3
+ metadata.gz: 960b3c8f2eee0b1252df5b2e6ce23b718de130d099f913812bd5fbb196c3b006
4
+ data.tar.gz: 3dec91bb9b7de86d3aef2ef78086f29239f078d1c6c004244b3ead7aaf139a58
5
5
  SHA512:
6
- metadata.gz: '07895d1b500d6f1d40cd50786440aa4156703ebae3c9eb92af96e1662400f66d493ce413d104e882d338e6b8990136f8a2b967130298c7d154e4b2abd77c6227'
7
- data.tar.gz: 82766796bfabe96afed53caec7aa1e78d4a788583a544c2a07130eb66265f598f7c1d15b693bd6b95f79dea15c3c8336bb6bd4d0260c8fd802c067e5fd531331
6
+ metadata.gz: b19e39c4963c64a5448f5b4ff4961722e1c17b020d3c6a8f3ef102a1bb136ba2feb504afddc8cbb6a286f384806776ebfdfebb76834f3d1a3eb37af36cbe4532
7
+ data.tar.gz: 3cc4cb2320f711e1d5fbb2024dc7a7ff1d734158b2f89322104a162fc00a2cb21940cf520f64e679b948c1a9be4bedeb3735c447c8287496438d1265bd372104
@@ -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"
@@ -1,7 +1,8 @@
1
1
  name: Check Changelog
2
2
 
3
- on: [pull_request]
4
-
3
+ on:
4
+ pull_request:
5
+ types: [opened, reopened, edited, synchronize]
5
6
  jobs:
6
7
  build:
7
8
  runs-on: ubuntu-latest
@@ -1,5 +1,45 @@
1
1
  ## HEAD
2
2
 
3
+ ## 7.3.2
4
+
5
+ - Fix App#in_directory_fork not receiving debugging output when an error is raised (https://github.com/heroku/hatchet/pull/146)
6
+ - Do not create CI tarball inside cwd to prevent tar "file changed as we read it" warnings.
7
+
8
+ ## 7.3.1
9
+
10
+ - Fix Ruby incompatibility introduced by using `&.` and `rescue` without
11
+ `begin`/`end` without a `required_ruby_version` in hatchet.gemspec.
12
+ (https://github.com/heroku/hatchet/pull/139)
13
+
14
+ ## 7.3.0
15
+
16
+ - Deprecations
17
+ - 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)
18
+ - Deprecation: HATCHET_BUILDPACK_BASE default (https://github.com/heroku/hatchet/pull/133)
19
+ - Deprecation: App#directory (https://github.com/heroku/hatchet/pull/135)
20
+
21
+ - Flappy test improvements
22
+ - Increase CI timeout limit to 900 seconds (15 minutes) (https://github.com/heroku/hatchet/pull/137)
23
+ - Empty string returns from App#run now trigger retries (https://github.com/heroku/hatchet/pull/132)
24
+ - Rescue 403 on pipeline delete (https://github.com/heroku/hatchet/pull/130)
25
+ - Additional rate throttle cases handled (https://github.com/heroku/hatchet/pull/128)
26
+
27
+ - Usability
28
+ - Annotate rspec expectation failures inside of deploy blocks with hatchet debug information (https://github.com/heroku/hatchet/pull/136)
29
+ - Hatchet#new raises a helpful error when no source code location is provided (https://github.com/heroku/hatchet/pull/134)
30
+ - Lazy evaluation of HATCHET_BUILDPACK_BASE env var (https://github.com/heroku/hatchet/pull/133)
31
+ - Allow multiple `App#before_deploy` blocks to be set and called (https://github.com/heroku/hatchet/pull/126)
32
+ - Performance improvement when running without an explicit HEROKU_API_KEY set (https://github.com/heroku/hatchet/pull/128)
33
+
34
+ ## 7.2.0
35
+
36
+ - App#setup! no longer modifies files on disk. (https://github.com/heroku/hatchet/pull/125)
37
+ - Add `$ hatchet init` command for bootstrapping new projects (https://github.com/heroku/hatchet/pull/123)
38
+
39
+ ## 7.1.3
40
+
41
+ - Important!! Fix branch name detection on CircleCI (https://github.com/heroku/hatchet/pull/124)
42
+
3
43
  ## 7.1.2
4
44
 
5
45
  - Fix support for Hatchet deploying the 'main' branch (https://github.com/heroku/hatchet/pull/122)
data/README.md CHANGED
@@ -57,6 +57,8 @@ In addition to speed, Hatchet provides isolation. Suppose you're executing `bin/
57
57
 
58
58
  ## Quicklinks
59
59
 
60
+ - Getting started
61
+ - [Add hatchet tests to a existing buildpack](#hatchet-init)
60
62
  - Concepts
61
63
  - [Tell Hatchet how to find your buildpack](#specify-buildpack)
62
64
  - [Give Hatchet some example apps to deploy](#example-apps)
@@ -78,6 +80,45 @@ In addition to speed, Hatchet provides isolation. Suppose you're executing `bin/
78
80
  - [Introduction to the Rspec testing framework for non-rubyists](#basic-rspec)
79
81
  - [Introduction to Ruby for non-rubyists](#basic-ruby)
80
82
 
83
+ ## Getting Started
84
+
85
+ ### Hatchet Init
86
+
87
+ If you're working in a project that does not already have hatchet tests you can run this command to get started quickly:
88
+
89
+ Make sure you're in directory that contains your buildpack and run:
90
+
91
+ ```
92
+ $ gem install heroku_hatchet
93
+ $ hatchet init
94
+ ```
95
+
96
+ This will bootstrap your project with the necessarry files to test your buildpack. Including but not limited to:
97
+
98
+ - Gemfile
99
+ - hatchet.json
100
+ - spec/spec_helper.rb
101
+ - spec/hatchet/buildpack_spec.rb
102
+ - .circleci/config.yml
103
+ - .github/dependabot.yml
104
+ - .gitignore
105
+
106
+ Once this executes successfully then you can run your tests with:
107
+
108
+ ```
109
+ $ bundle exec rspec
110
+ ```
111
+
112
+ > Note: You'll need to update the `buildpack_spec.rb` file to remove the exception
113
+
114
+ You can also focus a specific file or test by providing a path and line number:
115
+
116
+ ```
117
+ $ bundle exec rspec spec/hatchet/buildpack_spec:5
118
+ ```
119
+
120
+ Keep reading to find out more about how hatchet works.
121
+
81
122
  ## Concepts
82
123
 
83
124
  ### Specify buildpack
@@ -89,7 +130,7 @@ ENV["HATCHET_BUILDPACK_BASE"] = "https://github.com/path-to-your/buildpack"
89
130
  require 'hatchet'`
90
131
  ```
91
132
 
92
- If you do not specify `HATCHET_BUILDPACK_URL` the default Ruby buildpack will be used. If you do not specify a `HATCHET_BUILDPACK_BRANCH` the current branch you are on will be used. This is how the Ruby buildpack runs tests on branches on CI (by leaving `HATCHET_BUILDPACK_BRANCH` blank).
133
+ If you do not specify `HATCHET_BUILDPACK_BASE` the default Ruby buildpack will be used. If you do not specify a `HATCHET_BUILDPACK_BRANCH` the current branch you are on will be used. This is how the Ruby buildpack runs tests on branches on CI (by leaving `HATCHET_BUILDPACK_BRANCH` blank).
93
134
 
94
135
  The workflow generally looks like this:
95
136
 
@@ -156,7 +197,7 @@ You can reference one of these applications in your test by using it's git name:
156
197
  Hatchet::Runner.new('no_lockfile')
157
198
  ```
158
199
 
159
- If you have conflicting names, use full paths like `Hatchet::RUnner.new("sharpstone/no_lockfile")`.
200
+ If you have conflicting names, use full paths like `Hatchet::Runner.new("sharpstone/no_lockfile")`.
160
201
 
161
202
  When you run `hatchet install` it will lock all the Repos to a specific commit. This is done so that if a repo changes upstream that introduces an error the test suite won't automatically pick it up. For example in https://github.com/sharpstone/lock_fail/commit/e61ba47043fbae131abb74fd74added7e6e504df an error is added, but this will only cause a failure if your project intentionally locks to commit `e61ba47043fbae131abb74fd74added7e6e504df` or later.
162
203
 
@@ -484,7 +525,7 @@ end
484
525
 
485
526
  In this example, the app would use the nodejs buildpack, and then `:default` gets replaced by your Git url and branch name.
486
527
 
487
- - 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:
488
529
 
489
530
  ```ruby
490
531
  Hatchet::Runner.new("default_ruby", before_deploy: ->{ FileUtils.touch("foo.txt")}).deploy do
@@ -589,6 +630,23 @@ Hatchet::Runner.new("default_ruby", before_deploy: before_deploy_proc).deploy do
589
630
  end
590
631
  ```
591
632
 
633
+ You can call multiple blocks by specifying (`:prepend` or `:append`):
634
+
635
+ ```ruby
636
+ Hatchet::Runner.new("default_ruby").tap do |app|
637
+ app.before_deploy do
638
+ FileUtils.touch("foo.txt")
639
+ end
640
+
641
+ app.before_deploy(:append) do
642
+ FileUtils.touch("bar.txt")
643
+ end
644
+ app.deploy do
645
+ end
646
+ end
647
+ ```
648
+
649
+
592
650
  - `app.commit!`: Will updates the contents of your local git dir if you've modified files on disk
593
651
 
594
652
  ```ruby
@@ -619,7 +677,7 @@ end
619
677
  > 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.
620
678
 
621
679
  - `app.in_directory_fork`: Runs the given block in a temp directory and inside of a forked process, an example given above.
622
- - `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.
680
+ - `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.
623
681
  - `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).
624
682
  - `app.output`: The output contents of the deploy
625
683
  - `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.
@@ -15,8 +15,14 @@ require 'thor'
15
15
  require 'threaded'
16
16
  require 'date'
17
17
  require 'yaml'
18
+ require 'pathname'
18
19
 
19
20
  class HatchetCLI < Thor
21
+ desc "init", "bootstraps a project with minimal files required to add hatchet tests"
22
+ define_method("init") do
23
+ Hatchet::InitProject.new.call
24
+ end
25
+
20
26
  desc "ci:install_heroku", "installs the `heroku` cli"
21
27
  define_method("ci:install_heroku") do
22
28
  if `which heroku` && $?.success?
@@ -46,7 +52,7 @@ class HatchetCLI < Thor
46
52
  Threaded.later do
47
53
  commit = lock_hash[directory]
48
54
  directory = File.expand_path(directory)
49
- if !Dir[directory]&.empty?
55
+ if !(Dir[directory] && Dir[directory].empty?)
50
56
  puts "== pulling '#{git_repo}' into '#{directory}'\n"
51
57
  pull(directory, git_repo)
52
58
  else
@@ -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
@@ -18,6 +18,8 @@ require 'hatchet/anvil_app'
18
18
  require 'hatchet/git_app'
19
19
  require 'hatchet/config'
20
20
  require 'hatchet/api_rate_limit'
21
+ require 'hatchet/init_project'
22
+ require 'hatchet/heroku_run'
21
23
 
22
24
  module Hatchet
23
25
  RETRIES = Integer(ENV['HATCHET_RETRIES'] || 1)
@@ -30,7 +32,7 @@ module Hatchet
30
32
  return ENV['TRAVIS_PULL_REQUEST_BRANCH'] if ENV['TRAVIS_PULL_REQUEST_BRANCH'] && !ENV['TRAVIS_PULL_REQUEST_BRANCH'].empty?
31
33
  return ENV['TRAVIS_BRANCH'] if ENV['TRAVIS_BRANCH']
32
34
 
33
- out = `git describe --contains --all HEAD`.strip
35
+ out = `git rev-parse --abbrev-ref HEAD`.strip
34
36
  raise "Attempting to find current branch name. Error: Cannot describe git: #{out}" unless $?.success?
35
37
  out
36
38
  end
@@ -44,3 +46,22 @@ module Hatchet
44
46
  end
45
47
  end
46
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
+
@@ -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,8 +46,9 @@ 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,
51
+ def initialize(repo_name = DEFAULT_REPO_NAME,
47
52
  stack: "",
48
53
  name: default_name,
49
54
  debug: 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,36 @@ 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
+
86
115
  def self.default_buildpack
87
- [HATCHET_BUILDPACK_BASE, HATCHET_BUILDPACK_BRANCH.call].join("#")
116
+ [HATCHET_BUILDPACK_BASE.call, HATCHET_BUILDPACK_BRANCH.call].join("#")
88
117
  end
89
118
 
90
119
  def allow_failure?
@@ -157,32 +186,17 @@ module Hatchet
157
186
  command = command.to_s
158
187
  end
159
188
 
160
- heroku_command = build_heroku_command(command, options)
161
-
162
189
  allow_run_multi! if @run_multi
163
190
 
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)
191
+ run_obj = Hatchet::HerokuRun.new(
192
+ command,
193
+ app: self,
194
+ retry_on_empty: options.fetch(:retry_on_empty, !ENV["HATCHET_DISABLE_EMPTY_RUN_RETRY"]),
195
+ heroku: options[:heroku],
196
+ raw: options[:raw]
197
+ ).call
176
198
 
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(" ")
184
-
185
- "heroku run #{heroku_options} -- #{command}"
199
+ return run_obj.output
186
200
  end
187
201
 
188
202
  private def allow_run_multi!
@@ -191,7 +205,6 @@ module Hatchet
191
205
  @run_multi_is_setup ||= platform_api.formation.update(name, "web", {"size" => "Standard-1X"})
192
206
  end
193
207
 
194
-
195
208
  # Allows multiple commands to be run concurrently in the background.
196
209
  #
197
210
  # WARNING! Using the feature requres that the underlying app is not on the "free" Heroku
@@ -229,23 +242,15 @@ module Hatchet
229
242
  allow_run_multi!
230
243
 
231
244
  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
245
+ run_obj = Hatchet::HerokuRun.new(
246
+ command,
247
+ app: self,
248
+ retry_on_empty: options.fetch(:retry_on_empty, !ENV["HATCHET_DISABLE_EMPTY_RUN_RETRY"]),
249
+ heroku: options[:heroku],
250
+ raw: options[:raw]
251
+ ).call
252
+
253
+ yield run_obj.output, run_obj.status
249
254
  end
250
255
  run_thread.abort_on_exception = true
251
256
 
@@ -297,22 +302,46 @@ module Hatchet
297
302
  def setup!
298
303
  return self if @app_is_setup
299
304
  puts "Hatchet setup: #{name.inspect} for #{repo_name.inspect}"
300
- create_git_repo! unless is_git_repo?
301
305
  create_app
302
306
  set_labs!
303
307
  buildpack_list = @buildpacks.map { |pack| { buildpack: pack } }
304
308
  api_rate_limit.call.buildpack_installation.update(name, updates: buildpack_list)
305
309
  set_config @app_config
306
310
 
307
- call_before_deploy
308
311
  @app_is_setup = true
309
312
  self
310
313
  end
311
314
  alias :setup :setup!
312
315
 
313
- def before_deploy(&block)
316
+ private def in_dir_setup!
317
+ setup!
318
+ raise "Error you're in #{Dir.pwd} and might accidentally modify your disk contents" unless @already_in_dir
319
+ @in_dir_setup ||= begin
320
+ create_git_repo! unless is_git_repo?
321
+ call_before_deploy
322
+ true
323
+ end
324
+ end
325
+
326
+ def before_deploy(behavior = :default, &block)
314
327
  raise "block required" unless block
315
- @before_deploy = block
328
+
329
+ case behavior
330
+ when :default, :replace
331
+ if @before_deploy_array.any? && behavior == :default
332
+ STDERR.puts "Calling App#before_deploy multiple times will overwrite the contents. If you intended this: use `App#before_deploy(:replace)`"
333
+ STDERR.puts "In the future, calling this method with no arguements will default to `App#before_deploy(:append)` behavior.\n#{caller.join("\n")}"
334
+ end
335
+
336
+ @before_deploy_array.clear
337
+ @before_deploy_array << block
338
+ when :prepend
339
+ @before_deploy_array = [block] + @before_deploy_array
340
+ when :append
341
+ @before_deploy_array << block
342
+ else
343
+ raise "Unrecognized behavior: #{behavior.inspect}, valid inputs are :append, :prepend, and :replace"
344
+ end
316
345
 
317
346
  self
318
347
  end
@@ -338,14 +367,14 @@ module Hatchet
338
367
  @reaper.cycle if @app_is_setup
339
368
  end
340
369
 
341
- def in_directory(directory = self.directory)
342
- yield directory and return if @already_in_dir
370
+ def in_directory
371
+ yield and return if @already_in_dir
343
372
 
344
373
  Dir.mktmpdir do |tmpdir|
345
- FileUtils.cp_r("#{directory}/.", "#{tmpdir}/.")
374
+ FileUtils.cp_r("#{original_source_code_directory}/.", "#{tmpdir}/.")
346
375
  Dir.chdir(tmpdir) do
347
376
  @already_in_dir = true
348
- yield directory
377
+ yield
349
378
  @already_in_dir = false
350
379
  end
351
380
  end
@@ -358,8 +387,8 @@ module Hatchet
358
387
  def in_directory_fork(&block)
359
388
  Tempfile.create("stdout") do |tmp_file|
360
389
  pid = fork do
361
- $stdout.reopen(tmp_file, "w")
362
- $stderr.reopen(tmp_file, "w")
390
+ $stdout.reopen(tmp_file, "a")
391
+ $stderr.reopen(tmp_file, "a")
363
392
  $stdout.sync = true
364
393
  $stderr.sync = true
365
394
  in_directory do |dir|
@@ -379,9 +408,11 @@ module Hatchet
379
408
 
380
409
  def deploy(&block)
381
410
  in_directory do
382
- self.setup!
383
- self.push_with_retry!
384
- block.call(self, api_rate_limit.call, output) if block_given?
411
+ annotate_failures do
412
+ in_dir_setup!
413
+ self.push_with_retry!
414
+ block.call(self, api_rate_limit.call, output) if block_given?
415
+ end
385
416
  end
386
417
  ensure
387
418
  self.teardown! if block_given?
@@ -415,26 +446,26 @@ module Hatchet
415
446
  end
416
447
 
417
448
  def api_key
418
- @api_key ||= ENV['HEROKU_API_KEY'] || `heroku auth:token`.chomp
449
+ @api_key ||= ENV['HEROKU_API_KEY'] ||= `heroku auth:token`.chomp
419
450
  end
420
451
 
421
452
  def heroku
422
453
  raise "Not supported, use `platform_api` instead."
423
454
  end
424
455
 
425
- def run_ci(timeout: 300, &block)
456
+ def run_ci(timeout: 900, &block)
426
457
  in_directory do
427
458
  max_retries_count.times.retry do
428
459
  result = create_pipeline
429
460
  @pipeline_id = result["id"]
430
461
  end
431
462
 
432
- # when the CI run finishes, the associated ephemeral app created for the test run internally gets removed almost immediately
463
+ # When the CI run finishes, the associated ephemeral app created for the test run internally gets removed almost immediately
433
464
  # the system then sees a pipeline with no apps, and deletes it, also almost immediately
434
465
  # that would, with bad timing, mean our test run info poll in wait! would 403, and/or the delete_pipeline at the end
435
466
  # that's why we create an app explictly (or maybe it already exists), and then associate it with with the pipeline
436
467
  # the app will be auto cleaned up later
437
- self.setup!
468
+ in_dir_setup!
438
469
  max_retries_count.times.retry do
439
470
  couple_pipeline(@name, @pipeline_id)
440
471
  end
@@ -487,6 +518,9 @@ module Hatchet
487
518
 
488
519
  def delete_pipeline(pipeline_id)
489
520
  api_rate_limit.call.pipeline.delete(pipeline_id)
521
+ rescue Excon::Error::Forbidden
522
+ warn "Error deleting pipeline id: #{pipeline_id.inspect}, status: 403"
523
+ # Means the pipeline likely doesn't exist, not sure why though
490
524
  end
491
525
 
492
526
  def platform_api
@@ -526,14 +560,17 @@ module Hatchet
526
560
  end
527
561
 
528
562
  private def call_before_deploy
529
- return unless @before_deploy
530
- raise "before_deploy: #{@before_deploy.inspect} must respond to :call" unless @before_deploy.respond_to?(:call)
531
- raise "before_deploy: #{@before_deploy.inspect} must respond to :arity" unless @before_deploy.respond_to?(:arity)
563
+ return unless @before_deploy_array.any?
532
564
 
533
- if @before_deploy.arity == 1
534
- @before_deploy.call(self)
535
- else
536
- @before_deploy.call
565
+ @before_deploy_array.each do |block|
566
+ raise "before_deploy: #{block.inspect} must respond to :call" unless block.respond_to?(:call)
567
+ raise "before_deploy: #{block.inspect} must respond to :arity" unless block.respond_to?(:arity)
568
+
569
+ if block.arity == 1
570
+ block.call(self)
571
+ else
572
+ block.call
573
+ end
537
574
  end
538
575
 
539
576
  commit! if needs_commit?