shipit-engine 0.25.1 → 0.26.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/shipit/merge_status_controller.rb +1 -1
  3. data/app/controllers/shipit/stacks_controller.rb +2 -1
  4. data/app/controllers/shipit/webhooks_controller.rb +15 -0
  5. data/app/jobs/shipit/cache_deploy_spec_job.rb +1 -0
  6. data/app/jobs/shipit/perform_task_job.rb +4 -2
  7. data/app/jobs/shipit/refresh_check_runs_job.rb +14 -0
  8. data/app/models/shipit/check_run.rb +71 -0
  9. data/app/models/shipit/commit.rb +25 -2
  10. data/app/models/shipit/deploy_spec.rb +1 -1
  11. data/app/models/shipit/pull_request.rb +2 -2
  12. data/app/models/shipit/status.rb +1 -1
  13. data/app/views/shipit/commits/_commit.html.erb +1 -1
  14. data/db/migrate/20181010150947_create_shipit_check_runs.rb +17 -0
  15. data/lib/shipit.rb +1 -0
  16. data/lib/shipit/octokit_check_runs.rb +9 -0
  17. data/lib/shipit/stack_commands.rb +8 -3
  18. data/lib/shipit/task_commands.rb +2 -3
  19. data/lib/shipit/version.rb +1 -1
  20. data/test/controllers/merge_status_controller_test.rb +15 -0
  21. data/test/controllers/stacks_controller_test.rb +12 -2
  22. data/test/controllers/webhooks_controller_test.rb +9 -0
  23. data/test/dummy/db/schema.rb +17 -1
  24. data/test/fixtures/payloads/check_suite_master.json +194 -0
  25. data/test/fixtures/shipit/check_runs.yml +21 -0
  26. data/test/fixtures/shipit/commits.yml +13 -0
  27. data/test/fixtures/shipit/stacks.yml +8 -0
  28. data/test/jobs/perform_task_job_test.rb +10 -1
  29. data/test/models/commits_test.rb +29 -2
  30. data/test/models/deploy_spec_test.rb +40 -0
  31. data/test/models/shipit/check_run_test.rb +51 -0
  32. data/test/models/status/group_test.rb +3 -3
  33. data/test/unit/deploy_commands_test.rb +5 -1
  34. metadata +128 -118
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6fc984a73561067126bdfb062310f453acadeda6
4
- data.tar.gz: 45bd1783e2aff0f6a17179181070372d9f69737c
3
+ metadata.gz: 84e2ecaebc907d7522d77553a552bafefa65cc7f
4
+ data.tar.gz: 59cd29bbf69566d7a0c2fa82ed3ad0f0086d307b
5
5
  SHA512:
6
- metadata.gz: 8a6acb0b4acab4a16a5b143c944f90e8dc628c4464a490ed1b316dce08a91e7d265b99482e3697cb677f420b1ef11d91c89e634c476e116b51760a9fc7fa0734
7
- data.tar.gz: db7fcd010f4e2c1403c17b318e4dd241c3a40973842bc614d7141f3f6236dacae155753264c9ff6a43d6b344378c67615ee3775132423d86467850180ebb8309
6
+ metadata.gz: fa96c8e445ac42b2b3138d6fbc00b3f61a290f06d491547ee6e1a83c09bf5f322a716af4539cf138bbc586d31e19944fc43438338722fc144ec0ce4113c11ae3
7
+ data.tar.gz: dab317ea37e1211b7cc765a2e30521bc7ca2054880f3df11402570d86fcc0326baf1e832e7182d647dc9c2f7f56f52f85ddc1e1b317546170862e077b4ae0eaa
@@ -60,7 +60,7 @@ module Shipit
60
60
  @stack ||= if params[:stack_id]
61
61
  Stack.from_param!(params[:stack_id])
62
62
  else
63
- scope = Stack.order(id: :asc).where(
63
+ scope = Stack.order(merge_queue_enabled: :desc, id: :asc).where(
64
64
  repo_owner: referrer_parser.repo_owner,
65
65
  repo_name: referrer_parser.repo_name,
66
66
  )
@@ -17,7 +17,7 @@ module Shipit
17
17
  return if flash.empty? && !stale?(last_modified: @stack.updated_at)
18
18
 
19
19
  @tasks = @stack.tasks.order(id: :desc).preload(:since_commit, :until_commit, :user).limit(10)
20
- @commits = @stack.undeployed_commits { |scope| scope.preload(:author, :statuses) }
20
+ @commits = @stack.undeployed_commits { |scope| scope.preload(:author, :statuses, :check_runs) }
21
21
  end
22
22
 
23
23
  def lookup
@@ -43,6 +43,7 @@ module Shipit
43
43
 
44
44
  def refresh
45
45
  RefreshStatusesJob.perform_later(stack_id: @stack.id)
46
+ RefreshCheckRunsJob.perform_later(stack_id: @stack.id)
46
47
  GithubSyncJob.perform_later(stack_id: @stack.id)
47
48
  flash[:success] = 'Refresh scheduled'
48
49
  redirect_to request.referer.presence || stack_path(@stack)
@@ -70,6 +70,20 @@ module Shipit
70
70
  end
71
71
  end
72
72
 
73
+ class CheckSuiteHandler < Handler
74
+ params do
75
+ requires :check_suite do
76
+ requires :head_sha, String
77
+ requires :head_branch, String
78
+ end
79
+ end
80
+ def process
81
+ stacks.where(branch: params.check_suite.head_branch).each do |stack|
82
+ stack.commits.where(sha: params.check_suite.head_sha).each(&:schedule_refresh_check_runs!)
83
+ end
84
+ end
85
+ end
86
+
73
87
  class MembershipHandler < Handler
74
88
  params do
75
89
  requires :action, String
@@ -114,6 +128,7 @@ module Shipit
114
128
  'push' => PushHandler,
115
129
  'status' => StatusHandler,
116
130
  'membership' => MembershipHandler,
131
+ 'check_suite' => CheckSuiteHandler,
117
132
  }.freeze
118
133
 
119
134
  def create
@@ -1,6 +1,7 @@
1
1
  module Shipit
2
2
  class CacheDeploySpecJob < BackgroundJob
3
3
  include BackgroundJob::Unique
4
+ on_duplicate :drop
4
5
 
5
6
  queue_as :deploys
6
7
 
@@ -60,8 +60,10 @@ module Shipit
60
60
  end
61
61
 
62
62
  def checkout_repository
63
- @task.acquire_git_cache_lock do
64
- capture! @commands.fetch
63
+ unless @commands.fetched?(@task.until_commit).tap(&:run).success?
64
+ @task.acquire_git_cache_lock do
65
+ capture! @commands.fetch
66
+ end
65
67
  end
66
68
  capture_all! @commands.clone
67
69
  capture! @commands.checkout(@task.until_commit)
@@ -0,0 +1,14 @@
1
+ module Shipit
2
+ class RefreshCheckRunsJob < BackgroundJob
3
+ queue_as :default
4
+
5
+ def perform(params)
6
+ if params[:commit_id]
7
+ Commit.find(params[:commit_id]).refresh_check_runs!
8
+ else
9
+ stack = Stack.find(params[:stack_id])
10
+ stack.commits.order(id: :desc).limit(30).each(&:refresh_check_runs!)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,71 @@
1
+ module Shipit
2
+ class CheckRun < ApplicationRecord
3
+ CONCLUSIONS = %w(success failure neutral cancelled timed_out action_required).freeze
4
+ include DeferredTouch
5
+ include Status::Common
6
+
7
+ belongs_to :stack, required: true
8
+ belongs_to :commit, required: true
9
+
10
+ deferred_touch commit: :updated_at
11
+
12
+ validates :conclusion, inclusion: {in: CONCLUSIONS, allow_nil: true}
13
+
14
+ class << self
15
+ def create_or_update_by!(selector:, attributes: {})
16
+ create!(selector.merge(attributes))
17
+ rescue ActiveRecord::RecordNotUnique
18
+ record = find_by!(selector)
19
+ record.update!(attributes)
20
+ record
21
+ end
22
+
23
+ def create_or_update_from_github!(stack_id, github_check_run)
24
+ create_or_update_by!(
25
+ selector: {
26
+ github_id: github_check_run.id,
27
+ },
28
+ attributes: {
29
+ stack_id: stack_id,
30
+ name: github_check_run.name,
31
+ conclusion: github_check_run.conclusion,
32
+ title: github_check_run.output.title.to_s.truncate(1_000),
33
+ details_url: github_check_run.details_url,
34
+ html_url: github_check_run.html_url,
35
+ },
36
+ )
37
+ end
38
+ end
39
+
40
+ def state
41
+ case conclusion
42
+ when nil, 'action_required'
43
+ 'pending'
44
+ when 'success', 'neutral'
45
+ 'success'
46
+ when 'failure', 'cancelled'
47
+ 'failure'
48
+ when 'timed_out'
49
+ 'error'
50
+ else
51
+ 'unknown'
52
+ end
53
+ end
54
+
55
+ def context
56
+ name
57
+ end
58
+
59
+ def target_url
60
+ html_url
61
+ end
62
+
63
+ def description
64
+ title
65
+ end
66
+
67
+ def to_partial_path
68
+ 'shipit/statuses/status'
69
+ end
70
+ end
71
+ end
@@ -7,6 +7,7 @@ module Shipit
7
7
  belongs_to :stack
8
8
  has_many :deploys
9
9
  has_many :statuses, -> { order(created_at: :desc) }, dependent: :destroy, inverse_of: :commit
10
+ has_many :check_runs, dependent: :destroy
10
11
  has_many :commit_deployments, dependent: :destroy
11
12
  has_many :release_statuses, dependent: :destroy
12
13
  belongs_to :pull_request, inverse_of: :merge_commit, optional: true
@@ -17,7 +18,8 @@ module Shipit
17
18
  after_commit { broadcast_update }
18
19
  after_create { stack.update_undeployed_commits_count }
19
20
 
20
- after_commit :schedule_refresh_statuses!, :schedule_fetch_stats!, :schedule_continuous_delivery, on: :create
21
+ after_commit :schedule_refresh_statuses!, :schedule_refresh_check_runs!, :schedule_fetch_stats!,
22
+ :schedule_continuous_delivery, on: :create
21
23
 
22
24
  belongs_to :author, class_name: 'User', inverse_of: :authored_commits
23
25
  belongs_to :committer, class_name: 'User', inverse_of: :commits
@@ -101,10 +103,18 @@ module Shipit
101
103
  record
102
104
  end
103
105
 
106
+ def statuses_and_check_runs
107
+ statuses + check_runs
108
+ end
109
+
104
110
  def schedule_refresh_statuses!
105
111
  RefreshStatusesJob.perform_later(commit_id: id)
106
112
  end
107
113
 
114
+ def schedule_refresh_check_runs!
115
+ RefreshCheckRunsJob.perform_later(commit_id: id)
116
+ end
117
+
108
118
  def refresh_statuses!
109
119
  github_statuses = stack.handle_github_redirections { Shipit.github.api.statuses(github_repo_name, sha) }
110
120
  github_statuses.each do |status|
@@ -118,6 +128,19 @@ module Shipit
118
128
  end
119
129
  end
120
130
 
131
+ def refresh_check_runs!
132
+ response = stack.handle_github_redirections do
133
+ Shipit.github.api.check_runs(github_repo_name, sha)
134
+ end
135
+ response.check_runs.each do |check_run|
136
+ create_or_update_check_run_from_github!(check_run)
137
+ end
138
+ end
139
+
140
+ def create_or_update_check_run_from_github!(github_check_run)
141
+ check_runs.create_or_update_from_github!(stack_id, github_check_run)
142
+ end
143
+
121
144
  def last_release_status
122
145
  @last_release_status ||= release_statuses.last || Status::Unknown.new(self)
123
146
  end
@@ -216,7 +239,7 @@ module Shipit
216
239
  end
217
240
 
218
241
  def status
219
- @status ||= Status::Group.compact(self, statuses)
242
+ @status ||= Status::Group.compact(self, statuses_and_check_runs)
220
243
  end
221
244
 
222
245
  def deployed?
@@ -235,7 +235,7 @@ module Shipit
235
235
  end
236
236
 
237
237
  def discover_task_definitions
238
- {}
238
+ config('tasks') || {}
239
239
  end
240
240
 
241
241
  def discover_dependencies_steps
@@ -185,11 +185,11 @@ module Shipit
185
185
 
186
186
  def all_status_checks_passed?
187
187
  return false unless head
188
- StatusChecker.new(head, head.statuses, stack.cached_deploy_spec).success?
188
+ StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec).success?
189
189
  end
190
190
 
191
191
  def any_status_checks_failed?
192
- status = StatusChecker.new(head, head.statuses, stack.cached_deploy_spec)
192
+ status = StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec)
193
193
  status.failure? || status.error? || status.missing?
194
194
  end
195
195
 
@@ -9,7 +9,7 @@ module Shipit
9
9
  belongs_to :stack, required: true
10
10
  belongs_to :commit, required: true
11
11
 
12
- deferred_touch stack: :updated_at, commit: :updated_at
12
+ deferred_touch commit: :updated_at
13
13
 
14
14
  validates :state, inclusion: {in: STATES, allow_blank: true}, presence: true
15
15
 
@@ -9,7 +9,7 @@
9
9
  <span class="commit-title"><%= render_commit_message_with_link commit %></span>
10
10
  <p class="commit-meta">
11
11
  <span class="sha"><%= render_commit_id_link(commit) %></span>
12
- <% if commit.additions? && commit.deletions? %>
12
+ <% if commit.additions.present? && commit.deletions.present? %>
13
13
  <span class="code-additions">+<%= commit.additions %></span>
14
14
  <span class="code-deletions">-<%= commit.deletions %></span>
15
15
  <% end %>
@@ -0,0 +1,17 @@
1
+ class CreateShipitCheckRuns < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :check_runs do |t|
4
+ t.references :stack, foreign_key: false, null: false
5
+ t.references :commit, foreign_key: false, null: false
6
+ t.bigint :github_id, null: false
7
+ t.string :name, null: false
8
+ t.string :conclusion, limit: 20, null: true
9
+ t.string :title, limit: 1024
10
+ t.string :details_url
11
+ t.string :html_url
12
+ t.timestamps
13
+
14
+ t.index %i(github_id commit_id), unique: true
15
+ end
16
+ end
17
+ end
@@ -30,6 +30,7 @@ require 'faraday-http-cache'
30
30
 
31
31
  require 'shipit/version'
32
32
 
33
+ require 'shipit/octokit_check_runs'
33
34
  require 'shipit/flock'
34
35
  require 'shipit/github_app'
35
36
  require 'shipit/paginator'
@@ -0,0 +1,9 @@
1
+ module OctokitCheckRuns
2
+ def check_runs(repo, sha, options = {})
3
+ paginate "#{Octokit::Repository.path repo}/commits/#{sha}/check-runs", options.reverse_merge(
4
+ accept: 'application/vnd.github.antiope-preview+json',
5
+ )
6
+ end
7
+ end
8
+
9
+ Octokit::Client.include(OctokitCheckRuns)
@@ -44,14 +44,19 @@ module Shipit
44
44
  def with_temporary_working_directory(commit: nil)
45
45
  commit ||= @stack.last_deployed_commit.presence || @stack.commits.last
46
46
 
47
- @stack.acquire_git_cache_lock do
48
- if !commit || !fetched?(commit).tap(&:run).success?
47
+ if !commit || !fetched?(commit).tap(&:run).success?
48
+ @stack.acquire_git_cache_lock do
49
49
  fetch.run!
50
50
  end
51
51
  end
52
52
 
53
53
  Dir.mktmpdir do |dir|
54
- git('clone', @stack.git_path, @stack.repo_name, '--origin', 'cache', chdir: dir).run!
54
+ git(
55
+ 'clone', @stack.git_path, @stack.repo_name,
56
+ '--origin', 'cache',
57
+ chdir: dir
58
+ ).run!
59
+
55
60
  git_dir = File.join(dir, @stack.repo_name)
56
61
  git('checkout', commit.sha, chdir: git_dir).run! if commit
57
62
  yield Pathname.new(git_dir)
@@ -53,11 +53,10 @@ module Shipit
53
53
  git(
54
54
  'clone',
55
55
  '--local',
56
- '--origin',
57
- 'cache',
56
+ '--origin', 'cache',
58
57
  @stack.git_path,
59
58
  @task.working_directory,
60
- chdir: @stack.deploys_path,
59
+ chdir: @stack.deploys_path
61
60
  ),
62
61
  git('remote', 'add', 'origin', @stack.repo_git_url, chdir: @task.working_directory),
63
62
  ]
@@ -1,3 +1,3 @@
1
1
  module Shipit
2
- VERSION = '0.25.1'.freeze
2
+ VERSION = '0.26.0'.freeze
3
3
  end
@@ -33,5 +33,20 @@ module Shipit
33
33
  assert_response :ok
34
34
  assert_predicate response.body, :blank?
35
35
  end
36
+
37
+ test "GET show prefers stacks with merge_queue_enabled" do
38
+ existing = shipit_stacks(:shipit)
39
+ Shipit::Stack.create(
40
+ repo_owner: existing.repo_owner,
41
+ repo_name: existing.repo_name,
42
+ environment: 'foo',
43
+ branch: existing.branch,
44
+ merge_queue_enabled: true,
45
+ )
46
+ existing.update!(merge_queue_enabled: false)
47
+ get :show, params: {referrer: 'https://github.com/Shopify/shipit-engine/pull/42', branch: 'master'}
48
+ assert_response :ok
49
+ assert_includes response.body, 'shipit-engine/foo'
50
+ end
36
51
  end
37
52
  end
@@ -51,6 +51,14 @@ module Shipit
51
51
  assert_response :ok
52
52
  end
53
53
 
54
+ test "#show with a single CheckRun is successful" do
55
+ @stack = shipit_stacks(:check_runs)
56
+ assert_not_equal 0, CheckRun.where(stack_id: @stack.id).count
57
+
58
+ get :show, params: {id: @stack.to_param}
59
+ assert_response :ok
60
+ end
61
+
54
62
  test "#show handles locked stacks without a lock_author" do
55
63
  @stack.update!(lock_reason: "I am a lock with no author")
56
64
  get :show, params: {id: @stack.to_param}
@@ -120,8 +128,10 @@ module Shipit
120
128
  request.env['HTTP_REFERER'] = stack_settings_path(@stack)
121
129
 
122
130
  assert_enqueued_with(job: RefreshStatusesJob, args: [stack_id: @stack.id]) do
123
- assert_enqueued_with(job: GithubSyncJob, args: [stack_id: @stack.id]) do
124
- post :refresh, params: {id: @stack.to_param}
131
+ assert_enqueued_with(job: RefreshCheckRunsJob, args: [stack_id: @stack.id]) do
132
+ assert_enqueued_with(job: GithubSyncJob, args: [stack_id: @stack.id]) do
133
+ post :refresh, params: {id: @stack.to_param}
134
+ end
125
135
  end
126
136
  end
127
137
 
@@ -55,6 +55,15 @@ module Shipit
55
55
  assert_response :ok
56
56
  end
57
57
 
58
+ test ":check_suite with the target branch queues a RefreshCheckRunsJob" do
59
+ request.headers['X-Github-Event'] = 'check_suite'
60
+
61
+ assert_enqueued_with(job: RefreshCheckRunsJob) do
62
+ post :create, body: payload(:check_suite_master), as: :json
63
+ assert_response :ok
64
+ end
65
+ end
66
+
58
67
  test "returns head :ok if request is ping" do
59
68
  @request.headers['X-Github-Event'] = 'ping'
60
69
 
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 20180906083930) do
13
+ ActiveRecord::Schema.define(version: 20181010150947) do
14
14
 
15
15
  create_table "api_clients", force: :cascade do |t|
16
16
  t.text "permissions", limit: 65535
@@ -22,6 +22,22 @@ ActiveRecord::Schema.define(version: 20180906083930) do
22
22
  t.index ["creator_id"], name: "index_api_clients_on_creator_id"
23
23
  end
24
24
 
25
+ create_table "check_runs", force: :cascade do |t|
26
+ t.integer "stack_id", null: false
27
+ t.integer "commit_id", null: false
28
+ t.bigint "github_id", null: false
29
+ t.string "name", null: false
30
+ t.string "conclusion", limit: 20
31
+ t.string "title", limit: 1024
32
+ t.string "details_url"
33
+ t.string "html_url"
34
+ t.datetime "created_at", null: false
35
+ t.datetime "updated_at", null: false
36
+ t.index ["commit_id"], name: "index_check_runs_on_commit_id"
37
+ t.index ["github_id", "commit_id"], name: "index_check_runs_on_github_id_and_commit_id", unique: true
38
+ t.index ["stack_id"], name: "index_check_runs_on_stack_id"
39
+ end
40
+
25
41
  create_table "commit_deployment_statuses", force: :cascade do |t|
26
42
  t.integer "commit_deployment_id"
27
43
  t.string "status"