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 +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 +22 -1
- data/lib/hatchet/app.rb +110 -73
- 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/git_spec.rb +6 -0
- 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: 960b3c8f2eee0b1252df5b2e6ce23b718de130d099f913812bd5fbb196c3b006
|
|
4
|
+
data.tar.gz: 3dec91bb9b7de86d3aef2ef78086f29239f078d1c6c004244b3ead7aaf139a58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b19e39c4963c64a5448f5b4ff4961722e1c17b020d3c6a8f3ef102a1bb136ba2feb504afddc8cbb6a286f384806776ebfdfebb76834f3d1a3eb37af36cbe4532
|
|
7
|
+
data.tar.gz: 3cc4cb2320f711e1d5fbb2024dc7a7ff1d734158b2f89322104a162fc00a2cb21940cf520f64e679b948c1a9be4bedeb3735c447c8287496438d1265bd372104
|
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.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 `
|
|
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)
|
|
@@ -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
|
|
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
|
+
|
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|
|
|
@@ -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`.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?
|