shipit-engine 0.32.0 → 0.33.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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/magic-solid.svg +1 -0
  3. data/app/assets/javascripts/shipit/repositories_search.js.coffee +60 -0
  4. data/app/assets/javascripts/shipit/{search.js.coffee → stack_search.js.coffee} +0 -0
  5. data/app/assets/stylesheets/_pages/_repositories.scss +148 -0
  6. data/app/assets/stylesheets/_pages/_stacks.scss +19 -0
  7. data/app/assets/stylesheets/shipit.scss +1 -0
  8. data/app/controllers/shipit/api/{pull_requests_controller.rb → merge_requests_controller.rb} +8 -8
  9. data/app/controllers/shipit/api/stacks_controller.rb +14 -1
  10. data/app/controllers/shipit/deploys_controller.rb +2 -2
  11. data/app/controllers/shipit/merge_requests_controller.rb +31 -0
  12. data/app/controllers/shipit/merge_status_controller.rb +15 -15
  13. data/app/controllers/shipit/repositories_controller.rb +74 -0
  14. data/app/controllers/shipit/tasks_controller.rb +4 -4
  15. data/app/helpers/shipit/chunks_helper.rb +2 -2
  16. data/app/helpers/shipit/github_url_helper.rb +8 -0
  17. data/app/helpers/shipit/stacks_helper.rb +4 -0
  18. data/app/jobs/shipit/create_on_github_job.rb +1 -0
  19. data/app/jobs/shipit/destroy_repository_job.rb +24 -0
  20. data/app/jobs/shipit/destroy_stack_job.rb +2 -2
  21. data/app/jobs/shipit/perform_task_job.rb +4 -98
  22. data/app/jobs/shipit/process_merge_requests_job.rb +32 -0
  23. data/app/jobs/shipit/refresh_merge_request_job.rb +11 -0
  24. data/app/models/shipit/anonymous_user.rb +4 -0
  25. data/app/models/shipit/check_run.rb +2 -2
  26. data/app/models/shipit/command_line_user.rb +4 -0
  27. data/app/models/shipit/commit.rb +11 -11
  28. data/app/models/shipit/commit_checks.rb +1 -0
  29. data/app/models/shipit/deploy.rb +1 -0
  30. data/app/models/shipit/deploy_spec.rb +16 -4
  31. data/app/models/shipit/deploy_spec/file_system.rb +11 -5
  32. data/app/models/shipit/hook.rb +2 -0
  33. data/app/models/shipit/merge_request.rb +302 -0
  34. data/app/models/shipit/provisioning_handler.rb +32 -0
  35. data/app/models/shipit/provisioning_handler/base.rb +30 -0
  36. data/app/models/shipit/provisioning_handler/unregistered_provisioning_handler.rb +35 -0
  37. data/app/models/shipit/pull_request.rb +25 -264
  38. data/app/models/shipit/pull_request_assignment.rb +10 -0
  39. data/app/models/shipit/repository.rb +54 -0
  40. data/app/models/shipit/review_stack.rb +116 -0
  41. data/app/models/shipit/review_stack_provisioning_queue.rb +39 -0
  42. data/app/models/shipit/stack.rb +22 -8
  43. data/app/models/shipit/task.rb +56 -7
  44. data/app/models/shipit/task_execution_strategy/base.rb +20 -0
  45. data/app/models/shipit/task_execution_strategy/default.rb +110 -0
  46. data/app/models/shipit/user.rb +6 -1
  47. data/app/models/shipit/webhooks.rb +10 -0
  48. data/app/models/shipit/webhooks/handlers/pull_request/assigned_handler.rb +74 -0
  49. data/app/models/shipit/webhooks/handlers/pull_request/closed_handler.rb +68 -0
  50. data/app/models/shipit/webhooks/handlers/pull_request/edited_handler.rb +74 -0
  51. data/app/models/shipit/webhooks/handlers/pull_request/label_capturing_handler.rb +127 -0
  52. data/app/models/shipit/webhooks/handlers/pull_request/labeled_handler.rb +106 -0
  53. data/app/models/shipit/webhooks/handlers/pull_request/opened_handler.rb +83 -0
  54. data/app/models/shipit/webhooks/handlers/pull_request/reopened_handler.rb +88 -0
  55. data/app/models/shipit/webhooks/handlers/pull_request/review_stack_adapter.rb +103 -0
  56. data/app/models/shipit/webhooks/handlers/pull_request/unlabeled_handler.rb +107 -0
  57. data/app/serializers/shipit/deploy_serializer.rb +6 -0
  58. data/app/serializers/shipit/merge_request_serializer.rb +21 -0
  59. data/app/serializers/shipit/pull_request_serializer.rb +5 -8
  60. data/app/serializers/shipit/review_stack_serializer.rb +7 -0
  61. data/app/serializers/shipit/stack_serializer.rb +7 -6
  62. data/app/serializers/shipit/tail_task_serializer.rb +10 -2
  63. data/app/serializers/shipit/task_serializer.rb +1 -1
  64. data/app/views/shipit/merge_requests/_merge_request.html.erb +29 -0
  65. data/app/views/shipit/{pull_requests → merge_requests}/index.html.erb +2 -2
  66. data/app/views/shipit/merge_requests/merge_requests/_pull_request.html.erb +29 -0
  67. data/app/views/shipit/merge_requests/merge_requests/index.html.erb +20 -0
  68. data/app/views/shipit/merge_status/_merge_queue_button.html.erb +3 -3
  69. data/app/views/shipit/merge_status/backlogged.html.erb +1 -1
  70. data/app/views/shipit/merge_status/failure.html.erb +1 -1
  71. data/app/views/shipit/merge_status/locked.html.erb +1 -1
  72. data/app/views/shipit/merge_status/success.html.erb +2 -2
  73. data/app/views/shipit/repositories/_header.html.erb +19 -0
  74. data/app/views/shipit/repositories/index.html.erb +31 -0
  75. data/app/views/shipit/repositories/new.html.erb +23 -0
  76. data/app/views/shipit/repositories/settings.html.erb +53 -0
  77. data/app/views/shipit/repositories/show.html.erb +30 -0
  78. data/app/views/shipit/stacks/_banners.html.erb +13 -0
  79. data/app/views/shipit/stacks/_header.html.erb +5 -2
  80. data/app/views/shipit/stacks/_stack.html.erb +8 -0
  81. data/app/views/shipit/stacks/index.html.erb +2 -1
  82. data/app/views/shipit/stacks/settings.html.erb +5 -5
  83. data/app/views/shipit/stacks/show.html.erb +1 -1
  84. data/app/views/shipit/tasks/_task_output.html.erb +1 -1
  85. data/config/routes.rb +15 -5
  86. data/db/migrate/20200706145406_add_review_stacks.rb +12 -0
  87. data/db/migrate/20200804144639_rename_pull_request_to_merge_request.rb +7 -0
  88. data/db/migrate/20200804161512_rename_commits_pull_request_id_to_merge_request_id.rb +5 -0
  89. data/db/migrate/20200813134712_recreate_shipit_pull_requests.rb +22 -0
  90. data/db/migrate/20200813194056_create_pull_request_assignments.rb +8 -0
  91. data/db/migrate/20201001125502_add_provision_pr_stacks_flag_to_repositories.rb +7 -0
  92. data/db/migrate/20201008145809_add_retry_attempt_to_tasks.rb +5 -0
  93. data/db/migrate/20201008152744_add_max_retries_to_tasks.rb +5 -0
  94. data/lib/shipit.rb +11 -1
  95. data/lib/shipit/github_app.rb +1 -1
  96. data/lib/shipit/review_stack_commands.rb +8 -0
  97. data/lib/shipit/stack_commands.rb +6 -1
  98. data/lib/shipit/task_commands.rb +1 -0
  99. data/lib/shipit/version.rb +1 -1
  100. data/lib/tasks/cron.rake +11 -2
  101. data/test/controllers/api/{pull_requests_controller_test.rb → merge_requests_controller_test.rb} +12 -12
  102. data/test/controllers/api/outputs_controller_test.rb +1 -0
  103. data/test/controllers/api/rollback_controller_test.rb +1 -1
  104. data/test/controllers/api/stacks_controller_test.rb +21 -1
  105. data/test/controllers/{pull_requests_controller_test.rb → merge_requests_controller_test.rb} +6 -6
  106. data/test/controllers/repositories_controller_test.rb +71 -0
  107. data/test/controllers/stacks_controller_test.rb +9 -1
  108. data/test/controllers/tasks_controller_test.rb +14 -2
  109. data/test/controllers/webhooks_controller_test.rb +1 -1
  110. data/test/dummy/config/application.rb +6 -1
  111. data/test/dummy/config/environments/development.rb +0 -3
  112. data/test/dummy/config/environments/test.rb +0 -5
  113. data/test/dummy/db/schema.rb +52 -14
  114. data/test/dummy/db/seeds.rb +1 -1
  115. data/test/fixtures/payloads/check_suite_master.json +2 -2
  116. data/test/fixtures/payloads/invalid_pull_request.json +117 -0
  117. data/test/fixtures/payloads/provision_disabled_pull_request.json +454 -0
  118. data/test/fixtures/payloads/pull_request_assigned.json +480 -0
  119. data/test/fixtures/payloads/pull_request_closed.json +454 -0
  120. data/test/fixtures/payloads/pull_request_labeled.json +461 -0
  121. data/test/fixtures/payloads/pull_request_opened.json +454 -0
  122. data/test/fixtures/payloads/pull_request_reopened.json +454 -0
  123. data/test/fixtures/payloads/pull_request_unlabeled.json +454 -0
  124. data/test/fixtures/payloads/pull_request_with_no_repo.json +454 -0
  125. data/test/fixtures/shipit/commits.yml +15 -2
  126. data/test/fixtures/shipit/merge_requests.yml +141 -0
  127. data/test/fixtures/shipit/pull_request_assignments.yml +3 -0
  128. data/test/fixtures/shipit/pull_requests.yml +10 -131
  129. data/test/fixtures/shipit/repositories.yml +1 -0
  130. data/test/fixtures/shipit/stacks.yml +145 -0
  131. data/test/fixtures/shipit/statuses.yml +9 -0
  132. data/test/fixtures/shipit/tasks.yml +3 -0
  133. data/test/fixtures/shipit/users.yml +7 -0
  134. data/test/helpers/payloads_helper.rb +4 -0
  135. data/test/jobs/chunk_rollup_job_test.rb +15 -1
  136. data/test/jobs/destroy_repository_job_test.rb +27 -0
  137. data/test/jobs/perform_task_job_test.rb +8 -8
  138. data/test/jobs/{merge_pull_requests_job_test.rb → process_merge_requests_job_test.rb} +18 -18
  139. data/test/lib/shipit/deploy_commands_test.rb +16 -0
  140. data/test/lib/shipit/task_commands_test.rb +17 -0
  141. data/test/models/commits_test.rb +22 -13
  142. data/test/models/deploy_spec_test.rb +57 -24
  143. data/test/models/deploys_test.rb +148 -14
  144. data/test/models/{pull_request_test.rb → merge_request_test.rb} +30 -30
  145. data/test/models/pull_request_assignment_test.rb +16 -0
  146. data/test/models/shipit/provisioning_handler/base_test.rb +33 -0
  147. data/test/models/shipit/provisioning_handler/unregistered_provisioning_handler_test.rb +49 -0
  148. data/test/models/shipit/provisioning_handler_test.rb +64 -0
  149. data/test/models/shipit/pull_request_test.rb +52 -0
  150. data/test/models/shipit/repository_test.rb +5 -1
  151. data/test/models/shipit/review_stack_provision_status_test.rb +77 -0
  152. data/test/models/shipit/review_stack_provisioning_queue_test.rb +63 -0
  153. data/test/models/shipit/review_stack_test.rb +59 -0
  154. data/test/models/{stacks_test.rb → shipit/stacks_test.rb} +10 -4
  155. data/test/models/shipit/webhooks/handlers/pull_request/assigned_handler_test.rb +45 -0
  156. data/test/models/shipit/webhooks/handlers/pull_request/closed_handler_test.rb +192 -0
  157. data/test/models/shipit/webhooks/handlers/pull_request/edited_handler_test.rb +47 -0
  158. data/test/models/shipit/webhooks/handlers/pull_request/label_capturing_handler_test.rb +209 -0
  159. data/test/models/shipit/webhooks/handlers/pull_request/labeled_handler_test.rb +332 -0
  160. data/test/models/shipit/webhooks/handlers/pull_request/opened_handler_test.rb +238 -0
  161. data/test/models/shipit/webhooks/handlers/pull_request/reopened_handler_test.rb +282 -0
  162. data/test/models/shipit/webhooks/handlers/pull_request/review_stack_adapter_test.rb +83 -0
  163. data/test/models/shipit/webhooks/handlers/pull_request/unlabeled_handler_test.rb +324 -0
  164. data/test/models/shipit/{wehbooks → webhooks}/handlers_test.rb +0 -0
  165. data/test/models/tasks_test.rb +44 -3
  166. data/test/serializers/shipit/pull_request_serializer_test.rb +29 -0
  167. data/test/unit/command_test.rb +3 -3
  168. data/test/unit/github_url_helper_test.rb +5 -0
  169. data/test/unit/shipit_task_execution_strategy_test.rb +47 -0
  170. metadata +260 -154
  171. data/app/controllers/shipit/pull_requests_controller.rb +0 -31
  172. data/app/jobs/shipit/merge_pull_requests_job.rb +0 -32
  173. data/app/jobs/shipit/refresh_pull_request_job.rb +0 -11
  174. data/app/views/shipit/pull_requests/_pull_request.html.erb +0 -29
  175. data/test/fixtures/shipit/output_chunks.yml +0 -47
  176. data/test/models/output_chunk_test.rb +0 -21
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shipit
4
+ class ReviewStack < Shipit::Stack
5
+ def self.clear_stale_caches
6
+ Shipit::ReviewStack.where(
7
+ "archived_since > :earliest AND archived_since < :latest",
8
+ earliest: 1.day.ago,
9
+ latest: 1.hour.ago
10
+ ).each do |review_stack|
11
+ Shipit::ClearGitCacheJob.perform_later(review_stack)
12
+ end
13
+ end
14
+
15
+ def self.delete_old_deployment_directories
16
+ Shipit::Deploy.not_active.where(
17
+ "created_at > :earliest AND updated_at < :latest",
18
+ earliest: 1.day.ago,
19
+ latest: 1.hour.ago
20
+ ).find_each do |deploy|
21
+ Shipit::Commands.for(deploy).clear_working_directory
22
+ end
23
+ end
24
+
25
+ has_one :pull_request, foreign_key: :stack_id
26
+
27
+ after_commit :emit_added_hooks, on: :create
28
+ after_commit :emit_updated_hooks, on: :update
29
+ after_commit :emit_removed_hooks, on: :destroy
30
+
31
+ state_machine :provision_status, initial: :deprovisioned do
32
+ state :provisioned
33
+ state :provisioning
34
+ state :deprovisioning
35
+ state :deprovisioned
36
+
37
+ event :provision do
38
+ transition deprovisioned: :provisioning
39
+ end
40
+
41
+ event :provision_success do
42
+ transition provisioning: :provisioned
43
+ end
44
+
45
+ event :provision_failure do
46
+ transition provisioning: :deprovisioned
47
+ end
48
+
49
+ event :deprovision do
50
+ transition provisioned: :deprovisioning
51
+ end
52
+
53
+ event :deprovision_success do
54
+ transition deprovisioning: :deprovisioned
55
+ end
56
+
57
+ event :deprovision_failure do
58
+ transition deprovisioning: :provisioned
59
+ end
60
+
61
+ after_transition deprovisioned: :provisioning do |stack, _|
62
+ stack.provisioner.up
63
+ end
64
+
65
+ after_transition provisioned: :deprovisioning do |stack, _|
66
+ stack.provisioner.down
67
+ end
68
+ end
69
+
70
+ def env
71
+ return super unless pull_request.present?
72
+
73
+ super
74
+ .merge(
75
+ pull_request
76
+ .labels
77
+ .each_with_object({}) { |label_name, labels| labels[label_name.upcase] = "true" }
78
+ )
79
+ end
80
+
81
+ def provisioner
82
+ provisioner_class.new(self)
83
+ end
84
+
85
+ def provisioner_class
86
+ ProvisioningHandler.fetch(provisioning_handler_name)
87
+ end
88
+
89
+ def enqueue_for_provisioning
90
+ return if awaiting_provision
91
+ update!(awaiting_provision: true)
92
+ end
93
+
94
+ def remove_from_provisioning_queue
95
+ return unless awaiting_provision
96
+ update!(awaiting_provision: false)
97
+ end
98
+
99
+ def to_partial_path
100
+ "shipit/stacks/stack"
101
+ end
102
+
103
+ def emit_added_hooks
104
+ Hook.emit(:review_stack, self, action: :added, review_stack: self)
105
+ end
106
+
107
+ def emit_updated_hooks
108
+ changed = !(previous_changes.keys - %w(updated_at)).empty?
109
+ Hook.emit(:review_stack, self, action: :updated, review_stack: self) if changed
110
+ end
111
+
112
+ def emit_removed_hooks
113
+ Hook.emit(:review_stack, self, action: :removed, review_stack: self)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shipit
4
+ class ReviewStackProvisioningQueue
5
+ def self.work
6
+ new.work
7
+ end
8
+
9
+ def self.add(stack)
10
+ stack.enqueue_for_provisioning
11
+ end
12
+
13
+ def self.queued_stacks
14
+ new.queued_stacks
15
+ end
16
+
17
+ def work
18
+ queued_stacks.find_each(&method(:provision))
19
+ end
20
+
21
+ def queued_stacks
22
+ @queued_stacks ||= Shipit::ReviewStack
23
+ .with_provision_status(:deprovisioned)
24
+ .where(awaiting_provision: true)
25
+ end
26
+
27
+ private
28
+
29
+ def provision(stack)
30
+ if stack.provisioner.provision?
31
+ stack.provision
32
+ else
33
+ Rails.logger.info(
34
+ "Putting review ReviewStack<#{stack.id}> back into the provisioning queue - #provision? was falsey."
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -27,7 +27,7 @@ module Shipit
27
27
  REQUIRED_HOOKS = %i(push status).freeze
28
28
 
29
29
  has_many :commits, dependent: :destroy
30
- has_many :pull_requests, dependent: :destroy
30
+ has_many :merge_requests, dependent: :destroy
31
31
  has_many :tasks, dependent: :destroy
32
32
  has_many :deploys
33
33
  has_many :rollbacks
@@ -44,6 +44,9 @@ module Shipit
44
44
 
45
45
  scope :not_archived, -> { where(archived_since: nil) }
46
46
 
47
+ include DeferredTouch
48
+ deferred_touch repository: :updated_at
49
+
47
50
  default_scope { preload(:repository) }
48
51
 
49
52
  def env
@@ -91,8 +94,17 @@ module Shipit
91
94
  validates :lock_reason, length: { maximum: 4096 }
92
95
 
93
96
  serialize :cached_deploy_spec, DeploySpec
94
- delegate :find_task_definition, :supports_rollback?, :release_status?, :release_status_delay,
95
- :release_status_context, :supports_fetch_deployed_revision?, to: :cached_deploy_spec, allow_nil: true
97
+ delegate(
98
+ :provisioning_handler_name,
99
+ :find_task_definition,
100
+ :release_status?,
101
+ :release_status_context,
102
+ :release_status_delay,
103
+ :supports_fetch_deployed_revision?,
104
+ :supports_rollback?,
105
+ to: :cached_deploy_spec,
106
+ allow_nil: true
107
+ )
96
108
 
97
109
  def self.refresh_deployed_revisions
98
110
  find_each.select(&:supports_fetch_deployed_revision?).each(&:async_refresh_deployed_revision)
@@ -139,6 +151,7 @@ module Shipit
139
151
  env: filter_deploy_envs(env&.to_h || {}),
140
152
  allow_concurrency: force,
141
153
  ignored_safeties: force || !until_commit.deployable?,
154
+ max_retries: retries_on_deploy,
142
155
  )
143
156
  end
144
157
 
@@ -200,7 +213,7 @@ module Shipit
200
213
  end
201
214
 
202
215
  def schedule_merges
203
- MergePullRequestsJob.perform_later(self)
216
+ ProcessMergeRequestsJob.perform_later(self)
204
217
  end
205
218
 
206
219
  def next_commit_to_deploy
@@ -340,7 +353,7 @@ module Shipit
340
353
  end
341
354
 
342
355
  def deployable?
343
- !locked? && !active_task?
356
+ !locked? && !active_task? && !awaiting_provision?
344
357
  end
345
358
 
346
359
  def allows_merges?
@@ -348,7 +361,7 @@ module Shipit
348
361
  end
349
362
 
350
363
  def merge_method
351
- cached_deploy_spec&.pull_request_merge_method || Shipit.default_merge_method
364
+ cached_deploy_spec&.merge_request_merge_method || Shipit.default_merge_method
352
365
  end
353
366
 
354
367
  delegate :name=, to: :repository, prefix: :repo
@@ -384,7 +397,7 @@ module Shipit
384
397
  end
385
398
 
386
399
  def github_repo_name
387
- [repo_owner, repo_name].join('/')
400
+ repository.github_repo_name
388
401
  end
389
402
 
390
403
  def github_commits
@@ -475,7 +488,8 @@ module Shipit
475
488
 
476
489
  delegate :plugins, :task_definitions, :hidden_statuses, :required_statuses, :soft_failing_statuses,
477
490
  :blocking_statuses, :deploy_variables, :filter_task_envs, :filter_deploy_envs,
478
- :maximum_commits_per_deploy, :pause_between_deploys, to: :cached_deploy_spec
491
+ :maximum_commits_per_deploy, :pause_between_deploys, :retries_on_deploy, :retries_on_rollback,
492
+ to: :cached_deploy_spec
479
493
 
480
494
  def monitoring?
481
495
  monitoring.present?
@@ -27,8 +27,6 @@ module Shipit
27
27
 
28
28
  deferred_touch stack: :updated_at
29
29
 
30
- has_many :chunks, -> { order(:id) }, class_name: 'OutputChunk', dependent: :delete_all, inverse_of: :task
31
-
32
30
  serialize :definition, TaskDefinition
33
31
  serialize :env, Hash
34
32
 
@@ -79,6 +77,10 @@ module Shipit
79
77
  task.async_refresh_deployed_revision
80
78
  end
81
79
 
80
+ after_transition any => %i(aborted success failed error timedout) do |task|
81
+ task.schedule_rollup_chunks
82
+ end
83
+
82
84
  after_transition any => :flapping do |task|
83
85
  task.update!(confirmations: 0)
84
86
  end
@@ -87,6 +89,10 @@ module Shipit
87
89
  task.async_update_estimated_deploy_duration
88
90
  end
89
91
 
92
+ after_transition any => %i(failed error timedout) do |task|
93
+ task.retry_if_necessary
94
+ end
95
+
90
96
  event :run do
91
97
  transition pending: :running
92
98
  end
@@ -197,16 +203,16 @@ module Shipit
197
203
 
198
204
  def write(text)
199
205
  log_output(text)
200
- chunks.create!(text: text)
206
+ Shipit.redis.append(output_key, text)
201
207
  end
202
208
 
203
209
  def chunk_output
204
210
  if rolled_up?
205
211
  output
206
212
  else
207
- blob = chunks.pluck(:text).join
213
+ blob = Shipit.redis.get(output_key)
208
214
 
209
- if blob.size > OUTPUT_SIZE_LIMIT
215
+ if blob && blob.size > OUTPUT_SIZE_LIMIT
210
216
  Rails.logger.warn("Task #{id} output exceeds limit of #{HUMAN_READABLE_OUTPUT_LIMIT}, and will be truncated.")
211
217
  blob = blob.last(OUTPUT_SIZE_LIMIT - OUTPUT_TRUNCATED_MESSAGE.size)
212
218
  blob = OUTPUT_TRUNCATED_MESSAGE + blob
@@ -216,15 +222,30 @@ module Shipit
216
222
  end
217
223
  end
218
224
 
225
+ def chunk_output_size
226
+ return 0 if rolled_up?
227
+
228
+ Shipit.redis.strlen(output_key)
229
+ end
230
+
231
+ def tail_output(range_start)
232
+ Shipit.redis.getrange(output_key, range_start || 0, -1)
233
+ end
234
+
219
235
  def schedule_rollup_chunks
220
236
  ChunkRollupJob.perform_later(self)
221
237
  end
222
238
 
223
239
  def rollup_chunks
224
240
  ActiveRecord::Base.transaction do
225
- self.output = chunk_output
226
- chunks.delete_all
241
+ chunks = Shipit::OutputChunk.where(task: self).pluck(:text)
242
+ chunks << chunk_output
243
+ self.output = chunks.join("\n")
244
+
227
245
  update_attribute(:rolled_up, true)
246
+
247
+ Shipit.redis.del(output_key)
248
+ Shipit::OutputChunk.where(task: self).delete_all
228
249
  end
229
250
  end
230
251
 
@@ -382,12 +403,32 @@ module Shipit
382
403
  .reject(&:alive?)
383
404
  end
384
405
 
406
+ def retry_if_necessary
407
+ return unless retries_configured? && !stack.reload.locked?
408
+
409
+ if retry_attempt < max_retries
410
+ retry_task = duplicate_task
411
+ retry_task.retry_attempt = duplicate_task.retry_attempt + 1
412
+ retry_task.save!
413
+
414
+ retry_task.enqueue
415
+ end
416
+ end
417
+
418
+ def retries_configured?
419
+ !max_retries.nil? && max_retries > 0
420
+ end
421
+
385
422
  private
386
423
 
387
424
  def prevent_concurrency
388
425
  raise ConcurrentTaskRunning if stack.tasks.active.exclusive.count > 1
389
426
  end
390
427
 
428
+ def output_key
429
+ "#{status_key}:output"
430
+ end
431
+
391
432
  def status_key
392
433
  "shipit:task:#{id}"
393
434
  end
@@ -405,5 +446,13 @@ module Shipit
405
446
  def output_line_buffer
406
447
  @output_line_buffer ||= LineBuffer.new
407
448
  end
449
+
450
+ def duplicate_task
451
+ copy_task = dup
452
+ copy_task.status = 'pending'
453
+ copy_task.started_at = nil
454
+ copy_task.ended_at = nil
455
+ copy_task
456
+ end
408
457
  end
409
458
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shipit
4
+ module TaskExecutionStrategy
5
+ class Base
6
+ def initialize(task)
7
+ self.task = task
8
+ end
9
+
10
+ def execute
11
+ raise(
12
+ NotImplmentedError,
13
+ "subclasses of TaskExectuionStrategy::Base must implement the #execute method"
14
+ )
15
+ end
16
+
17
+ attr_accessor :task
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shipit
4
+ module TaskExecutionStrategy
5
+ class Default < Base
6
+ def execute
7
+ @commands = Commands.for(@task)
8
+ unless @task.pending?
9
+ Rails.logger.error("Task ##{@task.id} already in `#{@task.status}` state. Aborting.")
10
+ return
11
+ end
12
+ run
13
+ ensure
14
+ @commands.clear_working_directory
15
+ end
16
+
17
+ def run
18
+ @task.ping
19
+ @task.run!
20
+ checkout_repository
21
+ perform_task
22
+ @task.write("\nCompleted successfully\n")
23
+ @task.report_complete!
24
+ rescue Command::TimedOut => error
25
+ @task.write("\n#{error.message}\n")
26
+ @task.report_timeout!(error)
27
+ rescue Command::Error => error
28
+ @task.write("\n#{error.message}\n")
29
+ @task.report_failure!(error)
30
+ rescue StandardError => error
31
+ @task.report_error!(error)
32
+ rescue Exception => error
33
+ @task.report_error!(error)
34
+ raise
35
+ end
36
+
37
+ def abort!(signal: 'TERM')
38
+ pid = @task.pid
39
+ if pid
40
+ @task.write("$ kill #{pid}\n")
41
+ Process.kill(signal, pid)
42
+ else
43
+ @task.write("Can't abort, no recorded pid, WTF?\n")
44
+ end
45
+ rescue SystemCallError => error
46
+ @task.write("kill: (#{pid}) - #{error.message}\n")
47
+ end
48
+
49
+ def check_for_abort
50
+ @task.should_abort? do |times_killed|
51
+ if times_killed > 3
52
+ abort!(signal: 'KILL')
53
+ else
54
+ abort!
55
+ end
56
+ end
57
+ end
58
+
59
+ def perform_task
60
+ capture_all!(@commands.install_dependencies)
61
+ capture_all!(@commands.perform)
62
+ end
63
+
64
+ def checkout_repository
65
+ unless @commands.fetched?(@task.until_commit).tap(&:run).success?
66
+ # acquire_git_cache_lock can take upto 15 seconds
67
+ # to process. Try to make sure that the job isn't
68
+ # marked dead while we attempt to acquire the lock.
69
+ @task.ping
70
+ @task.acquire_git_cache_lock do
71
+ @task.ping
72
+ unless @commands.fetched?(@task.until_commit).tap(&:run).success?
73
+ capture!(@commands.fetch)
74
+ end
75
+ end
76
+ end
77
+ capture_all!(@commands.clone)
78
+ capture!(@commands.checkout(@task.until_commit))
79
+ end
80
+
81
+ def capture_all!(commands)
82
+ commands.map { |c| capture!(c) }
83
+ end
84
+
85
+ def capture!(command)
86
+ started_at = Time.now
87
+ command.start do
88
+ @task.ping
89
+ check_for_abort
90
+ end
91
+ @task.write("$ #{command}\npid: #{command.pid}\nstarted at: #{started_at}\n")
92
+ @task.pid = command.pid
93
+ command.stream! do |line|
94
+ @task.write(line)
95
+ end
96
+ @task.write("\n")
97
+ finished_at = Time.now
98
+ @task.write("pid: #{command.pid}\nfinished at: #{finished_at}\nran in: #{finished_at - started_at} seconds\n")
99
+ command.success?
100
+ end
101
+
102
+ def capture(command)
103
+ capture!(command)
104
+ command.success?
105
+ rescue Command::Error
106
+ false
107
+ end
108
+ end
109
+ end
110
+ end