shipit-engine 0.25.1 → 0.26.0

Sign up to get free protection for your applications and to get access to all the features.
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"