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 +4 -4
- data/.circleci/config.yml +5 -5
- data/CHANGELOG.md +46 -2
- data/README.md +22 -3
- data/bin/hatchet +1 -1
- data/etc/ci_setup.rb +2 -0
- data/etc/setup_heroku.sh +1 -1
- data/hatchet.gemspec +2 -2
- data/lib/hatchet.rb +20 -0
- data/lib/hatchet/app.rb +102 -72
- data/lib/hatchet/config.rb +2 -2
- data/lib/hatchet/git_app.rb +14 -10
- data/lib/hatchet/heroku_run.rb +96 -0
- data/lib/hatchet/reaper.rb +4 -4
- data/lib/hatchet/templates/dependabot.erb +1 -1
- data/lib/hatchet/test_run.rb +17 -12
- data/lib/hatchet/version.rb +1 -1
- data/spec/hatchet/allow_failure_git_spec.rb +1 -1
- data/spec/hatchet/app_spec.rb +97 -33
- data/spec/hatchet/ci_spec.rb +2 -2
- data/spec/hatchet/config_spec.rb +4 -0
- data/spec/hatchet/local_repo_spec.rb +9 -0
- data/spec/hatchet/lock_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -1
- data/spec/unit/heroku_run_spec.rb +115 -0
- data/spec/unit/shell_throttle_spec.rb +142 -0
- metadata +11 -22
- data/spec/unit/shell_throttle.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a45ee1ab2021838074b4f288d70aab769a71961cbae7cd12af67866e5558bb64
|
4
|
+
data.tar.gz: 0c99b826bfc797632924e151b796590f1ddbd07ff50e9eea2dc816b9991fb69d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
32
|
+
"ruby-2.2":
|
33
33
|
docker:
|
34
|
-
- image: circleci/ruby:2.
|
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.
|
42
|
+
"ruby-2.5":
|
43
43
|
docker:
|
44
|
-
- image: circleci/ruby:2.
|
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.
|
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
|
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.
|
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]
|
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
|
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", "~>
|
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
|
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, :
|
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.
|
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.
|
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
|
-
@
|
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
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
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
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
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
|
-
|
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
|
350
|
-
yield
|
371
|
+
def in_directory
|
372
|
+
yield and return if @already_in_dir
|
351
373
|
|
352
374
|
Dir.mktmpdir do |tmpdir|
|
353
|
-
FileUtils.cp_r("#{
|
375
|
+
FileUtils.cp_r("#{original_source_code_directory}/.", "#{tmpdir}/.")
|
354
376
|
Dir.chdir(tmpdir) do
|
355
377
|
@already_in_dir = true
|
356
|
-
yield
|
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, "
|
370
|
-
$stderr.reopen(tmp_file, "
|
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
|
-
|
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
|
-
|
391
|
-
|
392
|
-
|
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']
|
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:
|
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
|
-
#
|
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 @
|
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
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
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?
|