heroku_hatchet 7.0.0 → 7.2.0

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: 89dec86ebf0199547ebf527242257a88b26c2741b272aa73945b4c907b72f51a
4
- data.tar.gz: fe0bd8c520fc7102e13ad6f91da96b837158f0ba8eaa324c846e3acc92cb233f
3
+ metadata.gz: 2dd4a304828fca43576fed5a71b7be305142c3220c7dd020bff44b17a4a070a0
4
+ data.tar.gz: c0dee0a8ffb794f10a3dbb96beeafa26dd5f95b489eb9638a40bf408dce44c11
5
5
  SHA512:
6
- metadata.gz: bad4717807a5cddb7b0cdd2750bdd1811b8b012e767d92173f364795eb1da3a8af41fddb28269d14c67ad8d2cfe7e7d4c7f9cefbbafd575172408787cca41142
7
- data.tar.gz: b2f7905e07091c4b66ad7b6ba4ac00b8e304e32f10c01a259723cb5e12e592160f9ca30cb66f5485c3bc59dffa26d99bbba070f73be161022d85e63c6afd4409
6
+ metadata.gz: f6fa0cef6167e9b4985b08f01e42f33410632d4dfb083551d2641e7009745a079044f606cc4456ed8b8f0dfb0ec087b9b3197d1dabb9a0c41cd7f96ee978e22c
7
+ data.tar.gz: 50689c87372d4df2fc7d354ccc0f6f870285283869a190f3272f76d0d238e2d3cd13ca485ca6436916a6976c847fbd2be057fbe4e99ed1aa8ac97abdfef9eeb9
@@ -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,30 @@
1
1
  ## HEAD
2
2
 
3
+ ## 7.1.4
4
+
5
+ - App#setup! no longer modifies files on disk. (https://github.com/heroku/hatchet/pull/125)
6
+ - Add `$ hatchet init` command for bootstrapping new projects (https://github.com/heroku/hatchet/pull/123)
7
+
8
+ ## 7.1.3
9
+
10
+ - Important!! Fix branch name detection on CircleCI (https://github.com/heroku/hatchet/pull/124)
11
+
12
+ ## 7.1.2
13
+
14
+ - Fix support for Hatchet deploying the 'main' branch (https://github.com/heroku/hatchet/pull/122)
15
+
16
+ ## 7.1.1
17
+
18
+ - Fix destroy_all functionality (https://github.com/heroku/hatchet/pull/121)
19
+
20
+ ## 7.1.0
21
+
22
+ - Initializing an `App` can now take a `retries` key to overload the global hatchet env var (https://github.com/heroku/hatchet/pull/119)
23
+ - Calling `App#commit!` now adds an empty commit if there is no changes on disk (https://github.com/heroku/hatchet/pull/119)
24
+ - Bugfix: Failed release phase in deploys can now be re-run (https://github.com/heroku/hatchet/pull/119)
25
+ - Bugfix: Allow `hatchet lock` to be run against new projects (https://github.com/heroku/hatchet/pull/118)
26
+ - Bugfix: Allow `hatchet.lock` file to lock a repo to a different branch than what is specified as default for GitHub (https://github.com/heroku/hatchet/pull/118)
27
+
3
28
  ## 7.0.0
4
29
 
5
30
  - ActiveSupport's Object#blank? and Object#present? are no longer provided by default (https://github.com/heroku/hatchet/pull/107)
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
 
@@ -531,6 +572,8 @@ WARNING: Enabling `run_multi` setting on an app will charge your Heroku account
531
572
  WARNING: Do not use `run_multi` if you're not using the `deploy` block syntax or manually call `teardown!` inside the text context [more info about how behavior does not work with the `after` block syntax in rspec](https://github.com/heroku/hatchet/issues/110).
532
573
  WARNING: To work, `run_multi` requires your application to have a `web` process associated with it.
533
574
 
575
+ - `retries` (Integer): When passed in, this value will be used insead of the global `HATCHET_RETRIES` set via environment variable. When `allow_failures: true` is set as well as a retries value, then the application will not retry pushing to Heroku.
576
+
534
577
  ### App methods:
535
578
 
536
579
  - `app.set_config()`: Updates the configuration on your app taking in a hash
@@ -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?
@@ -67,7 +73,7 @@ class HatchetCLI < Thor
67
73
  desc "locks to specific git commits", "updates hatchet.lock"
68
74
  def lock
69
75
  lock_hash = {}
70
- lockfile_hash = load_lockfile
76
+ lockfile_hash = load_lockfile(create_if_does_not_exist: true)
71
77
  dirs.map do |directory, git_repo|
72
78
  Threaded.later do
73
79
  puts "== locking #{directory}"
@@ -118,10 +124,15 @@ class HatchetCLI < Thor
118
124
  end
119
125
 
120
126
  private
121
- def load_lockfile
127
+ def load_lockfile(create_if_does_not_exist: false)
122
128
  return YAML.safe_load(File.read('hatchet.lock')).to_h
123
129
  rescue Errno::ENOENT
124
- raise "No such file found `hatchet.lock` please run `$ bundle exec hatchet lock`"
130
+ if create_if_does_not_exist
131
+ FileUtils.touch('hatchet.lock')
132
+ {}
133
+ else
134
+ raise "No such file found `hatchet.lock` please run `$ bundle exec hatchet lock`"
135
+ end
125
136
  end
126
137
 
127
138
  def bad_repo?(url)
@@ -151,7 +162,7 @@ class HatchetCLI < Thor
151
162
  end
152
163
 
153
164
  def checkout_commit(directory, commit)
154
- cmd("cd #{directory} && git reset --hard #{commit}")
165
+ cmd("cd #{directory} && git fetch origin #{commit} && git checkout #{commit} && git checkout - && git reset --hard #{commit}")
155
166
  end
156
167
 
157
168
  def commit_at_directory(directory)
@@ -18,6 +18,7 @@ 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'
21
22
 
22
23
  module Hatchet
23
24
  RETRIES = Integer(ENV['HATCHET_RETRIES'] || 1)
@@ -30,7 +31,7 @@ module Hatchet
30
31
  return ENV['TRAVIS_PULL_REQUEST_BRANCH'] if ENV['TRAVIS_PULL_REQUEST_BRANCH'] && !ENV['TRAVIS_PULL_REQUEST_BRANCH'].empty?
31
32
  return ENV['TRAVIS_BRANCH'] if ENV['TRAVIS_BRANCH']
32
33
 
33
- out = `git describe --contains --all HEAD`.strip
34
+ out = `git rev-parse --abbrev-ref HEAD`.strip
34
35
  raise "Attempting to find current branch name. Error: Cannot describe git: #{out}" unless $?.success?
35
36
  out
36
37
  end
@@ -9,7 +9,7 @@ module Hatchet
9
9
  HATCHET_BUILDPACK_BRANCH = -> { ENV['HATCHET_BUILDPACK_BRANCH'] || ENV['HEROKU_TEST_RUN_BRANCH'] || Hatchet.git_branch }
10
10
  BUILDPACK_URL = "https://github.com/heroku/heroku-buildpack-ruby.git"
11
11
 
12
- attr_reader :name, :stack, :directory, :repo_name, :app_config, :buildpacks, :reaper
12
+ attr_reader :name, :stack, :directory, :repo_name, :app_config, :buildpacks, :reaper, :max_retries_count
13
13
 
14
14
  class FailedDeploy < StandardError; end
15
15
 
@@ -55,6 +55,7 @@ module Hatchet
55
55
  buildpack_url: nil,
56
56
  before_deploy: nil,
57
57
  run_multi: ENV["HATCHET_RUN_MULTI"],
58
+ retries: RETRIES,
58
59
  config: {}
59
60
  )
60
61
  @repo_name = repo_name
@@ -68,6 +69,7 @@ module Hatchet
68
69
  @buildpacks = Array(@buildpacks)
69
70
  @buildpacks.map! {|b| b == :default ? self.class.default_buildpack : b}
70
71
  @run_multi = run_multi
72
+ @max_retries_count = retries
71
73
 
72
74
  if run_multi && !ENV["HATCHET_EXPENSIVE_MODE"]
73
75
  raise "You're attempting to enable `run_multi: true` mode, but have not enabled `HATCHET_EXPENSIVE_MODE=1` env var to verify you understand the risks"
@@ -129,7 +131,7 @@ module Hatchet
129
131
  end
130
132
 
131
133
  def add_database(plan_name = 'heroku-postgresql:dev', match_val = "HEROKU_POSTGRESQL_[A-Z]+_URL")
132
- Hatchet::RETRIES.times.retry do
134
+ max_retries_count.times.retry do
133
135
  # heroku.post_addon(name, plan_name)
134
136
  api_rate_limit.call.addon.create(name, plan: plan_name )
135
137
  _, value = get_config.detect {|k, v| k.match(/#{match_val}/) }
@@ -295,19 +297,27 @@ module Hatchet
295
297
  def setup!
296
298
  return self if @app_is_setup
297
299
  puts "Hatchet setup: #{name.inspect} for #{repo_name.inspect}"
298
- create_git_repo! unless is_git_repo?
299
300
  create_app
300
301
  set_labs!
301
302
  buildpack_list = @buildpacks.map { |pack| { buildpack: pack } }
302
303
  api_rate_limit.call.buildpack_installation.update(name, updates: buildpack_list)
303
304
  set_config @app_config
304
305
 
305
- call_before_deploy
306
306
  @app_is_setup = true
307
307
  self
308
308
  end
309
309
  alias :setup :setup!
310
310
 
311
+ private def in_dir_setup!
312
+ setup!
313
+ raise "Error you're in #{Dir.pwd} and might accidentally modify your disk contents" unless @already_in_dir
314
+ @in_dir_setup ||= begin
315
+ create_git_repo! unless is_git_repo?
316
+ call_before_deploy
317
+ true
318
+ end
319
+ end
320
+
311
321
  def before_deploy(&block)
312
322
  raise "block required" unless block
313
323
  @before_deploy = block
@@ -316,7 +326,7 @@ module Hatchet
316
326
  end
317
327
 
318
328
  def commit!
319
- local_cmd_exec!('git add .; git commit -m next')
329
+ local_cmd_exec!('git add .; git commit --allow-empty -m next')
320
330
  end
321
331
 
322
332
  def push_without_retry!
@@ -377,7 +387,7 @@ module Hatchet
377
387
 
378
388
  def deploy(&block)
379
389
  in_directory do
380
- self.setup!
390
+ in_dir_setup!
381
391
  self.push_with_retry!
382
392
  block.call(self, api_rate_limit.call, output) if block_given?
383
393
  end
@@ -386,12 +396,12 @@ module Hatchet
386
396
  end
387
397
 
388
398
  def push
389
- max_retries = @allow_failure ? 1 : RETRIES
390
- max_retries.times.retry do |attempt|
399
+ retry_count = allow_failure? ? 1 : max_retries_count
400
+ retry_count.times.retry do |attempt|
391
401
  begin
392
402
  @output = self.push_without_retry!
393
403
  rescue StandardError => error
394
- puts retry_error_message(error, attempt, max_retries)
404
+ puts retry_error_message(error, attempt) unless retry_count == 1
395
405
  raise error
396
406
  end
397
407
  end
@@ -400,10 +410,10 @@ module Hatchet
400
410
  alias :push_with_retry :push
401
411
  alias :push_with_retry! :push_with_retry
402
412
 
403
- def retry_error_message(error, attempt, max_retries)
413
+ def retry_error_message(error, attempt)
404
414
  attempt += 1
405
- return "" if attempt == max_retries
406
- msg = "\nRetrying failed Attempt ##{attempt}/#{max_retries} to push for '#{name}' due to error: \n"<<
415
+ return "" if attempt == max_retries_count
416
+ msg = "\nRetrying failed Attempt ##{attempt}/#{max_retries_count} to push for '#{name}' due to error: \n"<<
407
417
  "#{error.class} #{error.message}\n #{error.backtrace.join("\n ")}"
408
418
  return msg
409
419
  end
@@ -422,7 +432,7 @@ module Hatchet
422
432
 
423
433
  def run_ci(timeout: 300, &block)
424
434
  in_directory do
425
- Hatchet::RETRIES.times.retry do
435
+ max_retries_count.times.retry do
426
436
  result = create_pipeline
427
437
  @pipeline_id = result["id"]
428
438
  end
@@ -432,8 +442,8 @@ module Hatchet
432
442
  # that would, with bad timing, mean our test run info poll in wait! would 403, and/or the delete_pipeline at the end
433
443
  # that's why we create an app explictly (or maybe it already exists), and then associate it with with the pipeline
434
444
  # the app will be auto cleaned up later
435
- self.setup!
436
- Hatchet::RETRIES.times.retry do
445
+ in_dir_setup!
446
+ max_retries_count.times.retry do
437
447
  couple_pipeline(@name, @pipeline_id)
438
448
  end
439
449
 
@@ -446,7 +456,7 @@ module Hatchet
446
456
  api_rate_limit: api_rate_limit
447
457
  )
448
458
 
449
- Hatchet::RETRIES.times.retry do
459
+ max_retries_count.times.retry do
450
460
  test_run.create_test_run
451
461
  end
452
462
  test_run.wait!(&block)
@@ -25,7 +25,7 @@ module Hatchet
25
25
  end
26
26
 
27
27
  private def git_push_heroku_yall
28
- output = `git push #{git_repo} master 2>&1`
28
+ output = `git push #{git_repo} HEAD:main 2>&1`
29
29
 
30
30
  if !$?.success?
31
31
  raise FailedDeployError.new(self, "Buildpack: #{@buildpack.inspect}\nRepo: #{git_repo}", output: output)
@@ -33,6 +33,7 @@ module Hatchet
33
33
 
34
34
  releases = platform_api.release.list(name)
35
35
  if releases.last["status"] == "failed"
36
+ commit! # An empty commit allows us to deploy again
36
37
  raise FailedReleaseError.new(self, "Buildpack: #{@buildpack.inspect}\nRepo: #{git_repo}", output: output)
37
38
  end
38
39
 
@@ -0,0 +1,86 @@
1
+ require 'thor'
2
+ require 'yaml'
3
+
4
+ module Hatchet
5
+ # Bootstraps a project with files for running hatchet tests
6
+ #
7
+ # Hatchet::InitProject.new.call
8
+ #
9
+ # puts File.exist?("spec/spec_helper.rb") # => true
10
+ # puts File.exist?("") # => true
11
+ class InitProject
12
+ def initialize(dir: ".", io: STDOUT)
13
+
14
+ @target_dir = Pathname.new(dir)
15
+ raise "Must run in a directory with a buildpack, #{@target_dir} has no bin/ directory" unless @target_dir.join("bin").directory?
16
+
17
+ @template_dir = Pathname.new(__dir__).join("templates")
18
+ @thor_shell = ::Thor::Shell::Basic.new
19
+ @io = io
20
+ @git_ignore = @target_dir.join(".gitignore")
21
+
22
+ FileUtils.touch(@git_ignore)
23
+ FileUtils.touch(@target_dir.join("hatchet.lock"))
24
+ end
25
+
26
+ def call
27
+ write_target(target: ".circleci/config.yml", template: "circleci_template.erb")
28
+ write_target(target: "Gemfile", template: "Gemfile.erb")
29
+ write_target(target: "hatchet.json", template: "hatchet_json.erb")
30
+ write_target(target: "spec/spec_helper.rb", template: "spec_helper.erb")
31
+ write_target(target: "spec/hatchet/buildpack_spec.rb", template: "buildpack_spec.erb")
32
+ write_target(target: ".github/dependabot.yml", template: "dependabot.erb")
33
+ write_target(target: ".github/workflows/check_changelog.yml", template: "check_changelog.erb")
34
+
35
+ add_gitignore(".rspec_status")
36
+ add_gitignore("repos/*")
37
+
38
+ stream("cd #{@target_dir} && bundle install")
39
+ stream("cd #{@target_dir} && hatchet install")
40
+
41
+ @io.puts
42
+ @io.puts "Done, run `bundle exec rspec` to execute your tests"
43
+ @io.puts
44
+ end
45
+
46
+ private def add_gitignore(statement)
47
+ @git_ignore.open("a") {|f| f.puts statement } unless @git_ignore.read.include?(statement)
48
+ end
49
+
50
+ private def stream(command)
51
+ output = ""
52
+ IO.popen(command) do |io|
53
+ until io.eof?
54
+ buffer = io.gets
55
+ output << buffer
56
+ @io.puts(buffer)
57
+ end
58
+ end
59
+ raise "Error running #{command}. Output:\n#{output}" unless $?.success?
60
+ output
61
+ end
62
+
63
+ private def write_target(template: nil, target:, contents: nil)
64
+ if template
65
+ template = @template_dir.join(template)
66
+ contents = ERB.new(template.read).result(binding)
67
+ end
68
+
69
+ target = @target_dir.join(target)
70
+ target.dirname.mkpath # Create directory if it doesn't exist already
71
+
72
+ if target.exist?
73
+ return if contents === target.read # identical
74
+ target.write(contents) if @thor_shell.file_collision(target) { contents }
75
+ else
76
+ target.write(contents)
77
+ end
78
+ end
79
+
80
+ private def cmd(command)
81
+ result = `#{command}`.chomp
82
+ raise "Command #{command} failed:\n#{result}" unless $?.success?
83
+ result
84
+ end
85
+ end
86
+ end
@@ -80,7 +80,7 @@ module Hatchet
80
80
 
81
81
  # No guardrails, will delete all apps that match the hatchet namespace
82
82
  def destroy_all
83
- get_apps
83
+ refresh_app_list
84
84
 
85
85
  (@finished_hatchet_apps + @unfinished_hatchet_apps).each do |app|
86
86
  begin
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "parallel_split_test"
4
+ gem "heroku_hatchet"
5
+ gem "rspec-retry"
@@ -0,0 +1,23 @@
1
+ require_relative "../spec_helper.rb"
2
+
3
+ RSpec.describe "This buildpack" do
4
+ it "has its own tests" do
5
+ raise "delete this and replace it with your own logic"
6
+
7
+ # Specify where you want your buildpack to go using :default
8
+ buildpacks = [:default, "heroku/ruby"]
9
+
10
+ # To deploy a different app modify the hatchet.json or
11
+ # commit an app to your source control and use a path
12
+ # instead of "default_ruby" here
13
+ Hatchet::Runner.new("default_ruby", buildpacks: buildpacks).tap do |app|
14
+ app.before_deploy do
15
+ # Modfiy the app here if you need
16
+ end
17
+ app.deploy do
18
+ # Assert the behavior you desire here
19
+ expect(app.output).to match("deployed to Heroku")
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ name: Check Changelog
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, reopened, edited, synchronize]
6
+ jobs:
7
+ build:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v1
11
+ - name: Check that CHANGELOG is touched
12
+ run: |
13
+ cat $GITHUB_EVENT_PATH | jq .pull_request.title | grep -i '\[\(\(changelog skip\)\|\(ci skip\)\)\]' || git diff remotes/origin/${{ github.base_ref }} --name-only | grep CHANGELOG.md
@@ -0,0 +1,45 @@
1
+ version: 2
2
+ references:
3
+ unit: &unit
4
+ run:
5
+ name: Run test suite
6
+ command: PARALLEL_SPLIT_TEST_PROCESSES=25 IS_RUNNING_ON_CI=1 bundle exec parallel_split_test spec/
7
+ restore: &restore
8
+ restore_cache:
9
+ keys:
10
+ - v1_bundler_deps-{{ .Environment.CIRCLE_JOB }}
11
+ save: &save
12
+ save_cache:
13
+ paths:
14
+ - ./vendor/bundle
15
+ key: v1_bundler_deps-{{ .Environment.CIRCLE_JOB }} # CIRCLE_JOB e.g. "ruby-2.5"
16
+ hatchet_setup: &hatchet_setup
17
+ run:
18
+ name: Hatchet setup
19
+ command: |
20
+ bundle exec hatchet ci:setup
21
+ bundle: &bundle
22
+ run:
23
+ name: install dependencies
24
+ command: |
25
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
26
+ bundle update
27
+ bundle clean
28
+ jobs:
29
+ "ruby-2.7":
30
+ docker:
31
+ - image: circleci/ruby:2.7
32
+ steps:
33
+ - checkout
34
+ - <<: *restore
35
+ - <<: *bundle
36
+ - <<: *hatchet_setup
37
+ - <<: *unit
38
+ - <<: *save
39
+
40
+ workflows:
41
+ version: 2
42
+ build:
43
+ jobs:
44
+ - "ruby-2.7"
45
+
@@ -0,0 +1,9 @@
1
+ version: 1
2
+ updates:
3
+ - package-ecosystem: "bundler"
4
+ directory: "/"
5
+ open-pull-requests-limit: 1 # Limit concurrent CI runs from executing
6
+ schedule:
7
+ interval: "weekly"
8
+ labels:
9
+ - "dependencies"
@@ -0,0 +1,11 @@
1
+ {
2
+ "ruby_apps": [
3
+ "sharpstone/default_ruby"
4
+ ],
5
+ "node_apps": [
6
+ "heroku/node-js-getting-started"
7
+ ],
8
+ "python_apps": [
9
+ "heroku/python-getting-started"
10
+ ]
11
+ }
@@ -0,0 +1,30 @@
1
+ require "bundler/setup"
2
+
3
+ require 'rspec/retry'
4
+
5
+ ENV["HATCHET_BUILDPACK_BASE"] = "<%= cmd("git config --get remote.origin.url") %>"
6
+
7
+ require 'hatchet'
8
+ require 'pathname'
9
+
10
+ RSpec.configure do |config|
11
+ # Enable flags like --only-failures and --next-failure
12
+ config.example_status_persistence_file_path = ".rspec_status"
13
+ config.verbose_retry = true # show retry status in spec process
14
+ config.default_retry_count = 2 if ENV['IS_RUNNING_ON_CI'] # retry all tests that fail again
15
+
16
+ config.expect_with :rspec do |c|
17
+ c.syntax = :expect
18
+ end
19
+ end
20
+
21
+ def run!(cmd)
22
+ out = `#{cmd}`
23
+ raise "Error running #{cmd}, output: #{out}" unless $?.success?
24
+ out
25
+ end
26
+
27
+ def spec_dir
28
+ Pathname.new(__dir__)
29
+ end
30
+
@@ -1,3 +1,3 @@
1
1
  module Hatchet
2
- VERSION = "7.0.0"
2
+ VERSION = "7.2.0"
3
3
  end
@@ -13,14 +13,29 @@ describe "AllowFailureGitTest" do
13
13
  }
14
14
 
15
15
  it "is marked as a failure if the release fails" do
16
+ app = Hatchet::GitApp.new("default_ruby", before_deploy: release_fail_proc, retries: 2)
17
+ def app.retry_error_message(*args); @test_attempts_count ||= 0; @test_attempts_count += 1; "" end
18
+ def app.test_attempts_count; @test_attempts_count ; end
19
+
16
20
  expect {
17
- Hatchet::GitApp.new("default_ruby", before_deploy: release_fail_proc).deploy {}
18
- }.to(raise_error(Hatchet::App::FailedReleaseError))
21
+ app.deploy {}
22
+ }.to raise_error { |error|
23
+ expect(error).to be_a(Hatchet::App::FailedReleaseError)
24
+ expect(error.message).to_not match("Everything up-to-date")
25
+ }
26
+
27
+ expect(app.test_attempts_count).to eq(2)
19
28
  end
20
29
 
21
30
  it "works when failure is allowed" do
22
- Hatchet::GitApp.new("default_ruby", before_deploy: release_fail_proc, allow_failure: true).deploy do |app|
23
- expect(app.output).to match("failing on release")
31
+ Hatchet::GitApp.new("default_ruby", before_deploy: release_fail_proc, allow_failure: true, retries: 3).tap do |app|
32
+ def app.retry_error_message(*args); @test_attempts_count ||= 0; @test_attempts_count += 1; "" end
33
+ def app.test_attempts_count; @test_attempts_count ; end
34
+
35
+ app.deploy do
36
+ expect(app.output).to match("failing on release")
37
+ expect(app.test_attempts_count).to eq(nil)
38
+ end
24
39
  end
25
40
  end
26
41
  end
@@ -1,6 +1,28 @@
1
1
  require("spec_helper")
2
2
 
3
3
  describe "AppTest" do
4
+ it "does not modify local files by mistake" do
5
+ Dir.mktmpdir do |dir_1|
6
+ app = Hatchet::Runner.new(dir_1)
7
+ Dir.mktmpdir do |dir_2|
8
+ Dir.chdir(dir_2) do
9
+ FileUtils.touch("foo.txt")
10
+
11
+ app.setup!
12
+ end
13
+
14
+ entries_array = Dir.entries(dir_2)
15
+ entries_array -= ["..", ".", "foo.txt"]
16
+ expect(entries_array).to be_empty
17
+
18
+
19
+ entries_array = Dir.entries(dir_1)
20
+ entries_array -= ["..", ".", "foo.txt"]
21
+ expect(entries_array).to be_empty
22
+ end
23
+ end
24
+ end
25
+
4
26
  it "rate throttles `git push` " do
5
27
  app = Hatchet::GitApp.new("default_ruby")
6
28
  def app.git_push_heroku_yall
@@ -1,9 +1,15 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe "GitAppTest" do
4
- it "can deploy git app" do
5
- Hatchet::GitApp.new("rails5_ruby_schema_format").deploy do |app|
6
- expect(app.run("ruby -v")).to match("2.6.6")
4
+ it "can deploy git app to the main branch" do
5
+ Hatchet::GitApp.new("lock_fail_main", allow_failure: true).deploy do |app|
6
+ expect(app.output).to match("INTENTIONAL ERROR")
7
7
  end
8
8
  end
9
+
10
+ it "returns the correct branch name on circle CI" do
11
+ skip("only runs on circle") unless ENV["CIRCLE_BRANCH"]
12
+
13
+ expect(Hatchet.git_branch).to eq(ENV["CIRCLE_BRANCH"])
14
+ end
9
15
  end
@@ -13,14 +13,15 @@ describe "LocalRepoTest" do
13
13
 
14
14
  it "repos checked into git" do
15
15
  begin
16
- app = Hatchet::App.new("repo_fixtures/different-folder-for-checked-in-repos/default_ruby")
17
- app.in_directory do
18
- expect(Dir.exist?(".git")).to eq(false)
19
- app.setup!
20
- expect(Dir.exist?(".git")).to eq(true)
16
+ fixture_dir = "repo_fixtures/different-folder-for-checked-in-repos/default_ruby"
17
+ app = Hatchet::App.new(fixture_dir)
18
+ def app.push_with_retry!; end
19
+
20
+ expect(Dir.exist?("#{fixture_dir}/.git")).to be_falsey
21
+
22
+ app.deploy do
23
+ expect(Dir.exist?(".git")).to be_truthy
21
24
  end
22
- ensure
23
- app.teardown! if app
24
25
  end
25
26
  end
26
27
  end
@@ -28,3 +28,54 @@ describe "LockTest" do
28
28
  expect(branch).to eq("main")
29
29
  end
30
30
  end
31
+
32
+ describe "isolated lock tests" do
33
+ it "works when there's no hatchet.lock" do
34
+ Dir.mktmpdir do |dir|
35
+ dir = Pathname.new(dir)
36
+
37
+ dir.join("hatchet.json").open("w+") do |f|
38
+ f.puts %Q{{ "foo": ["sharpstone/lock_fail_main_default_is_master"] }}
39
+ end
40
+
41
+ output = `cd #{dir} && hatchet lock 2>&1`
42
+
43
+ raise "Expected cmd `hatchet lock` to succeed, but it did not: #{output}" unless $?.success?
44
+ expect(output).to include("locking")
45
+
46
+ lockfile_contents = dir.join('hatchet.lock').read
47
+ expect(lockfile_contents).to include("repos/foo/lock_fail_main_default_is_master")
48
+ end
49
+ end
50
+
51
+ it "works when a project is locked to main but the default branch is master" do
52
+ Dir.mktmpdir do |dir|
53
+ dir = Pathname.new(dir)
54
+
55
+ dir.join("hatchet.json").open("w+") do |f|
56
+ f.puts %Q{{ "foo": ["sharpstone/lock_fail_main_default_is_master"] }}
57
+ end
58
+
59
+ dir.join("hatchet.lock").open("w+") do |f|
60
+ f.puts <<~EOM
61
+ ---
62
+ - - "./repos/foo/lock_fail_main_default_is_master"
63
+ - main
64
+ EOM
65
+ end
66
+
67
+ output = `cd #{dir} && hatchet install 2>&1`
68
+
69
+ raise "Expected cmd `hatchet install` to succeed, but it did not:\n#{output}" unless $?.success?
70
+ expect(output).to include("Installing")
71
+
72
+ lockfile_contents = dir.join('hatchet.lock').read
73
+ contents = YAML.safe_load(lockfile_contents).to_h
74
+ expect(contents).to eq({"./repos/foo/lock_fail_main_default_is_master" => "main"})
75
+
76
+ contents.each do |repo_dir, commit_or_branch|
77
+ expect(`cd #{dir.join(repo_dir)} && git describe --contains --all HEAD`).to match("main")
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+ describe "Hatchet::Init" do
4
+ def fake_buildpack_dir
5
+ Dir.mktmpdir do |dir|
6
+ FileUtils.mkdir_p("#{dir}/bin")
7
+ yield dir
8
+ end
9
+ end
10
+ it "raises an error when not pointing at the right directory" do
11
+ Dir.mktmpdir do |dir|
12
+ expect {
13
+ Hatchet::InitProject.new(dir: dir)
14
+ }.to raise_error(/Must run in a directory with a buildpack/)
15
+ end
16
+ end
17
+
18
+ # write_target(target: ".circleci/config.yml", template: "circleci_template.erb")
19
+ # write_target(target: "Gemfile", template: "Gemfile.erb")
20
+ # write_target(target: "hatchet.json", contents: "{}")
21
+ # write_target(target: "hatchet.lock", contents: YAML.dump({}))
22
+ # write_target(target: "spec/spec_helper.rb", template: "spec_helper.erb")
23
+ # write_target(target: "spec/hatchet/buildpack_spec.rb", template: "buildpack_spec.erb")
24
+ # write_target(target: ".github/dependabot.yml", template: "dependabot.erb")
25
+
26
+ it "generates files" do
27
+ fake_buildpack_dir do |dir|
28
+ fake_stdout = StringIO.new
29
+ init = Hatchet::InitProject.new(dir: dir, io: fake_stdout)
30
+ init.call
31
+
32
+ circle_ci_file = Pathname.new(dir).join(".circleci/config.yml")
33
+ expect(circle_ci_file.read).to match("parallel_split_test")
34
+
35
+ %W{
36
+ .circleci/config.yml
37
+ Gemfile
38
+ hatchet.json
39
+ hatchet.lock
40
+ spec/spec_helper.rb
41
+ spec/hatchet/buildpack_spec.rb
42
+ .github/dependabot.yml
43
+ .github/workflows/check_changelog.yml
44
+ .gitignore
45
+ }.each do |path|
46
+ expect(Pathname.new(dir).join(path)).to exist
47
+ end
48
+
49
+ expect(fake_stdout.string).to match("Bundle complete")
50
+ end
51
+ end
52
+ end
@@ -1,6 +1,22 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe "Reaper" do
4
+ it "destroy all" do
5
+ reaper = Hatchet::Reaper.new(api_rate_limit: Object.new, hatchet_app_limit: 1, io: StringIO.new)
6
+
7
+ def reaper.get_heroku_apps
8
+ @mock_apps ||= [
9
+ {"name" => "hatchet-t-unfinished", "id" => 2, "maintenance" => false, "created_at" => Time.now.to_s},
10
+ {"name" => "hatchet-t-foo", "id" => 1, "maintenance" => true, "created_at" => Time.now.to_s}
11
+ ]
12
+ end
13
+ def reaper.destroy_with_log(*args); @destroy_with_log_count ||= 0; @destroy_with_log_count += 1; end
14
+
15
+ reaper.destroy_all
16
+
17
+ expect(reaper.instance_variable_get("@destroy_with_log_count")).to eq(1)
18
+ end
19
+
4
20
  describe "cycle" do
5
21
  it "does not delete anything if under the limit" do
6
22
  reaper = Hatchet::Reaper.new(api_rate_limit: Object.new, hatchet_app_limit: 1, io: StringIO.new)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heroku_hatchet
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0
4
+ version: 7.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Schneeman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-11 00:00:00.000000000 Z
11
+ date: 2020-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: platform-api
@@ -192,11 +192,19 @@ files:
192
192
  - lib/hatchet/app.rb
193
193
  - lib/hatchet/config.rb
194
194
  - lib/hatchet/git_app.rb
195
+ - lib/hatchet/init_project.rb
195
196
  - lib/hatchet/reaper.rb
196
197
  - lib/hatchet/reaper/app_age.rb
197
198
  - lib/hatchet/reaper/reaper_throttle.rb
198
199
  - lib/hatchet/shell_throttle.rb
199
200
  - lib/hatchet/tasks.rb
201
+ - lib/hatchet/templates/Gemfile.erb
202
+ - lib/hatchet/templates/buildpack_spec.erb
203
+ - lib/hatchet/templates/check_changelog.erb
204
+ - lib/hatchet/templates/circleci_template.erb
205
+ - lib/hatchet/templates/dependabot.erb
206
+ - lib/hatchet/templates/hatchet_json.erb
207
+ - lib/hatchet/templates/spec_helper.erb
200
208
  - lib/hatchet/test_run.rb
201
209
  - lib/hatchet/version.rb
202
210
  - repo_fixtures/different-folder-for-checked-in-repos/default_ruby/Gemfile
@@ -211,6 +219,7 @@ files:
211
219
  - spec/hatchet/local_repo_spec.rb
212
220
  - spec/hatchet/lock_spec.rb
213
221
  - spec/spec_helper.rb
222
+ - spec/unit/init_spec.rb
214
223
  - spec/unit/reaper_spec.rb
215
224
  - spec/unit/shell_throttle.rb
216
225
  - tmp/parallel_runtime_test.log
@@ -248,5 +257,6 @@ test_files:
248
257
  - spec/hatchet/local_repo_spec.rb
249
258
  - spec/hatchet/lock_spec.rb
250
259
  - spec/spec_helper.rb
260
+ - spec/unit/init_spec.rb
251
261
  - spec/unit/reaper_spec.rb
252
262
  - spec/unit/shell_throttle.rb