shipit-engine 0.33.0 → 0.34.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -2
  3. data/app/assets/stylesheets/_pages/_deploy.scss +0 -2
  4. data/app/controllers/shipit/api/ccmenu_controller.rb +1 -1
  5. data/app/controllers/shipit/api/deploys_controller.rb +2 -0
  6. data/app/controllers/shipit/api/rollbacks_controller.rb +2 -1
  7. data/app/controllers/shipit/api/stacks_controller.rb +1 -0
  8. data/app/controllers/shipit/deploys_controller.rb +1 -1
  9. data/app/controllers/shipit/stacks_controller.rb +2 -2
  10. data/app/controllers/shipit/tasks_controller.rb +2 -2
  11. data/app/controllers/shipit/webhooks_controller.rb +23 -4
  12. data/app/helpers/shipit/shipit_helper.rb +0 -1
  13. data/app/jobs/shipit/deliver_hook_job.rb +1 -1
  14. data/app/jobs/shipit/github_sync_job.rb +13 -9
  15. data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +1 -1
  16. data/app/models/shipit/anonymous_user.rb +6 -2
  17. data/app/models/shipit/check_run.rb +36 -0
  18. data/app/models/shipit/commit.rb +20 -9
  19. data/app/models/shipit/commit_checks.rb +13 -13
  20. data/app/models/shipit/commit_deployment.rb +3 -3
  21. data/app/models/shipit/commit_deployment_status.rb +3 -3
  22. data/app/models/shipit/deploy.rb +16 -11
  23. data/app/models/shipit/deploy_spec/lerna_discovery.rb +12 -4
  24. data/app/models/shipit/duration.rb +2 -0
  25. data/app/models/shipit/hook.rb +26 -2
  26. data/app/models/shipit/merge_request.rb +9 -7
  27. data/app/models/shipit/pull_request.rb +1 -1
  28. data/app/models/shipit/release_status.rb +1 -1
  29. data/app/models/shipit/repository.rb +9 -3
  30. data/app/models/shipit/review_stack.rb +16 -2
  31. data/app/models/shipit/stack.rb +59 -25
  32. data/app/models/shipit/status/group.rb +1 -1
  33. data/app/models/shipit/task.rb +6 -2
  34. data/app/models/shipit/task_execution_strategy/default.rb +4 -5
  35. data/app/models/shipit/team.rb +4 -2
  36. data/app/models/shipit/user.rb +4 -0
  37. data/app/models/shipit/webhooks/handlers/pull_request/review_stack_adapter.rb +1 -1
  38. data/app/models/shipit/webhooks/handlers/push_handler.rb +4 -1
  39. data/app/serializers/shipit/merge_request_serializer.rb +1 -1
  40. data/app/validators/subset_validator.rb +1 -1
  41. data/app/views/layouts/merge_status.html.erb +1 -1
  42. data/app/views/shipit/stacks/_banners.html.erb +2 -1
  43. data/app/views/shipit/stacks/new.html.erb +1 -1
  44. data/config/secrets.development.example.yml +24 -0
  45. data/config/secrets.development.shopify.yml +20 -9
  46. data/db/migrate/20210325194053_remove_stacks_branch_default.rb +5 -0
  47. data/db/migrate/20210504200438_add_github_updated_at_to_check_runs.rb +5 -0
  48. data/lib/shipit.rb +39 -15
  49. data/lib/shipit/command.rb +7 -6
  50. data/lib/shipit/commands.rb +9 -2
  51. data/lib/shipit/engine.rb +2 -0
  52. data/lib/shipit/flock.rb +8 -1
  53. data/lib/shipit/github_app.rb +7 -5
  54. data/lib/shipit/octokit_iterator.rb +3 -3
  55. data/lib/shipit/simple_message_verifier.rb +2 -2
  56. data/lib/shipit/stack_commands.rb +28 -4
  57. data/lib/shipit/task_commands.rb +6 -0
  58. data/lib/shipit/version.rb +1 -1
  59. data/lib/snippets/publish-lerna-independent-packages +35 -34
  60. data/lib/snippets/publish-lerna-independent-packages-legacy +39 -0
  61. data/test/controllers/api/ccmenu_controller_test.rb +1 -1
  62. data/test/controllers/api/deploys_controller_test.rb +17 -0
  63. data/test/controllers/api/stacks_controller_test.rb +21 -7
  64. data/test/controllers/webhooks_controller_test.rb +26 -11
  65. data/test/dummy/app/assets/config/manifest.js +3 -0
  66. data/test/dummy/config/application.rb +1 -1
  67. data/test/dummy/config/database.yml +9 -0
  68. data/test/dummy/config/environments/development.rb +1 -1
  69. data/test/dummy/config/secrets_double_github_app.yml +79 -0
  70. data/test/dummy/db/schema.rb +5 -4
  71. data/test/dummy/db/seeds.rb +1 -0
  72. data/test/fixtures/payloads/check_suite_master.json +2 -30
  73. data/test/fixtures/payloads/push_master.json +1 -1
  74. data/test/fixtures/payloads/push_not_master.json +1 -1
  75. data/test/fixtures/shipit/commits.yml +2 -2
  76. data/test/fixtures/shipit/hooks.yml +1 -0
  77. data/test/fixtures/shipit/tasks.yml +1 -1
  78. data/test/helpers/json_helper.rb +5 -1
  79. data/test/jobs/github_sync_job_test.rb +2 -1
  80. data/test/models/commit_deployment_status_test.rb +3 -3
  81. data/test/models/commits_test.rb +2 -0
  82. data/test/models/deploy_spec_test.rb +7 -0
  83. data/test/models/deploys_test.rb +18 -0
  84. data/test/models/hook_test.rb +30 -1
  85. data/test/models/merge_request_test.rb +19 -4
  86. data/test/models/shipit/check_run_test.rb +124 -5
  87. data/test/models/shipit/review_stack_test.rb +38 -6
  88. data/test/models/shipit/stacks_test.rb +42 -4
  89. data/test/models/shipit/webhooks/handlers/pull_request/review_stack_adapter_test.rb +24 -0
  90. data/test/models/tasks_test.rb +22 -0
  91. data/test/test_helper.rb +15 -0
  92. data/test/unit/anonymous_user_serializer_test.rb +1 -1
  93. data/test/unit/command_test.rb +5 -0
  94. data/test/unit/commit_serializer_test.rb +1 -1
  95. data/test/unit/deploy_commands_test.rb +70 -14
  96. data/test/unit/deploy_serializer_test.rb +1 -1
  97. data/test/unit/github_app_test.rb +2 -3
  98. data/test/unit/github_apps_test.rb +416 -0
  99. data/test/unit/shipit_deployment_checks_test.rb +77 -0
  100. data/test/unit/shipit_test.rb +14 -0
  101. data/test/unit/user_serializer_test.rb +1 -1
  102. metadata +202 -191
@@ -11,7 +11,7 @@ module Shipit
11
11
  assert_equal DeploySerializer, serializer
12
12
  serialized = serializer.new(deploy).to_json
13
13
 
14
- assert_json("commits.0.author.name", first_commit_author.name, document: serialized)
14
+ assert_json_document(serialized, "commits.0.author.name", first_commit_author.name)
15
15
  end
16
16
  end
17
17
  end
@@ -18,7 +18,7 @@ module Shipit
18
18
 
19
19
  test "#initialize doesn't raise if given an empty config" do
20
20
  assert_nothing_raised do
21
- GitHubApp.new({})
21
+ GitHubApp.new(nil, {})
22
22
  end
23
23
  end
24
24
 
@@ -183,7 +183,6 @@ module Shipit
183
183
  .any_instance
184
184
  .expects(:create_app_installation_access_token).with(config[:installation_id], anything)
185
185
  .returns(second_token)
186
-
187
186
  first_token = valid_app.token
188
187
 
189
188
  first_cached_token = Rails.cache.fetch(@token_cache_key)
@@ -199,7 +198,7 @@ module Shipit
199
198
  private
200
199
 
201
200
  def app(extra_config = {})
202
- GitHubApp.new(default_config.deep_merge(extra_config))
201
+ GitHubApp.new(nil, default_config.deep_merge(extra_config))
203
202
  end
204
203
 
205
204
  def default_config
@@ -0,0 +1,416 @@
1
+ # frozen_string_literal: true
2
+ require 'test_helper'
3
+
4
+ module Shipit
5
+ class GitHubAppsTestOrgOne < ActiveSupport::TestCase
6
+ setup do
7
+ @organization = "OrgOne"
8
+ @github = app(@organization)
9
+ @enterprise = app(@organization, domain: 'github.example.com')
10
+ @rails_env = Rails.env
11
+ @token_cache_key = "github:integration:#{@organization.downcase}:access-token"
12
+ Rails.cache.delete(@token_cache_key)
13
+ end
14
+
15
+ teardown do
16
+ Rails.env = @rails_env
17
+ Rails.cache.delete(@token_cache_key)
18
+ end
19
+
20
+ test "#domain defaults to github.com" do
21
+ assert_equal 'github.com', @github.domain
22
+ end
23
+
24
+ test "#url returns the HTTPS url to the github installation" do
25
+ assert_equal 'https://github.example.com', @enterprise.url
26
+ assert_equal 'https://github.example.com/foo/bar', @enterprise.url('/foo/bar')
27
+ assert_equal 'https://github.example.com/foo/bar/baz', @enterprise.url('foo/bar', 'baz')
28
+ end
29
+
30
+ test "#new_client retruns an Octokit::Client configured to use the github installation" do
31
+ assert_equal 'https://github.example.com/', @enterprise.new_client.web_endpoint
32
+ assert_equal 'https://github.example.com/api/v3/', @enterprise.new_client.api_endpoint
33
+ end
34
+
35
+ test "#oauth_config.last[:client_options] is nil if domain is not overriden" do
36
+ assert_nil @github.oauth_config.last[:client_options][:site]
37
+ end
38
+
39
+ test "#oauth_config.last[:client_options] returns Enterprise endpoint if domain is overriden" do
40
+ assert_equal 'https://github.example.com/api/v3/', @enterprise.oauth_config.last[:client_options][:site]
41
+ end
42
+
43
+ test "#initialize doesn't raise if given an empty config" do
44
+ assert_nothing_raised do
45
+ GitHubApp.new(@organization, {})
46
+ end
47
+ end
48
+
49
+ test "#api_status" do
50
+ stub_request(:get, "https://www.githubstatus.com/api/v2/components.json").to_return(
51
+ status: 200,
52
+ body: %(
53
+ {
54
+ "page":{},
55
+ "components":[
56
+ {
57
+ "id":"brv1bkgrwx7q",
58
+ "name":"API Requests",
59
+ "status":"operational",
60
+ "created_at":"2017-01-31T20:01:46.621Z",
61
+ "updated_at":"2019-07-23T18:41:18.197Z",
62
+ "position":2,
63
+ "description":"Requests for GitHub APIs",
64
+ "showcase":false,
65
+ "group_id":null,
66
+ "page_id":"kctbh9vrtdwd",
67
+ "group":false,
68
+ "only_show_if_degraded":false
69
+ }
70
+ ]
71
+ }
72
+ ),
73
+ )
74
+ assert_equal "operational", app(@organization).api_status[:status]
75
+ end
76
+
77
+ test "#github token is refreshed after expiration" do
78
+ Rails.env = 'not_test'
79
+ config = {
80
+ app_id: "test_id",
81
+ installation_id: "test_installation_id",
82
+ private_key: "test_private_key",
83
+ }
84
+ initial_token = OpenStruct.new(
85
+ token: "some_initial_github_token",
86
+ expires_at: Time.now.utc + 60.minutes,
87
+ )
88
+ second_token = OpenStruct.new(
89
+ token: "some_new_github_token",
90
+ expires_at: initial_token.expires_at + 60.minutes,
91
+ )
92
+ auth_payload = "test_auth_payload"
93
+
94
+ GitHubApp.any_instance.expects(:authentication_payload).twice.returns(auth_payload)
95
+ valid_app = app(@organization, config)
96
+
97
+ freeze_time do
98
+ Octokit::Client
99
+ .any_instance
100
+ .expects(:create_app_installation_access_token).twice.with(config[:installation_id], anything)
101
+ .returns(initial_token, second_token)
102
+
103
+ initial_token = valid_app.token
104
+ initial_cached_token = Rails.cache.fetch(@token_cache_key)
105
+ assert_equal initial_token, initial_cached_token.to_s
106
+
107
+ travel 5.minutes
108
+ assert_equal initial_token, valid_app.token
109
+
110
+ travel_to initial_cached_token.expires_at + 5.minutes
111
+ assert_equal second_token.token, valid_app.token
112
+ end
113
+ end
114
+
115
+ test "#github token is refreshed in refresh window before expiry" do
116
+ Rails.env = 'not_test'
117
+ config = {
118
+ app_id: "test_id",
119
+ installation_id: "test_installation_id",
120
+ private_key: "test_private_key",
121
+ }
122
+ initial_token = OpenStruct.new(
123
+ token: "some_initial_github_token",
124
+ expires_at: Time.now.utc + 60.minutes,
125
+ )
126
+ second_token = OpenStruct.new(
127
+ token: "some_new_github_token",
128
+ expires_at: initial_token.expires_at + 60.minutes,
129
+ )
130
+ auth_payload = "test_auth_payload"
131
+
132
+ GitHubApp.any_instance.expects(:authentication_payload).twice.returns(auth_payload)
133
+ valid_app = app(@organization, config)
134
+
135
+ freeze_time do
136
+ Octokit::Client
137
+ .any_instance
138
+ .expects(:create_app_installation_access_token).twice.with(config[:installation_id], anything)
139
+ .returns(initial_token, second_token)
140
+
141
+ initial_token = valid_app.token
142
+ initial_cached_token = Rails.cache.fetch(@token_cache_key)
143
+ assert_equal initial_token, initial_cached_token.to_s
144
+
145
+ # Travel forward, but before the token is refreshed, so the cached value should be the same.
146
+ travel 40.minutes
147
+ assert_equal initial_token, valid_app.token
148
+
149
+ # Travel to when the token should refresh, but is not expired, which should result in our cache.fetch update block.
150
+ travel 15.minutes
151
+ updated_token = valid_app.token
152
+ assert_not_equal initial_token, updated_token
153
+
154
+ cached_token = Rails.cache.fetch(@token_cache_key)
155
+ assert_operator cached_token.expires_at, :>, initial_cached_token.expires_at
156
+ end
157
+ end
158
+
159
+ test "#github token is missing refresh_at field" do
160
+ # $debugging = true
161
+ Rails.env = 'not_test'
162
+ config = {
163
+ app_id: "test_id",
164
+ installation_id: "test_installation_id",
165
+ private_key: "test_private_key",
166
+ }
167
+ initial_cached_token = Shipit::GitHubApp::Token.new("some_initial_github_token", Time.now.utc - 1.minute)
168
+ initial_cached_token.instance_variable_set(:@refresh_at, nil)
169
+
170
+ second_token = OpenStruct.new(
171
+ token: "some_new_github_token",
172
+ expires_at: initial_cached_token.expires_at + 60.minutes,
173
+ )
174
+ auth_payload = "test_auth_payload"
175
+
176
+ GitHubApp.any_instance.expects(:authentication_payload).returns(auth_payload)
177
+ valid_app = app(@organization, config)
178
+
179
+ freeze_time do
180
+ valid_app.instance_variable_set(:@token, initial_cached_token)
181
+ Rails.cache.write(@token_cache_key, initial_cached_token, expires_in: 1.minute)
182
+
183
+ Octokit::Client
184
+ .any_instance
185
+ .expects(:create_app_installation_access_token).with(config[:installation_id], anything)
186
+ .returns(second_token)
187
+
188
+ first_token = valid_app.token
189
+
190
+ first_cached_token = Rails.cache.fetch(@token_cache_key)
191
+ assert_equal first_token, first_cached_token.to_s
192
+
193
+ travel_to first_cached_token.expires_at + 5.minutes
194
+ new_token = valid_app.token
195
+
196
+ assert_equal second_token.token, new_token
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ def app(organization, extra_config = {})
203
+ GitHubApp.new(organization, double_github_app_config.deep_merge(extra_config))
204
+ end
205
+
206
+ def double_github_app_config
207
+ YAML.load_file('test/dummy/config/secrets_double_github_app.yml')
208
+ end
209
+ end
210
+
211
+ class GitHubAppsTestOrgTwo < ActiveSupport::TestCase
212
+ setup do
213
+ @organization = "OrgTwo"
214
+ @github = app(@organization)
215
+ @enterprise = app(@organization, domain: 'github.example.com')
216
+ @rails_env = Rails.env
217
+ @token_cache_key = "github:integration:#{@organization.downcase}:access-token"
218
+ Rails.cache.delete(@token_cache_key)
219
+ end
220
+
221
+ teardown do
222
+ Rails.env = @rails_env
223
+ Rails.cache.delete(@token_cache_key)
224
+ end
225
+
226
+ test "#domain defaults to github.com" do
227
+ assert_equal 'github.com', @github.domain
228
+ end
229
+
230
+ test "#url returns the HTTPS url to the github installation" do
231
+ assert_equal 'https://github.example.com', @enterprise.url
232
+ assert_equal 'https://github.example.com/foo/bar', @enterprise.url('/foo/bar')
233
+ assert_equal 'https://github.example.com/foo/bar/baz', @enterprise.url('foo/bar', 'baz')
234
+ end
235
+
236
+ test "#new_client retruns an Octokit::Client configured to use the github installation" do
237
+ assert_equal 'https://github.example.com/', @enterprise.new_client.web_endpoint
238
+ assert_equal 'https://github.example.com/api/v3/', @enterprise.new_client.api_endpoint
239
+ end
240
+
241
+ test "#oauth_config.last[:client_options] is nil if domain is not overriden" do
242
+ assert_nil @github.oauth_config.last[:client_options][:site]
243
+ end
244
+
245
+ test "#oauth_config.last[:client_options] returns Enterprise endpoint if domain is overriden" do
246
+ assert_equal 'https://github.example.com/api/v3/', @enterprise.oauth_config.last[:client_options][:site]
247
+ end
248
+
249
+ test "#initialize doesn't raise if given an empty config" do
250
+ assert_nothing_raised do
251
+ GitHubApp.new(@organization, {})
252
+ end
253
+ end
254
+
255
+ test "#api_status" do
256
+ stub_request(:get, "https://www.githubstatus.com/api/v2/components.json").to_return(
257
+ status: 200,
258
+ body: %(
259
+ {
260
+ "page":{},
261
+ "components":[
262
+ {
263
+ "id":"brv1bkgrwx7q",
264
+ "name":"API Requests",
265
+ "status":"operational",
266
+ "created_at":"2017-01-31T20:01:46.621Z",
267
+ "updated_at":"2019-07-23T18:41:18.197Z",
268
+ "position":2,
269
+ "description":"Requests for GitHub APIs",
270
+ "showcase":false,
271
+ "group_id":null,
272
+ "page_id":"kctbh9vrtdwd",
273
+ "group":false,
274
+ "only_show_if_degraded":false
275
+ }
276
+ ]
277
+ }
278
+ ),
279
+ )
280
+ assert_equal "operational", app(@organization).api_status[:status]
281
+ end
282
+
283
+ test "#github token is refreshed after expiration" do
284
+ Rails.env = 'not_test'
285
+ config = {
286
+ app_id: "test_id",
287
+ installation_id: "test_installation_id",
288
+ private_key: "test_private_key",
289
+ }
290
+ initial_token = OpenStruct.new(
291
+ token: "some_initial_github_token",
292
+ expires_at: Time.now.utc + 60.minutes,
293
+ )
294
+ second_token = OpenStruct.new(
295
+ token: "some_new_github_token",
296
+ expires_at: initial_token.expires_at + 60.minutes,
297
+ )
298
+ auth_payload = "test_auth_payload"
299
+
300
+ GitHubApp.any_instance.expects(:authentication_payload).twice.returns(auth_payload)
301
+ valid_app = app(@organization, config)
302
+
303
+ freeze_time do
304
+ Octokit::Client
305
+ .any_instance
306
+ .expects(:create_app_installation_access_token).twice.with(config[:installation_id], anything)
307
+ .returns(initial_token, second_token)
308
+
309
+ initial_token = valid_app.token
310
+ initial_cached_token = Rails.cache.fetch(@token_cache_key)
311
+ assert_equal initial_token, initial_cached_token.to_s
312
+
313
+ travel 5.minutes
314
+ assert_equal initial_token, valid_app.token
315
+
316
+ travel_to initial_cached_token.expires_at + 5.minutes
317
+ assert_equal second_token.token, valid_app.token
318
+ end
319
+ end
320
+
321
+ test "#github token is refreshed in refresh window before expiry" do
322
+ Rails.env = 'not_test'
323
+ config = {
324
+ app_id: "test_id",
325
+ installation_id: "test_installation_id",
326
+ private_key: "test_private_key",
327
+ }
328
+ initial_token = OpenStruct.new(
329
+ token: "some_initial_github_token",
330
+ expires_at: Time.now.utc + 60.minutes,
331
+ )
332
+ second_token = OpenStruct.new(
333
+ token: "some_new_github_token",
334
+ expires_at: initial_token.expires_at + 60.minutes,
335
+ )
336
+ auth_payload = "test_auth_payload"
337
+
338
+ GitHubApp.any_instance.expects(:authentication_payload).twice.returns(auth_payload)
339
+ valid_app = app(@organization, config)
340
+
341
+ freeze_time do
342
+ Octokit::Client
343
+ .any_instance
344
+ .expects(:create_app_installation_access_token).twice.with(config[:installation_id], anything)
345
+ .returns(initial_token, second_token)
346
+
347
+ initial_token = valid_app.token
348
+ initial_cached_token = Rails.cache.fetch(@token_cache_key)
349
+ assert_equal initial_token, initial_cached_token.to_s
350
+
351
+ # Travel forward, but before the token is refreshed, so the cached value should be the same.
352
+ travel 40.minutes
353
+ assert_equal initial_token, valid_app.token
354
+
355
+ # Travel to when the token should refresh, but is not expired, which should result in our cache.fetch update block.
356
+ travel 15.minutes
357
+ updated_token = valid_app.token
358
+ assert_not_equal initial_token, updated_token
359
+
360
+ cached_token = Rails.cache.fetch(@token_cache_key)
361
+ assert_operator cached_token.expires_at, :>, initial_cached_token.expires_at
362
+ end
363
+ end
364
+
365
+ test "#github token is missing refresh_at field" do
366
+ # $debugging = true
367
+ Rails.env = 'not_test'
368
+ config = {
369
+ app_id: "test_id",
370
+ installation_id: "test_installation_id",
371
+ private_key: "test_private_key",
372
+ }
373
+ initial_cached_token = Shipit::GitHubApp::Token.new("some_initial_github_token", Time.now.utc - 1.minute)
374
+ initial_cached_token.instance_variable_set(:@refresh_at, nil)
375
+
376
+ second_token = OpenStruct.new(
377
+ token: "some_new_github_token",
378
+ expires_at: initial_cached_token.expires_at + 60.minutes,
379
+ )
380
+ auth_payload = "test_auth_payload"
381
+
382
+ GitHubApp.any_instance.expects(:authentication_payload).returns(auth_payload)
383
+ valid_app = app(@organization, config)
384
+
385
+ freeze_time do
386
+ valid_app.instance_variable_set(:@token, initial_cached_token)
387
+ Rails.cache.write(@token_cache_key, initial_cached_token, expires_in: 1.minute)
388
+
389
+ Octokit::Client
390
+ .any_instance
391
+ .expects(:create_app_installation_access_token).with(config[:installation_id], anything)
392
+ .returns(second_token)
393
+
394
+ first_token = valid_app.token
395
+
396
+ first_cached_token = Rails.cache.fetch(@token_cache_key)
397
+ assert_equal first_token, first_cached_token.to_s
398
+
399
+ travel_to first_cached_token.expires_at + 5.minutes
400
+ new_token = valid_app.token
401
+
402
+ assert_equal second_token.token, new_token
403
+ end
404
+ end
405
+
406
+ private
407
+
408
+ def app(organization, extra_config = {})
409
+ GitHubApp.new(organization, double_github_app_config.deep_merge(extra_config))
410
+ end
411
+
412
+ def double_github_app_config
413
+ YAML.load_file('test/dummy/config/secrets_double_github_app.yml')
414
+ end
415
+ end
416
+ end