heroku_hatchet 7.1.3 → 7.3.3
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/.github/workflows/check_changelog.yml +3 -2
- data/CHANGELOG.md +40 -0
- data/README.md +62 -4
- data/bin/hatchet +7 -1
- data/hatchet.gemspec +2 -2
- data/lib/hatchet.rb +21 -0
- data/lib/hatchet/app.rb +111 -74
- data/lib/hatchet/config.rb +1 -1
- data/lib/hatchet/git_app.rb +14 -10
- data/lib/hatchet/heroku_run.rb +96 -0
- data/lib/hatchet/init_project.rb +86 -0
- data/lib/hatchet/reaper.rb +2 -2
- data/lib/hatchet/templates/Gemfile.erb +5 -0
- data/lib/hatchet/templates/buildpack_spec.erb +23 -0
- data/lib/hatchet/templates/check_changelog.erb +13 -0
- data/lib/hatchet/templates/circleci_template.erb +45 -0
- data/lib/hatchet/templates/dependabot.erb +9 -0
- data/lib/hatchet/templates/hatchet_json.erb +11 -0
- data/lib/hatchet/templates/spec_helper.erb +30 -0
- 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 +95 -26
- data/spec/hatchet/local_repo_spec.rb +17 -7
- 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/init_spec.rb +52 -0
- data/spec/unit/shell_throttle_spec.rb +94 -0
- metadata +21 -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: 38e2cd43133aeb405a133834284c026c0e4a1347e33f84e805ed7b9fd2674fc9
|
4
|
+
data.tar.gz: b1f88b0a62250a4cb2d9e22a89fe101356c5360aa37d786a51faebb2fac23824
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b36428d7f0e5288ac434d3fa66c3bf70b067742f4e150ebc4208d2e1e9b23761c7fd94cee0c8713909e07c63845014b7b139dd86f82d0118a53978b0033a83f7
|
7
|
+
data.tar.gz: 3250c42cdd66bfbcfc4b129a0cf4083b30b94308614bf5ae4c765ced438522e814a6205dde298b51132eb6057581329054fde9d7bbd49208dda2d9cf89bd3e8d
|
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,5 +1,45 @@
|
|
1
1
|
## HEAD
|
2
2
|
|
3
|
+
## 7.3.3
|
4
|
+
|
5
|
+
- Quiet personal tokens (https://github.com/heroku/hatchet/pull/148)
|
6
|
+
|
7
|
+
## 7.3.2
|
8
|
+
|
9
|
+
- Fix App#in_directory_fork not receiving debugging output when an error is raised (https://github.com/heroku/hatchet/pull/146)
|
10
|
+
- Do not create CI tarball inside cwd to prevent tar "file changed as we read it" warnings.
|
11
|
+
|
12
|
+
## 7.3.1
|
13
|
+
|
14
|
+
- Fix Ruby incompatibility introduced by using `&.` and `rescue` without
|
15
|
+
`begin`/`end` without a `required_ruby_version` in hatchet.gemspec.
|
16
|
+
(https://github.com/heroku/hatchet/pull/139)
|
17
|
+
|
18
|
+
## 7.3.0
|
19
|
+
|
20
|
+
- Deprecations
|
21
|
+
- 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)
|
22
|
+
- Deprecation: HATCHET_BUILDPACK_BASE default (https://github.com/heroku/hatchet/pull/133)
|
23
|
+
- Deprecation: App#directory (https://github.com/heroku/hatchet/pull/135)
|
24
|
+
|
25
|
+
- Flappy test improvements
|
26
|
+
- Increase CI timeout limit to 900 seconds (15 minutes) (https://github.com/heroku/hatchet/pull/137)
|
27
|
+
- Empty string returns from App#run now trigger retries (https://github.com/heroku/hatchet/pull/132)
|
28
|
+
- Rescue 403 on pipeline delete (https://github.com/heroku/hatchet/pull/130)
|
29
|
+
- Additional rate throttle cases handled (https://github.com/heroku/hatchet/pull/128)
|
30
|
+
|
31
|
+
- Usability
|
32
|
+
- Annotate rspec expectation failures inside of deploy blocks with hatchet debug information (https://github.com/heroku/hatchet/pull/136)
|
33
|
+
- Hatchet#new raises a helpful error when no source code location is provided (https://github.com/heroku/hatchet/pull/134)
|
34
|
+
- Lazy evaluation of HATCHET_BUILDPACK_BASE env var (https://github.com/heroku/hatchet/pull/133)
|
35
|
+
- Allow multiple `App#before_deploy` blocks to be set and called (https://github.com/heroku/hatchet/pull/126)
|
36
|
+
- Performance improvement when running without an explicit HEROKU_API_KEY set (https://github.com/heroku/hatchet/pull/128)
|
37
|
+
|
38
|
+
## 7.2.0
|
39
|
+
|
40
|
+
- App#setup! no longer modifies files on disk. (https://github.com/heroku/hatchet/pull/125)
|
41
|
+
- Add `$ hatchet init` command for bootstrapping new projects (https://github.com/heroku/hatchet/pull/123)
|
42
|
+
|
3
43
|
## 7.1.3
|
4
44
|
|
5
45
|
- Important!! Fix branch name detection on CircleCI (https://github.com/heroku/hatchet/pull/124)
|
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 `
|
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::
|
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.
|
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.
|
data/bin/hatchet
CHANGED
@@ -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]
|
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
|
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
@@ -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)
|
@@ -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
|
+
|
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,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
|
-
@
|
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
|
-
|
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)
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
342
|
-
yield
|
370
|
+
def in_directory
|
371
|
+
yield and return if @already_in_dir
|
343
372
|
|
344
373
|
Dir.mktmpdir do |tmpdir|
|
345
|
-
FileUtils.cp_r("#{
|
374
|
+
FileUtils.cp_r("#{original_source_code_directory}/.", "#{tmpdir}/.")
|
346
375
|
Dir.chdir(tmpdir) do
|
347
376
|
@already_in_dir = true
|
348
|
-
yield
|
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, "
|
362
|
-
$stderr.reopen(tmp_file, "
|
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|
|
@@ -370,7 +399,7 @@ module Hatchet
|
|
370
399
|
Process.waitpid(pid)
|
371
400
|
|
372
401
|
if $?.success?
|
373
|
-
|
402
|
+
print File.read(tmp_file)
|
374
403
|
else
|
375
404
|
raise File.read(tmp_file)
|
376
405
|
end
|
@@ -379,9 +408,11 @@ module Hatchet
|
|
379
408
|
|
380
409
|
def deploy(&block)
|
381
410
|
in_directory do
|
382
|
-
|
383
|
-
|
384
|
-
|
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']
|
449
|
+
@api_key ||= ENV['HEROKU_API_KEY'] ||= `heroku auth:token 2> /dev/null`.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:
|
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
|
-
#
|
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
|
-
|
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 @
|
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
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
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?
|