shipit-engine 0.27.1 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -1
  3. data/app/assets/stylesheets/_pages/_commits.scss +2 -0
  4. data/app/assets/stylesheets/_pages/_deploy.scss +6 -0
  5. data/app/controllers/shipit/api/release_statuses_controller.rb +22 -0
  6. data/app/controllers/shipit/api/stacks_controller.rb +6 -1
  7. data/app/controllers/shipit/commits_controller.rb +12 -1
  8. data/app/controllers/shipit/deploys_controller.rb +11 -0
  9. data/app/controllers/shipit/stacks_controller.rb +29 -1
  10. data/app/controllers/shipit/tasks_controller.rb +13 -1
  11. data/app/helpers/shipit/merge_status_helper.rb +2 -2
  12. data/app/helpers/shipit/stacks_helper.rb +10 -4
  13. data/app/jobs/shipit/perform_task_job.rb +1 -0
  14. data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +41 -0
  15. data/app/models/shipit/command_line_user.rb +58 -0
  16. data/app/models/shipit/commit.rb +42 -2
  17. data/app/models/shipit/deploy.rb +31 -2
  18. data/app/models/shipit/deploy_spec/lerna_discovery.rb +43 -19
  19. data/app/models/shipit/deploy_spec/pypi_discovery.rb +5 -1
  20. data/app/models/shipit/rollback.rb +4 -2
  21. data/app/models/shipit/stack.rb +72 -15
  22. data/app/models/shipit/task.rb +30 -0
  23. data/app/models/shipit/undeployed_commit.rb +10 -1
  24. data/app/serializers/shipit/command_line_user_serializer.rb +4 -0
  25. data/app/views/layouts/shipit.html.erb +5 -1
  26. data/app/views/shipit/commits/_commit.html.erb +7 -2
  27. data/app/views/shipit/merge_status/_commit_count_warning.html.erb +1 -5
  28. data/app/views/shipit/merge_status/backlogged.html.erb +1 -1
  29. data/app/views/shipit/merge_status/failure.html.erb +1 -1
  30. data/app/views/shipit/merge_status/locked.html.erb +1 -1
  31. data/app/views/shipit/merge_status/success.html.erb +1 -1
  32. data/app/views/shipit/stacks/show.html.erb +10 -1
  33. data/app/views/shipit/tasks/_task_output.html.erb +2 -2
  34. data/config/locales/en.yml +2 -1
  35. data/config/routes.rb +8 -1
  36. data/db/migrate/20190502020249_add_lock_author_id_to_commits.rb +5 -0
  37. data/lib/shipit.rb +14 -2
  38. data/lib/shipit/cast_value.rb +9 -0
  39. data/lib/shipit/command.rb +62 -16
  40. data/lib/shipit/line_buffer.rb +42 -0
  41. data/lib/shipit/version.rb +1 -1
  42. data/lib/tasks/shipit.rake +27 -0
  43. data/test/controllers/api/release_statuses_controller_test.rb +66 -0
  44. data/test/controllers/api/stacks_controller_test.rb +19 -0
  45. data/test/controllers/commits_controller_test.rb +30 -6
  46. data/test/controllers/deploys_controller_test.rb +51 -2
  47. data/test/controllers/tasks_controller_test.rb +24 -0
  48. data/test/dummy/db/schema.rb +2 -1
  49. data/test/dummy/db/seeds.rb +2 -0
  50. data/test/fixtures/shipit/check_runs.yml +11 -0
  51. data/test/fixtures/shipit/commits.yml +104 -0
  52. data/test/fixtures/shipit/stacks.yml +98 -3
  53. data/test/fixtures/shipit/tasks.yml +42 -0
  54. data/test/jobs/update_github_last_deployed_ref_job_test.rb +88 -0
  55. data/test/models/commits_test.rb +88 -1
  56. data/test/models/deploy_spec_test.rb +34 -6
  57. data/test/models/deploys_test.rb +308 -6
  58. data/test/models/rollbacks_test.rb +17 -11
  59. data/test/models/stacks_test.rb +217 -4
  60. data/test/models/tasks_test.rb +13 -0
  61. data/test/models/undeployed_commits_test.rb +62 -3
  62. data/test/test_helper.rb +0 -1
  63. data/test/unit/command_test.rb +55 -0
  64. data/test/unit/line_buffer_test.rb +20 -0
  65. metadata +142 -128
@@ -107,6 +107,25 @@ module Shipit
107
107
  assert_response :ok
108
108
  assert_json 'last_deployed_at', @stack.last_deployed_at
109
109
  end
110
+
111
+ test "#destroy schedules stack deletion job" do
112
+ assert_enqueued_with(job: DestroyStackJob) do
113
+ delete :destroy, params: {id: @stack.to_param}
114
+ end
115
+ assert_response :accepted
116
+ end
117
+
118
+ test "#destroy fails with insufficient permissions" do
119
+ @client.permissions.delete('write:stack')
120
+ @client.save!
121
+
122
+ assert_no_difference 'Stack.count' do
123
+ delete :destroy, params: {id: @stack.to_param}
124
+ end
125
+
126
+ assert_response :forbidden
127
+ assert_json 'message', 'This operation requires the `write:stack` permission'
128
+ end
110
129
  end
111
130
  end
112
131
  end
@@ -5,14 +5,38 @@ module Shipit
5
5
  setup do
6
6
  @stack = shipit_stacks(:shipit)
7
7
  @commit = shipit_commits(:first)
8
- session[:user_id] = shipit_users(:walrus).id
8
+ @user = shipit_users(:walrus)
9
+ session[:user_id] = @user.id
9
10
  end
10
11
 
11
- test "#update allows to lock a commit" do
12
- refute_predicate @commit, :locked?
13
- patch :update, params: {stack_id: @stack.to_param, id: @commit.id, commit: {locked: true}}
14
- assert_response :ok
15
- assert_predicate @commit.reload, :locked?
12
+ test "#update allows commits to be locked and sets the lock author" do
13
+ refute_predicate(@commit, :locked?)
14
+
15
+ patch(:update, params: {
16
+ stack_id: @stack.to_param,
17
+ id: @commit.id,
18
+ commit: {locked: true},
19
+ })
20
+
21
+ assert_response(:ok)
22
+ @commit.reload
23
+ assert_predicate(@commit, :locked?)
24
+ assert_equal(@user, @commit.lock_author)
25
+ end
26
+
27
+ test "#update allows commits to be unlocked and clears the lock author" do
28
+ @commit.lock(@user)
29
+
30
+ patch(:update, params: {
31
+ stack_id: @stack.to_param,
32
+ id: @commit.id,
33
+ commit: {locked: false},
34
+ })
35
+
36
+ assert_response(:ok)
37
+ @commit.reload
38
+ refute_predicate(@commit, :locked?)
39
+ assert_nil(@commit.lock_author_id)
16
40
  end
17
41
  end
18
42
  end
@@ -7,11 +7,12 @@ module Shipit
7
7
  @stack = shipit_stacks(:shipit)
8
8
  @deploy = shipit_deploys(:shipit)
9
9
  @commit = shipit_commits(:second)
10
- session[:user_id] = shipit_users(:walrus).id
10
+ @user = shipit_users(:walrus)
11
+ session[:user_id] = @user.id
11
12
  end
12
13
 
13
14
  test ":show is success" do
14
- get :show, params: {stack_id: @stack.to_param, id: @deploy.id}
15
+ get :show, params: {stack_id: @stack.to_param, id: @stack.deploys.last.id}
15
16
  assert_response :success
16
17
  end
17
18
 
@@ -100,6 +101,54 @@ module Shipit
100
101
  assert_select '#new_rollback #force', 1
101
102
  end
102
103
 
104
+ test ":rollback button shows deploy and commit ids" do
105
+ previous_deploy = @stack.deploys.second_to_last
106
+ previous_deploy.status = "success"
107
+ previous_deploy.type = "Shipit::Deploy"
108
+ previous_deploy.since_commit_id = 1
109
+ previous_deploy.until_commit_id = 2
110
+ previous_deploy.save
111
+
112
+ latest_deploy = @stack.deploys.last
113
+ latest_deploy.status = "running"
114
+ latest_deploy.type = "Shipit::Deploy"
115
+ latest_deploy.since_commit_id = 3
116
+ latest_deploy.until_commit_id = 4
117
+ latest_deploy.save
118
+
119
+ rollback_commit = @stack.commits.where(id: 2).first
120
+
121
+ get :show, params: {stack_id: @stack, id: latest_deploy.id, format: 'html'}
122
+
123
+ expected_result = "Abort and Rollback to <span class=\"short-sha-no-bg\">#{rollback_commit.short_sha}</span>"
124
+ expected_rolling_back_element = "Aborting with Rollback... to <span class=\"short-sha-no-bg\">#{rollback_commit.short_sha}</span>"
125
+
126
+ assert_select 'span.caption--ready', {html: expected_result}, "rollback button element was not found, or did not match the expected result of '#{expected_result}'"
127
+ assert_select 'span.caption--pending', {html: expected_rolling_back_element}, "ready rollback button element was not found, or did not match the expected result of '#{expected_rolling_back_element}'"
128
+ end
129
+
130
+ test ":rollback (regression) works correctly when a previous deploy is not found" do
131
+ rollback_commit_id = 3
132
+ latest_deploy = @stack.deploys.last
133
+ latest_deploy.status = "running"
134
+ latest_deploy.type = "Shipit::Deploy"
135
+ latest_deploy.since_commit_id = rollback_commit_id
136
+ latest_deploy.until_commit_id = 4
137
+ latest_deploy.save
138
+
139
+ @stack.deploys.where.not(id: latest_deploy.id).delete_all
140
+
141
+ rollback_commit = @stack.commits.where(id: rollback_commit_id).take
142
+
143
+ get :show, params: {stack_id: @stack, id: latest_deploy.id, format: 'html'}
144
+
145
+ expected_result = "Abort and Rollback to <span class=\"short-sha-no-bg\">#{rollback_commit.short_sha}</span>"
146
+ expected_rolling_back_element = "Aborting with Rollback... to <span class=\"short-sha-no-bg\">#{rollback_commit.short_sha}</span>"
147
+
148
+ assert_select 'span.caption--ready', {html: expected_result}, "rollback button element was not found, or did not match the expected result of '#{expected_result}'"
149
+ assert_select 'span.caption--pending', {html: expected_rolling_back_element}, "ready rollback button element was not found, or did not match the expected result of '#{expected_rolling_back_element}'"
150
+ end
151
+
103
152
  test ":revert redirect to the proper rollback page" do
104
153
  get :revert, params: {stack_id: @stack.to_param, id: shipit_deploys(:shipit2).id}
105
154
  assert_redirected_to rollback_stack_deploy_path(@stack, shipit_deploys(:shipit))
@@ -123,5 +123,29 @@ module Shipit
123
123
  assert_response :success
124
124
  assert_json_keys %w(status output rollback_url)
125
125
  end
126
+
127
+ test ":lookup returns stack task url if the task is an instance of Task" do
128
+ @task = shipit_tasks(:shipit_restart)
129
+
130
+ get :lookup, params: {id: @task.id}
131
+
132
+ assert_redirected_to stack_task_path(@task.stack, @task)
133
+ end
134
+
135
+ test ":lookup returns stack deploy url if the task is an instance of Deploy" do
136
+ @task = shipit_tasks(:shipit)
137
+
138
+ get :lookup, params: {id: @task.id}
139
+
140
+ assert_redirected_to stack_deploy_path(@task.stack, @task)
141
+ end
142
+
143
+ test ":lookup returns stack deploy url if the task is an instance of Rollback" do
144
+ @task = shipit_tasks(:shipit_rollback)
145
+
146
+ get :lookup, params: {id: @task.id}
147
+
148
+ assert_redirected_to stack_deploy_path(@task.stack, @task)
149
+ end
126
150
  end
127
151
  end
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 2018_10_10_150947) do
13
+ ActiveRecord::Schema.define(version: 2019_05_02_020249) do
14
14
 
15
15
  create_table "api_clients", force: :cascade do |t|
16
16
  t.text "permissions", limit: 65535
@@ -76,6 +76,7 @@ ActiveRecord::Schema.define(version: 2018_10_10_150947) do
76
76
  t.string "pull_request_title", limit: 1024
77
77
  t.integer "pull_request_id"
78
78
  t.boolean "locked", default: false, null: false
79
+ t.integer "lock_author_id", limit: 4
79
80
  t.index ["author_id"], name: "index_commits_on_author_id"
80
81
  t.index ["committer_id"], name: "index_commits_on_committer_id"
81
82
  t.index ["created_at"], name: "index_commits_on_created_at"
@@ -9,6 +9,8 @@ module Shipit
9
9
  Stack.send(:define_method, :sync_github) {}
10
10
  Commit.send(:define_method, :fetch_stats!) {}
11
11
  Commit.send(:define_method, :refresh_statuses!) {}
12
+ Commit.send(:define_method, :refresh_check_runs!) {}
13
+ ReleaseStatus.send(:define_method, :create_status_on_github!) {}
12
14
 
13
15
  users = 3.times.map do
14
16
  User.create!(
@@ -9,6 +9,17 @@ second_pending_travis:
9
9
  details_url: "http://www.example.com/build/424242"
10
10
  created_at: <%= 10.days.ago.to_s(:db) %>
11
11
 
12
+ check_runs_first_pending_coveralls:
13
+ stack: check_runs
14
+ commit_id: 201 # check_runs_first
15
+ github_id: 43
16
+ title: lets go
17
+ name: Coverage metrics
18
+ created_at: <%= 10.days.ago.to_s(:db) %>
19
+ conclusion: pending
20
+ html_url: "http://www.example.com/run/434343"
21
+ details_url: "http://www.example.com/build/434343"
22
+
12
23
  check_runs_first_success_coveralls:
13
24
  stack: check_runs
14
25
  commit_id: 201 # check_runs_first
@@ -246,3 +246,107 @@ canaries_fifth:
246
246
  additions: 1
247
247
  deletions: 24
248
248
  updated_at: <%= 8.days.ago.to_s(:db) %>
249
+
250
+ undeployed_1:
251
+ id: 401
252
+ sha: 4d9278037b872fd9a6690523e411ecb3aa181355
253
+ message: "lets go"
254
+ stack: shipit_undeployed
255
+ author: walrus
256
+ committer: walrus
257
+ authored_at: <%= 10.days.ago.to_s(:db) %>
258
+ committed_at: <%= 9.days.ago.to_s(:db) %>
259
+ additions: 42
260
+ deletions: 24
261
+ updated_at: <%= 8.days.ago.to_s(:db) %>
262
+
263
+ undeployed_2:
264
+ id: 402
265
+ sha: 4890fd8b5f2be05d1fedb763a3605ee461c39074
266
+ message: "sheep it!"
267
+ stack: shipit_undeployed
268
+ author: walrus
269
+ committer: walrus
270
+ authored_at: <%= 8.days.ago.to_s(:db) %>
271
+ committed_at: <%= 7.days.ago.to_s(:db) %>
272
+ additions: 1
273
+ deletions: 1
274
+ updated_at: <%= 8.days.ago.to_s(:db) %>
275
+
276
+ undeployed_3:
277
+ id: 403
278
+ sha: 467578b362bf2b4df5903e1c7960929361c39074
279
+ message: "fix it!"
280
+ stack: shipit_undeployed
281
+ author: walrus
282
+ committer: walrus
283
+ authored_at: <%= 6.days.ago.to_s(:db) %>
284
+ committed_at: <%= 5.days.ago.to_s(:db) %>
285
+ additions: 12
286
+ deletions: 64
287
+ updated_at: <%= 8.days.ago.to_s(:db) %>
288
+
289
+ undeployed_4:
290
+ id: 404
291
+ sha: 447578b362bf2b4df5903e1c7960929361c3435a
292
+ message: "Merge pull request #7 from shipit-engine/yoloshipit\n\nyoloshipit!"
293
+ stack: shipit_undeployed
294
+ author: walrus
295
+ committer: walrus
296
+ authored_at: <%= 4.days.ago.to_s(:db) %>
297
+ committed_at: <%= 3.days.ago.to_s(:db) %>
298
+ additions: 420
299
+ deletions: 342
300
+ updated_at: <%= 8.days.ago.to_s(:db) %>
301
+
302
+ undeployed_5:
303
+ id: 405
304
+ sha: 457578b362bf2b4df5903e1c7960929361c3435a
305
+ message: "Merge pull request #7 from shipit-engine/yoloshipit\n\nyoloshipit!"
306
+ stack: shipit_undeployed
307
+ author: walrus
308
+ committer: walrus
309
+ authored_at: <%= 4.days.ago.to_s(:db) %>
310
+ committed_at: <%= 3.days.ago.to_s(:db) %>
311
+ additions: 420
312
+ deletions: 342
313
+ updated_at: <%= 8.days.ago.to_s(:db) %>
314
+
315
+ undeployed_6:
316
+ id: 406
317
+ sha: 467578b362bf2b4df5903e1c7960929361c3435a
318
+ message: "Merge pull request #7 from shipit-engine/yoloshipit\n\nyoloshipit!"
319
+ stack: shipit_undeployed
320
+ author: walrus
321
+ committer: walrus
322
+ authored_at: <%= 4.days.ago.to_s(:db) %>
323
+ committed_at: <%= 3.days.ago.to_s(:db) %>
324
+ additions: 420
325
+ deletions: 342
326
+ updated_at: <%= 8.days.ago.to_s(:db) %>
327
+
328
+ undeployed_7:
329
+ id: 407
330
+ sha: 477578b362bf2b4df5903e1c7960929361c3435a
331
+ message: "Merge pull request #7 from shipit-engine/yoloshipit\n\nyoloshipit!"
332
+ stack: shipit_undeployed
333
+ author: walrus
334
+ committer: walrus
335
+ authored_at: <%= 4.days.ago.to_s(:db) %>
336
+ committed_at: <%= 3.days.ago.to_s(:db) %>
337
+ additions: 420
338
+ deletions: 342
339
+ updated_at: <%= 8.days.ago.to_s(:db) %>
340
+
341
+ single:
342
+ id: 501
343
+ sha: 547578b362bf2b4df5903e1c7960929361c3435a
344
+ message: "first commit"
345
+ stack: shipit_single
346
+ author: walrus
347
+ committer: walrus
348
+ authored_at: <%= 4.days.ago.to_s(:db) %>
349
+ committed_at: <%= 3.days.ago.to_s(:db) %>
350
+ additions: 420
351
+ deletions: 342
352
+ updated_at: <%= 8.days.ago.to_s(:db) %>
@@ -5,8 +5,8 @@ shipit:
5
5
  branch: master
6
6
  ignore_ci: false
7
7
  merge_queue_enabled: true
8
- tasks_count: 3
9
- undeployed_commits_count: 1
8
+ tasks_count: 8
9
+ undeployed_commits_count: 2
10
10
  cached_deploy_spec: >
11
11
  {
12
12
  "machine": {"environment": {}},
@@ -62,6 +62,7 @@ shipit_canaries:
62
62
  merge_queue_enabled: true
63
63
  tasks_count: 3
64
64
  undeployed_commits_count: 1
65
+ continuous_deployment: true
65
66
  cached_deploy_spec: >
66
67
  {
67
68
  "machine": {"environment": {}},
@@ -224,4 +225,98 @@ check_runs:
224
225
  environment: production
225
226
  branch: master
226
227
  tasks_count: 0
227
- undeployed_commits_count: 1
228
+ undeployed_commits_count: 1
229
+
230
+ shipit_undeployed:
231
+ repo_owner: "shopify"
232
+ repo_name: "shipit-engine"
233
+ environment: "undeployed"
234
+ branch: master
235
+ ignore_ci: true
236
+ merge_queue_enabled: true
237
+ tasks_count: 2
238
+ undeployed_commits_count: 6
239
+ continuous_deployment: true
240
+ cached_deploy_spec: >
241
+ {
242
+ "machine": {"environment": {}},
243
+ "review": {
244
+ "checklist": ["foo", "bar", "baz"],
245
+ "monitoring": [
246
+ {"image": "https://example.com/monitor.png", "width": 200, "height": 300}
247
+ ]
248
+ },
249
+ "dependencies": {"override": []},
250
+ "deploy": {"override": null, "interval": 60, "max_commits": 3, "variables": [{"name": "SAFETY_DISABLED", "title": "Set to 1 to do dangerous things", "default": 0}]},
251
+ "rollback": {"override": ["echo 'Rollback!'"]},
252
+ "fetch": ["echo '42'"],
253
+ "tasks": {
254
+ "restart": {
255
+ "action": "Restart application",
256
+ "description": "Restart app and job servers",
257
+ "variables": [
258
+ {"name": "FOO", "title": "Set to 0 to foo", "default": 1},
259
+ {"name": "BAR", "title": "Set to 1 to bar", "default": 0},
260
+ {"name": "WALRUS", "title": "Walrus without a default value"}
261
+ ],
262
+ "steps": [
263
+ "cap $ENVIRONMENT deploy:restart"
264
+ ]
265
+ },
266
+ "flush_cache": {
267
+ "action": "Flush cache",
268
+ "description": "Flush the application cache",
269
+ "steps": [
270
+ "cap $ENVIRONMENT cache:flush"
271
+ ],
272
+ "allow_concurrency": true
273
+ }
274
+ },
275
+ "merge": {
276
+ "revalidate_after": 900
277
+ },
278
+ "ci": {
279
+ "hide": ["ci/hidden"],
280
+ "allow_failures": ["ci/ok_to_fail"]
281
+ }
282
+ }
283
+ last_deployed_at: <%= 8.days.ago.to_s(:db) %>
284
+ updated_at: <%= 8.days.ago.to_s(:db) %>
285
+
286
+ shipit_single:
287
+ repo_owner: "shopify"
288
+ repo_name: "shipit-engine"
289
+ environment: "single"
290
+ branch: master
291
+ ignore_ci: false
292
+ merge_queue_enabled: true
293
+ tasks_count: 2
294
+ undeployed_commits_count: 1
295
+ cached_deploy_spec: >
296
+ {
297
+ "machine": {"environment": {}},
298
+ "review": {
299
+ "checklist": ["foo", "bar", "baz"],
300
+ "monitoring": [
301
+ {"image": "https://example.com/monitor.png", "width": 200, "height": 300}
302
+ ]
303
+ },
304
+ "dependencies": {"override": []},
305
+ "deploy": {"override": null},
306
+ "rollback": {"override": ["echo 'Rollback!'"]},
307
+ "fetch": ["echo '42'"],
308
+ "tasks": {
309
+ "restart": {
310
+ "action": "Restart application",
311
+ "description": "Restart app and job servers",
312
+ "steps": [
313
+ "cap $ENVIRONMENT deploy:restart"
314
+ ]
315
+ }
316
+ },
317
+ "ci": {
318
+ "blocking": ["soc/compliance"]
319
+ }
320
+ }
321
+ updated_at: <%= 8.days.ago.to_s(:db) %>
322
+ last_deployed_at: <%= 8.days.ago.to_s(:db) %>
@@ -236,3 +236,45 @@ shipit_with_title_parsing_issue:
236
236
  created_at: <%= (60 - 3).minutes.ago.to_s(:db) %>
237
237
  started_at: <%= (60 - 3).minutes.ago.to_s(:db) %>
238
238
  ended_at: <%= (60 - 4).minutes.ago.to_s(:db) %>
239
+
240
+ shipit_undeployed_1:
241
+ id: 201
242
+ user: walrus
243
+ since_commit_id: 401
244
+ until_commit_id: 401
245
+ type: Shipit::Deploy
246
+ stack: shipit_undeployed
247
+ status: success
248
+ additions: 1
249
+ deletions: 1
250
+ created_at: <%= (60 - 1).minutes.ago.to_s(:db) %>
251
+ started_at: <%= (60 - 1).minutes.ago.to_s(:db) %>
252
+ ended_at: <%= (60 - 3).minutes.ago.to_s(:db) %>
253
+
254
+ shipit_undeployed_2:
255
+ id: 202
256
+ user: walrus
257
+ since_commit_id: 402
258
+ until_commit_id: 403
259
+ type: Shipit::Deploy
260
+ stack: shipit_undeployed
261
+ status: running
262
+ additions: 12
263
+ deletions: 64
264
+ created_at: <%= (60 - 2).minutes.ago.to_s(:db) %>
265
+ started_at: <%= (60 - 2).minutes.ago.to_s(:db) %>
266
+ ended_at: <%= (60 - 4).minutes.ago.to_s(:db) %>
267
+
268
+ shipit_single:
269
+ id: 301
270
+ user: walrus
271
+ since_commit_id: 501
272
+ until_commit_id: 501
273
+ type: Shipit::Deploy
274
+ stack: shipit_single
275
+ status: running
276
+ additions: 12
277
+ deletions: 64
278
+ created_at: <%= (60 - 2).minutes.ago.to_s(:db) %>
279
+ started_at: <%= (60 - 2).minutes.ago.to_s(:db) %>
280
+ ended_at: <%= (60 - 4).minutes.ago.to_s(:db) %>