shipit-engine 0.32.0 → 0.35.1

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 (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