shipit-engine 0.32.0 → 0.35.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (235) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -2
  3. data/app/assets/images/magic-solid.svg +1 -0
  4. data/app/assets/javascripts/shipit/repositories_search.js.coffee +60 -0
  5. data/app/assets/javascripts/shipit/{search.js.coffee → stack_search.js.coffee} +0 -0
  6. data/app/assets/stylesheets/_pages/_deploy.scss +2 -3
  7. data/app/assets/stylesheets/_pages/_repositories.scss +148 -0
  8. data/app/assets/stylesheets/_pages/_stacks.scss +19 -0
  9. data/app/assets/stylesheets/shipit.scss +1 -0
  10. data/app/controllers/shipit/api/ccmenu_controller.rb +1 -1
  11. data/app/controllers/shipit/api/deploys_controller.rb +2 -0
  12. data/app/controllers/shipit/api/{pull_requests_controller.rb → merge_requests_controller.rb} +8 -8
  13. data/app/controllers/shipit/api/rollbacks_controller.rb +2 -1
  14. data/app/controllers/shipit/api/stacks_controller.rb +15 -1
  15. data/app/controllers/shipit/deploys_controller.rb +1 -1
  16. data/app/controllers/shipit/merge_requests_controller.rb +31 -0
  17. data/app/controllers/shipit/merge_status_controller.rb +15 -15
  18. data/app/controllers/shipit/repositories_controller.rb +74 -0
  19. data/app/controllers/shipit/stacks_controller.rb +2 -2
  20. data/app/controllers/shipit/tasks_controller.rb +2 -2
  21. data/app/controllers/shipit/webhooks_controller.rb +23 -4
  22. data/app/helpers/shipit/chunks_helper.rb +2 -2
  23. data/app/helpers/shipit/github_url_helper.rb +8 -0
  24. data/app/helpers/shipit/shipit_helper.rb +0 -1
  25. data/app/helpers/shipit/stacks_helper.rb +4 -0
  26. data/app/jobs/shipit/create_on_github_job.rb +1 -0
  27. data/app/jobs/shipit/deliver_hook_job.rb +1 -1
  28. data/app/jobs/shipit/destroy_repository_job.rb +24 -0
  29. data/app/jobs/shipit/destroy_stack_job.rb +2 -2
  30. data/app/jobs/shipit/github_sync_job.rb +13 -9
  31. data/app/jobs/shipit/perform_task_job.rb +4 -98
  32. data/app/jobs/shipit/process_merge_requests_job.rb +32 -0
  33. data/app/jobs/shipit/refresh_merge_request_job.rb +11 -0
  34. data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +1 -1
  35. data/app/models/shipit/anonymous_user.rb +10 -2
  36. data/app/models/shipit/check_run.rb +38 -2
  37. data/app/models/shipit/command_line_user.rb +4 -0
  38. data/app/models/shipit/commit.rb +31 -20
  39. data/app/models/shipit/commit_checks.rb +14 -13
  40. data/app/models/shipit/commit_deployment.rb +3 -3
  41. data/app/models/shipit/commit_deployment_status.rb +3 -3
  42. data/app/models/shipit/deploy.rb +17 -11
  43. data/app/models/shipit/deploy_spec/file_system.rb +11 -5
  44. data/app/models/shipit/deploy_spec/lerna_discovery.rb +12 -4
  45. data/app/models/shipit/deploy_spec.rb +16 -4
  46. data/app/models/shipit/duration.rb +2 -0
  47. data/app/models/shipit/hook.rb +28 -2
  48. data/app/models/shipit/merge_request.rb +304 -0
  49. data/app/models/shipit/provisioning_handler/base.rb +30 -0
  50. data/app/models/shipit/provisioning_handler/unregistered_provisioning_handler.rb +35 -0
  51. data/app/models/shipit/provisioning_handler.rb +32 -0
  52. data/app/models/shipit/pull_request.rb +26 -265
  53. data/app/models/shipit/pull_request_assignment.rb +10 -0
  54. data/app/models/shipit/release_status.rb +1 -1
  55. data/app/models/shipit/repository.rb +63 -3
  56. data/app/models/shipit/review_stack.rb +130 -0
  57. data/app/models/shipit/review_stack_provisioning_queue.rb +39 -0
  58. data/app/models/shipit/rollback.rb +5 -0
  59. data/app/models/shipit/stack.rb +78 -30
  60. data/app/models/shipit/status/group.rb +1 -1
  61. data/app/models/shipit/task.rb +62 -9
  62. data/app/models/shipit/task_execution_strategy/base.rb +20 -0
  63. data/app/models/shipit/task_execution_strategy/default.rb +109 -0
  64. data/app/models/shipit/team.rb +4 -2
  65. data/app/models/shipit/user.rb +10 -1
  66. data/app/models/shipit/webhooks/handlers/pull_request/assigned_handler.rb +74 -0
  67. data/app/models/shipit/webhooks/handlers/pull_request/closed_handler.rb +68 -0
  68. data/app/models/shipit/webhooks/handlers/pull_request/edited_handler.rb +74 -0
  69. data/app/models/shipit/webhooks/handlers/pull_request/label_capturing_handler.rb +127 -0
  70. data/app/models/shipit/webhooks/handlers/pull_request/labeled_handler.rb +106 -0
  71. data/app/models/shipit/webhooks/handlers/pull_request/opened_handler.rb +83 -0
  72. data/app/models/shipit/webhooks/handlers/pull_request/reopened_handler.rb +88 -0
  73. data/app/models/shipit/webhooks/handlers/pull_request/review_stack_adapter.rb +103 -0
  74. data/app/models/shipit/webhooks/handlers/pull_request/unlabeled_handler.rb +107 -0
  75. data/app/models/shipit/webhooks/handlers/push_handler.rb +4 -1
  76. data/app/models/shipit/webhooks.rb +10 -0
  77. data/app/serializers/shipit/deploy_serializer.rb +6 -0
  78. data/app/serializers/shipit/merge_request_serializer.rb +21 -0
  79. data/app/serializers/shipit/pull_request_serializer.rb +5 -8
  80. data/app/serializers/shipit/review_stack_serializer.rb +7 -0
  81. data/app/serializers/shipit/stack_serializer.rb +7 -6
  82. data/app/serializers/shipit/tail_task_serializer.rb +10 -2
  83. data/app/serializers/shipit/task_serializer.rb +1 -1
  84. data/app/validators/subset_validator.rb +1 -1
  85. data/app/views/layouts/merge_status.html.erb +1 -1
  86. data/app/views/shipit/merge_requests/_merge_request.html.erb +29 -0
  87. data/app/views/shipit/{pull_requests → merge_requests}/index.html.erb +2 -2
  88. data/app/views/shipit/merge_requests/merge_requests/_pull_request.html.erb +29 -0
  89. data/app/views/shipit/merge_requests/merge_requests/index.html.erb +20 -0
  90. data/app/views/shipit/merge_status/_merge_queue_button.html.erb +3 -3
  91. data/app/views/shipit/merge_status/backlogged.html.erb +1 -1
  92. data/app/views/shipit/merge_status/failure.html.erb +1 -1
  93. data/app/views/shipit/merge_status/locked.html.erb +1 -1
  94. data/app/views/shipit/merge_status/success.html.erb +2 -2
  95. data/app/views/shipit/repositories/_header.html.erb +19 -0
  96. data/app/views/shipit/repositories/index.html.erb +31 -0
  97. data/app/views/shipit/repositories/new.html.erb +23 -0
  98. data/app/views/shipit/repositories/settings.html.erb +53 -0
  99. data/app/views/shipit/repositories/show.html.erb +30 -0
  100. data/app/views/shipit/stacks/_banners.html.erb +15 -1
  101. data/app/views/shipit/stacks/_header.html.erb +5 -2
  102. data/app/views/shipit/stacks/_stack.html.erb +8 -0
  103. data/app/views/shipit/stacks/index.html.erb +2 -1
  104. data/app/views/shipit/stacks/new.html.erb +1 -1
  105. data/app/views/shipit/stacks/settings.html.erb +5 -5
  106. data/app/views/shipit/stacks/show.html.erb +1 -1
  107. data/app/views/shipit/tasks/_task_output.html.erb +1 -1
  108. data/config/routes.rb +15 -5
  109. data/config/secrets.development.example.yml +24 -0
  110. data/config/secrets.development.shopify.yml +20 -9
  111. data/db/migrate/20200706145406_add_review_stacks.rb +12 -0
  112. data/db/migrate/20200804144639_rename_pull_request_to_merge_request.rb +7 -0
  113. data/db/migrate/20200804161512_rename_commits_pull_request_id_to_merge_request_id.rb +5 -0
  114. data/db/migrate/20200813134712_recreate_shipit_pull_requests.rb +22 -0
  115. data/db/migrate/20200813194056_create_pull_request_assignments.rb +8 -0
  116. data/db/migrate/20201001125502_add_provision_pr_stacks_flag_to_repositories.rb +7 -0
  117. data/db/migrate/20201008145809_add_retry_attempt_to_tasks.rb +5 -0
  118. data/db/migrate/20201008152744_add_max_retries_to_tasks.rb +5 -0
  119. data/db/migrate/20210325194053_remove_stacks_branch_default.rb +5 -0
  120. data/db/migrate/20210504200438_add_github_updated_at_to_check_runs.rb +5 -0
  121. data/db/migrate/20210823075617_change_check_runs_github_updated_at_default.rb +5 -0
  122. data/lib/shipit/command.rb +7 -6
  123. data/lib/shipit/commands.rb +18 -5
  124. data/lib/shipit/engine.rb +2 -0
  125. data/lib/shipit/flock.rb +8 -1
  126. data/lib/shipit/github_app.rb +8 -6
  127. data/lib/shipit/octokit_iterator.rb +3 -3
  128. data/lib/shipit/review_stack_commands.rb +8 -0
  129. data/lib/shipit/simple_message_verifier.rb +2 -2
  130. data/lib/shipit/stack_commands.rb +36 -7
  131. data/lib/shipit/task_commands.rb +8 -1
  132. data/lib/shipit/version.rb +1 -1
  133. data/lib/shipit.rb +50 -16
  134. data/lib/snippets/publish-lerna-independent-packages +35 -34
  135. data/lib/snippets/publish-lerna-independent-packages-legacy +39 -0
  136. data/lib/tasks/cron.rake +11 -2
  137. data/test/controllers/api/ccmenu_controller_test.rb +1 -1
  138. data/test/controllers/api/deploys_controller_test.rb +17 -0
  139. data/test/controllers/api/{pull_requests_controller_test.rb → merge_requests_controller_test.rb} +12 -12
  140. data/test/controllers/api/outputs_controller_test.rb +1 -0
  141. data/test/controllers/api/rollback_controller_test.rb +1 -1
  142. data/test/controllers/api/stacks_controller_test.rb +42 -8
  143. data/test/controllers/{pull_requests_controller_test.rb → merge_requests_controller_test.rb} +6 -6
  144. data/test/controllers/repositories_controller_test.rb +71 -0
  145. data/test/controllers/stacks_controller_test.rb +9 -1
  146. data/test/controllers/tasks_controller_test.rb +14 -2
  147. data/test/controllers/webhooks_controller_test.rb +27 -12
  148. data/test/dummy/app/assets/config/manifest.js +3 -0
  149. data/test/dummy/config/application.rb +7 -2
  150. data/test/dummy/config/database.yml +9 -0
  151. data/test/dummy/config/environments/development.rb +1 -4
  152. data/test/dummy/config/environments/test.rb +0 -5
  153. data/test/dummy/config/secrets_double_github_app.yml +79 -0
  154. data/test/dummy/db/schema.rb +56 -17
  155. data/test/dummy/db/seeds.rb +2 -1
  156. data/test/fixtures/payloads/check_suite_master.json +4 -32
  157. data/test/fixtures/payloads/invalid_pull_request.json +117 -0
  158. data/test/fixtures/payloads/provision_disabled_pull_request.json +454 -0
  159. data/test/fixtures/payloads/pull_request_assigned.json +480 -0
  160. data/test/fixtures/payloads/pull_request_closed.json +454 -0
  161. data/test/fixtures/payloads/pull_request_labeled.json +461 -0
  162. data/test/fixtures/payloads/pull_request_opened.json +454 -0
  163. data/test/fixtures/payloads/pull_request_reopened.json +454 -0
  164. data/test/fixtures/payloads/pull_request_unlabeled.json +454 -0
  165. data/test/fixtures/payloads/pull_request_with_no_repo.json +454 -0
  166. data/test/fixtures/payloads/push_master.json +1 -1
  167. data/test/fixtures/payloads/push_not_master.json +1 -1
  168. data/test/fixtures/shipit/commits.yml +17 -4
  169. data/test/fixtures/shipit/hooks.yml +1 -0
  170. data/test/fixtures/shipit/merge_requests.yml +141 -0
  171. data/test/fixtures/shipit/pull_request_assignments.yml +3 -0
  172. data/test/fixtures/shipit/pull_requests.yml +10 -131
  173. data/test/fixtures/shipit/repositories.yml +1 -0
  174. data/test/fixtures/shipit/stacks.yml +145 -0
  175. data/test/fixtures/shipit/statuses.yml +9 -0
  176. data/test/fixtures/shipit/tasks.yml +4 -1
  177. data/test/fixtures/shipit/users.yml +7 -0
  178. data/test/helpers/json_helper.rb +5 -1
  179. data/test/helpers/payloads_helper.rb +4 -0
  180. data/test/jobs/chunk_rollup_job_test.rb +15 -1
  181. data/test/jobs/destroy_repository_job_test.rb +27 -0
  182. data/test/jobs/github_sync_job_test.rb +2 -1
  183. data/test/jobs/perform_task_job_test.rb +8 -8
  184. data/test/jobs/{merge_pull_requests_job_test.rb → process_merge_requests_job_test.rb} +18 -18
  185. data/test/lib/shipit/deploy_commands_test.rb +16 -0
  186. data/test/lib/shipit/task_commands_test.rb +17 -0
  187. data/test/models/commit_deployment_status_test.rb +3 -3
  188. data/test/models/commits_test.rb +24 -13
  189. data/test/models/deploy_spec_test.rb +64 -24
  190. data/test/models/deploys_test.rb +188 -14
  191. data/test/models/hook_test.rb +30 -1
  192. data/test/models/{pull_request_test.rb → merge_request_test.rb} +49 -34
  193. data/test/models/pull_request_assignment_test.rb +16 -0
  194. data/test/models/shipit/check_run_test.rb +124 -5
  195. data/test/models/shipit/provisioning_handler/base_test.rb +33 -0
  196. data/test/models/shipit/provisioning_handler/unregistered_provisioning_handler_test.rb +49 -0
  197. data/test/models/shipit/provisioning_handler_test.rb +64 -0
  198. data/test/models/shipit/pull_request_test.rb +52 -0
  199. data/test/models/shipit/repository_test.rb +5 -1
  200. data/test/models/shipit/review_stack_provision_status_test.rb +77 -0
  201. data/test/models/shipit/review_stack_provisioning_queue_test.rb +63 -0
  202. data/test/models/shipit/review_stack_test.rb +91 -0
  203. data/test/models/{stacks_test.rb → shipit/stacks_test.rb} +52 -8
  204. data/test/models/shipit/webhooks/handlers/pull_request/assigned_handler_test.rb +45 -0
  205. data/test/models/shipit/webhooks/handlers/pull_request/closed_handler_test.rb +192 -0
  206. data/test/models/shipit/webhooks/handlers/pull_request/edited_handler_test.rb +47 -0
  207. data/test/models/shipit/webhooks/handlers/pull_request/label_capturing_handler_test.rb +209 -0
  208. data/test/models/shipit/webhooks/handlers/pull_request/labeled_handler_test.rb +332 -0
  209. data/test/models/shipit/webhooks/handlers/pull_request/opened_handler_test.rb +238 -0
  210. data/test/models/shipit/webhooks/handlers/pull_request/reopened_handler_test.rb +282 -0
  211. data/test/models/shipit/webhooks/handlers/pull_request/review_stack_adapter_test.rb +107 -0
  212. data/test/models/shipit/webhooks/handlers/pull_request/unlabeled_handler_test.rb +324 -0
  213. data/test/models/shipit/{wehbooks → webhooks}/handlers_test.rb +0 -0
  214. data/test/models/tasks_test.rb +66 -3
  215. data/test/serializers/shipit/pull_request_serializer_test.rb +29 -0
  216. data/test/test_helper.rb +15 -0
  217. data/test/unit/anonymous_user_serializer_test.rb +1 -1
  218. data/test/unit/command_test.rb +8 -3
  219. data/test/unit/commit_serializer_test.rb +1 -1
  220. data/test/unit/deploy_commands_test.rb +73 -17
  221. data/test/unit/deploy_serializer_test.rb +1 -1
  222. data/test/unit/github_app_test.rb +2 -3
  223. data/test/unit/github_apps_test.rb +416 -0
  224. data/test/unit/github_url_helper_test.rb +5 -0
  225. data/test/unit/shipit_deployment_checks_test.rb +77 -0
  226. data/test/unit/shipit_task_execution_strategy_test.rb +47 -0
  227. data/test/unit/shipit_test.rb +14 -0
  228. data/test/unit/user_serializer_test.rb +1 -1
  229. metadata +306 -188
  230. data/app/controllers/shipit/pull_requests_controller.rb +0 -31
  231. data/app/jobs/shipit/merge_pull_requests_job.rb +0 -32
  232. data/app/jobs/shipit/refresh_pull_request_job.rb +0 -11
  233. data/app/views/shipit/pull_requests/_pull_request.html.erb +0 -29
  234. data/test/fixtures/shipit/output_chunks.yml +0 -47
  235. data/test/models/output_chunk_test.rb +0 -21
@@ -20,15 +20,15 @@ module Shipit
20
20
  return if github_id?
21
21
 
22
22
  response = begin
23
- create_deployment_on_github(author.github_api)
23
+ create_deployment_on_github(stack.github_api)
24
24
  rescue Octokit::ClientError
25
- raise if Shipit.github.api == author.github_api
25
+ raise if Shipit.github(organization: stack.repository.owner).api == stack.github_api
26
26
  # If the deploy author didn't gave us the permission to create the deployment we falback the the main shipit
27
27
  # user.
28
28
  #
29
29
  # Octokit currently raise NotFound, but I'm convinced it should be Forbidden if the user can see the repository.
30
30
  # So to be future proof I catch boths.
31
- create_deployment_on_github(Shipit.github.api)
31
+ create_deployment_on_github(stack.github_api)
32
32
  end
33
33
  update!(github_id: response.id, api_url: response.url)
34
34
  end
@@ -12,15 +12,15 @@ module Shipit
12
12
  def create_on_github!
13
13
  return if github_id?
14
14
  response = begin
15
- create_status_on_github(author.github_api)
15
+ create_status_on_github(stack.github_api)
16
16
  rescue Octokit::ClientError
17
- raise if Shipit.github.api == author.github_api
17
+ raise if Shipit.github(organization: stack.repository.owner).api == stack.github_api
18
18
  # If the deploy author didn't gave us the permission to create the deployment we falback the the main shipit
19
19
  # user.
20
20
  #
21
21
  # Octokit currently raise NotFound, but I'm convinced it should be Forbidden if the user can see the repository.
22
22
  # So to be future proof I catch boths.
23
- create_status_on_github(Shipit.github.api)
23
+ create_status_on_github(stack.github_api)
24
24
  end
25
25
  update!(github_id: response.id, api_url: response.url)
26
26
  end
@@ -15,8 +15,8 @@ module Shipit
15
15
  after_transition any => any, do: :update_last_deploy_time
16
16
  end
17
17
 
18
- belongs_to :until_commit, class_name: 'Commit', required: true, inverse_of: :deploys
19
- belongs_to :since_commit, class_name: 'Commit', required: true, inverse_of: :deploys
18
+ belongs_to :until_commit, class_name: 'Commit', required: true
19
+ belongs_to :since_commit, class_name: 'Commit', required: true
20
20
  has_many :commit_deployments, dependent: :destroy, inverse_of: :task, foreign_key: :task_id do
21
21
  GITHUB_STATUSES = {
22
22
  'pending' => 'pending',
@@ -92,18 +92,21 @@ module Shipit
92
92
  env: env&.to_h || {},
93
93
  allow_concurrency: force,
94
94
  ignored_safeties: force,
95
+ max_retries: stack.retries_on_rollback,
95
96
  )
96
97
  end
97
98
 
98
99
  # Rolls the stack back to this deploy
99
- def trigger_rollback(user = AnonymousUser.new, env: nil, force: false)
100
+ def trigger_rollback(user = AnonymousUser.new, env: nil, force: false, lock: true)
100
101
  rollback = build_rollback(user, env: env, force: force)
101
102
  rollback.save!
102
103
  rollback.enqueue
103
104
 
104
- lock_reason = "A rollback for #{rollback.since_commit.sha} has been triggered. " \
105
- "Please make sure the reason for the rollback has been addressed before deploying again."
106
- stack.update!(lock_reason: lock_reason, lock_author_id: user.id)
105
+ if lock
106
+ lock_reason = "A rollback for #{rollback.since_commit.sha} has been triggered. " \
107
+ "Please make sure the reason for the rollback has been addressed before deploying again."
108
+ stack.update!(lock_reason: lock_reason, lock_author_id: user.id)
109
+ end
107
110
 
108
111
  rollback
109
112
  end
@@ -208,7 +211,7 @@ module Shipit
208
211
  end
209
212
 
210
213
  def report_complete!
211
- if stack.release_status? && stack.release_status_delay.positive?
214
+ if stack.release_status? && !stack.release_status_delay.zero?
212
215
  enter_validation!
213
216
  else
214
217
  super
@@ -268,10 +271,13 @@ module Shipit
268
271
  when 'aborted', 'aborting'
269
272
  append_release_status('failure', "The deploy on #{stack.environment} was canceled")
270
273
  when 'validating'
271
- if stack.release_status_delay.positive?
272
- append_release_status('pending', "The deploy on #{stack.environment} succeeded")
273
- MarkDeployHealthyJob.set(wait: stack.release_status_delay).perform_later(self)
274
- end
274
+ append_release_status(
275
+ 'pending',
276
+ "The deploy on #{stack.environment} succeeded"
277
+ ) unless stack.release_status_delay.zero?
278
+
279
+ MarkDeployHealthyJob.set(wait: stack.release_status_delay)
280
+ .perform_later(self) if stack.release_status_delay.positive?
275
281
  when 'success'
276
282
  if stack.release_status_delay.zero?
277
283
  append_release_status('success', "The deploy on #{stack.environment} succeeded")
@@ -13,6 +13,7 @@ module Shipit
13
13
  def initialize(app_dir, env)
14
14
  @app_dir = Pathname(app_dir)
15
15
  @env = env
16
+ super(nil)
16
17
  end
17
18
 
18
19
  def cacheable
@@ -32,10 +33,10 @@ module Shipit
32
33
  def cacheable_config
33
34
  (config || {}).deep_merge(
34
35
  'merge' => {
35
- 'require' => pull_request_required_statuses,
36
- 'ignore' => pull_request_ignored_statuses,
37
- 'revalidate_after' => revalidate_pull_requests_after&.to_i,
38
- 'method' => pull_request_merge_method,
36
+ 'require' => merge_request_required_statuses,
37
+ 'ignore' => merge_request_ignored_statuses,
38
+ 'revalidate_after' => revalidate_merge_requests_after&.to_i,
39
+ 'method' => merge_request_merge_method,
39
40
  'max_divergence' => {
40
41
  'commits' => max_divergence_commits&.to_i,
41
42
  'age' => max_divergence_age&.to_i,
@@ -63,13 +64,18 @@ module Shipit
63
64
  'delay' => release_status_delay,
64
65
  },
65
66
  'dependencies' => { 'override' => dependencies_steps },
67
+ 'provision' => { 'handler_name' => provisioning_handler_name },
66
68
  'deploy' => {
67
69
  'override' => deploy_steps,
68
70
  'variables' => deploy_variables.map(&:to_h),
69
71
  'max_commits' => maximum_commits_per_deploy,
70
72
  'interval' => pause_between_deploys,
73
+ 'retries' => retries_on_deploy,
74
+ },
75
+ 'rollback' => {
76
+ 'override' => rollback_steps,
77
+ 'retries' => retries_on_rollback,
71
78
  },
72
- 'rollback' => { 'override' => rollback_steps },
73
79
  'fetch' => fetch_deployed_revision_steps,
74
80
  'tasks' => cacheable_tasks,
75
81
  )
@@ -76,10 +76,18 @@ module Shipit
76
76
  end
77
77
 
78
78
  def publish_independent_packages
79
- [
80
- 'assert-lerna-independent-version-tags',
81
- 'publish-lerna-independent-packages',
82
- ]
79
+ command = if lerna_lerna >= LATEST_MAJOR_VERSION
80
+ [
81
+ 'assert-lerna-independent-version-tags',
82
+ 'publish-lerna-independent-packages',
83
+ ]
84
+ else
85
+ [
86
+ 'assert-lerna-independent-version-tags',
87
+ 'publish-lerna-independent-packages-legacy',
88
+ ]
89
+ end
90
+ command
83
91
  end
84
92
 
85
93
  def publish_fixed_version_packages
@@ -104,6 +104,10 @@ module Shipit
104
104
  Duration.parse(config('deploy', 'interval') { 0 })
105
105
  end
106
106
 
107
+ def provisioning_handler_name
108
+ config('provision', 'handler_name')
109
+ end
110
+
107
111
  def deploy_steps
108
112
  around_steps('deploy') do
109
113
  config('deploy', 'override') { discover_deploy_steps }
@@ -122,6 +126,10 @@ module Shipit
122
126
  deploy_variables.map { |v| [v.name, v.default] }.to_h
123
127
  end
124
128
 
129
+ def retries_on_deploy
130
+ config('deploy', 'retries') { nil }
131
+ end
132
+
125
133
  def rollback_steps
126
134
  around_steps('rollback') do
127
135
  config('rollback', 'override') { discover_rollback_steps }
@@ -132,6 +140,10 @@ module Shipit
132
140
  rollback_steps || cant_detect!(:rollback)
133
141
  end
134
142
 
143
+ def retries_on_rollback
144
+ config('rollback', 'retries') { nil }
145
+ end
146
+
135
147
  def fetch_deployed_revision_steps
136
148
  config('fetch') || discover_fetch_deployed_revision_steps
137
149
  end
@@ -179,12 +191,12 @@ module Shipit
179
191
  Array.wrap(config('ci', 'blocking'))
180
192
  end
181
193
 
182
- def pull_request_merge_method
194
+ def merge_request_merge_method
183
195
  method = config('merge', 'method')
184
196
  method if %w(merge rebase squash).include?(method)
185
197
  end
186
198
 
187
- def pull_request_required_statuses
199
+ def merge_request_required_statuses
188
200
  if config('merge', 'require') || config('merge', 'ignore')
189
201
  Array.wrap(config('merge', 'require'))
190
202
  else
@@ -192,7 +204,7 @@ module Shipit
192
204
  end
193
205
  end
194
206
 
195
- def pull_request_ignored_statuses
207
+ def merge_request_ignored_statuses
196
208
  if config('merge', 'require') || config('merge', 'ignore')
197
209
  Array.wrap(config('merge', 'ignore')) + [release_status_context].compact
198
210
  else
@@ -200,7 +212,7 @@ module Shipit
200
212
  end
201
213
  end
202
214
 
203
- def revalidate_pull_requests_after
215
+ def revalidate_merge_requests_after
204
216
  if timeout = config('merge', 'revalidate_after')
205
217
  begin
206
218
  Duration.parse(timeout)
@@ -20,6 +20,8 @@ module Shipit
20
20
 
21
21
  class << self
22
22
  def parse(value)
23
+ return new(-1) if value.to_s == "-1"
24
+
23
25
  unless match = FORMAT.match(value.to_s)
24
26
  raise ParseError, "not a duration: #{value.inspect}"
25
27
  end
@@ -1,12 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
  module Shipit
3
3
  class Hook < Record
4
+ class DeliverySigner
5
+ attr_reader :secret
6
+
7
+ ALGORITHM = 'sha256'
8
+
9
+ def initialize(secret)
10
+ @secret = secret
11
+ end
12
+
13
+ def sign(payload)
14
+ hmac = OpenSSL::HMAC.hexdigest(ALGORITHM, secret, payload)
15
+ "#{ALGORITHM}=#{hmac}"
16
+ end
17
+ end
18
+
4
19
  class DeliverySpec
5
- def initialize(event:, url:, content_type:, payload:)
20
+ def initialize(event:, url:, content_type:, payload:, secret:)
6
21
  @event = event
7
22
  @url = url
8
23
  @content_type = content_type
9
24
  @payload = payload
25
+ @secret = secret
10
26
  end
11
27
 
12
28
  def send!
@@ -15,7 +31,7 @@ module Shipit
15
31
 
16
32
  private
17
33
 
18
- attr_reader :event, :url, :content_type, :payload
34
+ attr_reader :event, :url, :content_type, :payload, :secret
19
35
 
20
36
  def http
21
37
  Faraday::Connection.new do |connection|
@@ -29,9 +45,16 @@ module Shipit
29
45
  'User-Agent' => 'Shipit Webhook',
30
46
  'Content-Type' => content_type,
31
47
  'X-Shipit-Event' => event,
48
+ 'X-Shipit-Signature' => signature,
32
49
  'Accept' => '*/*',
33
50
  }
34
51
  end
52
+
53
+ def signature
54
+ return nil if secret.blank?
55
+
56
+ DeliverySigner.new(secret).sign(payload)
57
+ end
35
58
  end
36
59
 
37
60
  default_scope { order :id }
@@ -45,6 +68,7 @@ module Shipit
45
68
 
46
69
  EVENTS = %w(
47
70
  stack
71
+ review_stack
48
72
  task
49
73
  deploy
50
74
  rollback
@@ -53,6 +77,7 @@ module Shipit
53
77
  deployable_status
54
78
  merge_status
55
79
  merge
80
+ pull_request
56
81
  ).freeze
57
82
 
58
83
  belongs_to :stack, required: false
@@ -117,6 +142,7 @@ module Shipit
117
142
  url: delivery_url,
118
143
  content_type: CONTENT_TYPES[content_type],
119
144
  payload: serialize_payload(payload),
145
+ secret: secret,
120
146
  )
121
147
  end
122
148
 
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+ module Shipit
3
+ class MergeRequest < ApplicationRecord
4
+ include DeferredTouch
5
+
6
+ MERGE_REQUEST_FIELD = 'Merge-Requested-By'
7
+
8
+ WAITING_STATUSES = %w(fetching pending).freeze
9
+ QUEUED_STATUSES = %w(pending revalidating).freeze
10
+ REJECTION_REASONS = %w(ci_missing ci_failing merge_conflict requires_rebase).freeze
11
+ InvalidTransition = Class.new(StandardError)
12
+ NotReady = Class.new(StandardError)
13
+
14
+ class StatusChecker < Status::Group
15
+ def initialize(commit, statuses, deploy_spec)
16
+ @deploy_spec = deploy_spec
17
+ super(commit, statuses)
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :deploy_spec
23
+
24
+ def reject_hidden(statuses)
25
+ statuses.reject { |s| ignored_statuses.include?(s.context) }
26
+ end
27
+
28
+ def reject_allowed_to_fail(statuses)
29
+ statuses.reject { |s| ignored_statuses.include?(s.context) }
30
+ end
31
+
32
+ def ignored_statuses
33
+ deploy_spec&.merge_request_ignored_statuses || []
34
+ end
35
+
36
+ def required_statuses
37
+ deploy_spec&.merge_request_required_statuses || []
38
+ end
39
+ end
40
+
41
+ belongs_to :stack
42
+ belongs_to :head, class_name: 'Shipit::Commit', optional: true
43
+ belongs_to :base_commit, class_name: 'Shipit::Commit', optional: true
44
+ belongs_to :merge_requested_by, class_name: 'Shipit::User', optional: true
45
+ has_one :merge_commit, class_name: 'Shipit::Commit'
46
+
47
+ deferred_touch stack: :updated_at
48
+
49
+ validates :number, presence: true, uniqueness: { scope: :stack_id }
50
+
51
+ scope :waiting, -> { where(merge_status: WAITING_STATUSES) }
52
+ scope :pending, -> { where(merge_status: 'pending') }
53
+ scope :to_be_merged, -> { pending.order(merge_requested_at: :asc) }
54
+ scope :queued, -> { where(merge_status: QUEUED_STATUSES).order(merge_requested_at: :asc) }
55
+
56
+ after_save :record_merge_status_change
57
+ after_commit :emit_hooks
58
+
59
+ state_machine :merge_status, initial: :fetching do
60
+ state :fetching
61
+ state :pending
62
+ state :rejected
63
+ state :canceled
64
+ state :merged
65
+ state :revalidating
66
+
67
+ event :fetched do
68
+ transition fetching: :pending
69
+ end
70
+
71
+ event :reject do
72
+ transition pending: :rejected
73
+ end
74
+
75
+ event :revalidate do
76
+ transition pending: :revalidating
77
+ end
78
+
79
+ event :cancel do
80
+ transition any => :canceled
81
+ end
82
+
83
+ event :complete do
84
+ transition pending: :merged
85
+ end
86
+
87
+ event :retry do
88
+ transition %i(rejected canceled revalidating) => :pending
89
+ end
90
+
91
+ before_transition rejected: any do |pr|
92
+ pr.rejection_reason = nil
93
+ end
94
+
95
+ before_transition %i(fetching rejected canceled) => :pending do |pr|
96
+ pr.merge_requested_at = Time.now.utc
97
+ end
98
+
99
+ before_transition any => :pending do |pr|
100
+ pr.revalidated_at = Time.now.utc
101
+ end
102
+
103
+ before_transition %i(pending) => :merged do |pr|
104
+ Stack.increment_counter(:undeployed_commits_count, pr.stack_id)
105
+ end
106
+ end
107
+
108
+ def self.schedule_merges
109
+ Shipit::Stack.where(merge_queue_enabled: true).find_each(&:schedule_merges)
110
+ end
111
+
112
+ def self.extract_number(stack, number_or_url)
113
+ org = stack.repository.owner
114
+ case number_or_url
115
+ when /\A#?(\d+)\z/
116
+ $1.to_i
117
+ when %r{\Ahttps://#{Regexp.escape(Shipit.github(organization: org).domain)}/([^/]+)/([^/]+)/pull/(\d+)}
118
+ return unless $1.downcase == stack.repo_owner.downcase
119
+ return unless $2.downcase == stack.repo_name.downcase
120
+ $3.to_i
121
+ end
122
+ end
123
+
124
+ def self.request_merge!(stack, number, user)
125
+ now = Time.now.utc
126
+ merge_request = begin
127
+ create_with(
128
+ merge_requested_at: now,
129
+ merge_requested_by: user.presence,
130
+ ).find_or_create_by!(
131
+ stack: stack,
132
+ number: number,
133
+ )
134
+ rescue ActiveRecord::RecordNotUnique
135
+ retry
136
+ end
137
+ merge_request.update!(merge_requested_by: user.presence)
138
+ merge_request.retry! if merge_request.rejected? || merge_request.canceled? || merge_request.revalidating?
139
+ merge_request.schedule_refresh!
140
+ merge_request
141
+ end
142
+
143
+ def reject!(reason)
144
+ unless REJECTION_REASONS.include?(reason)
145
+ raise ArgumentError, "invalid reason: #{reason.inspect}, must be one of: #{REJECTION_REASONS.inspect}"
146
+ end
147
+ self.rejection_reason = reason.presence
148
+ super()
149
+ true
150
+ end
151
+
152
+ def reject_unless_mergeable!
153
+ return reject!('merge_conflict') if merge_conflict?
154
+ return reject!('ci_missing') if any_status_checks_missing?
155
+ return reject!('ci_failing') if any_status_checks_failed?
156
+ return reject!('requires_rebase') if stale?
157
+ false
158
+ end
159
+
160
+ def merge!
161
+ raise InvalidTransition unless pending?
162
+
163
+ raise NotReady if not_mergeable_yet?
164
+
165
+ stack.github_api.merge_pull_request(
166
+ stack.github_repo_name,
167
+ number,
168
+ merge_message,
169
+ sha: head.sha,
170
+ commit_message: 'Merged by Shipit',
171
+ merge_method: stack.merge_method,
172
+ )
173
+ begin
174
+ if stack.github_api.pull_requests(stack.github_repo_name, base: branch).empty?
175
+ stack.github_api.delete_branch(stack.github_repo_name, branch)
176
+ end
177
+ rescue Octokit::UnprocessableEntity
178
+ # branch was already deleted somehow
179
+ end
180
+ complete!
181
+ true
182
+ rescue Octokit::MethodNotAllowed # merge conflict
183
+ reject!('merge_conflict')
184
+ false
185
+ rescue Octokit::Conflict # shas didn't match, PR was updated.
186
+ raise NotReady
187
+ end
188
+
189
+ def all_status_checks_passed?
190
+ return false unless head
191
+ StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec).success?
192
+ end
193
+
194
+ def any_status_checks_failed?
195
+ status = StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec)
196
+ status.failure? || status.error?
197
+ end
198
+
199
+ def any_status_checks_missing?
200
+ StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec).missing?
201
+ end
202
+
203
+ def waiting?
204
+ WAITING_STATUSES.include?(merge_status)
205
+ end
206
+
207
+ def need_revalidation?
208
+ timeout = stack.cached_deploy_spec&.revalidate_merge_requests_after
209
+ return false unless timeout
210
+ (revalidated_at + timeout).past?
211
+ end
212
+
213
+ def merge_conflict?
214
+ mergeable == false
215
+ end
216
+
217
+ def not_mergeable_yet?
218
+ mergeable.nil?
219
+ end
220
+
221
+ def schedule_refresh!
222
+ RefreshMergeRequestJob.perform_later(self)
223
+ end
224
+
225
+ def closed?
226
+ state == "closed"
227
+ end
228
+
229
+ def merged_upstream?
230
+ closed? && merged_at
231
+ end
232
+
233
+ def refresh!
234
+ update!(github_pull_request: stack.github_api.pull_request(stack.github_repo_name, number))
235
+ head.refresh_statuses!
236
+ head.refresh_check_runs!
237
+ fetched! if fetching?
238
+ @comparison = nil
239
+ end
240
+
241
+ def github_pull_request=(github_pull_request)
242
+ self.github_id = github_pull_request.id
243
+ self.api_url = github_pull_request.url
244
+ self.title = github_pull_request.title
245
+ self.state = github_pull_request.state
246
+ self.mergeable = github_pull_request.mergeable
247
+ self.additions = github_pull_request.additions
248
+ self.deletions = github_pull_request.deletions
249
+ self.branch = github_pull_request.head.ref
250
+ self.head = find_or_create_commit_from_github_by_sha!(github_pull_request.head.sha, detached: true)
251
+ self.merged_at = github_pull_request.merged_at
252
+ self.base_ref = github_pull_request.base.ref
253
+ self.base_commit = find_or_create_commit_from_github_by_sha!(github_pull_request.base.sha, detached: true)
254
+ end
255
+
256
+ def merge_message
257
+ return title unless merge_requested_by
258
+ "#{title}\n\n#{MERGE_REQUEST_FIELD}: #{merge_requested_by.login}\n"
259
+ end
260
+
261
+ def stale?
262
+ return false unless base_commit
263
+ spec = stack.cached_deploy_spec
264
+ if max_branch_age = spec.max_divergence_age
265
+ return true if Time.now.utc - head.committed_at > max_branch_age
266
+ end
267
+ if commit_count_limit = spec.max_divergence_commits
268
+ return true if comparison.behind_by > commit_count_limit
269
+ end
270
+ false
271
+ end
272
+
273
+ def comparison
274
+ @comparison ||= stack.github_api.compare(
275
+ stack.github_repo_name,
276
+ base_ref,
277
+ head.sha,
278
+ )
279
+ end
280
+
281
+ private
282
+
283
+ def record_merge_status_change
284
+ @merge_status_changed ||= saved_change_to_attribute?(:merge_status)
285
+ end
286
+
287
+ def emit_hooks
288
+ return unless @merge_status_changed
289
+ @merge_status_changed = nil
290
+ Hook.emit('merge', stack, merge_request: self, status: merge_status, stack: stack)
291
+ end
292
+
293
+ def find_or_create_commit_from_github_by_sha!(sha, attributes)
294
+ if commit = stack.commits.by_sha(sha)
295
+ commit
296
+ else
297
+ github_commit = stack.github_api.commit(stack.github_repo_name, sha)
298
+ stack.commits.create_from_github!(github_commit, attributes)
299
+ end
300
+ rescue ActiveRecord::RecordNotUnique
301
+ retry
302
+ end
303
+ end
304
+ end