shipit-engine 0.20.1 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +43 -6
  3. data/app/assets/stylesheets/_base/_base.scss +4 -0
  4. data/app/assets/stylesheets/_pages/_commits.scss +3 -1
  5. data/app/assets/stylesheets/_pages/_deploy.scss +4 -2
  6. data/app/controllers/concerns/shipit/authentication.rb +1 -1
  7. data/app/controllers/shipit/api/base_controller.rb +6 -1
  8. data/app/controllers/shipit/api/pull_requests_controller.rb +1 -1
  9. data/app/controllers/shipit/commit_checks_controller.rb +1 -1
  10. data/app/controllers/shipit/shipit_controller.rb +1 -5
  11. data/app/controllers/shipit/stacks_controller.rb +2 -0
  12. data/app/controllers/shipit/tasks_controller.rb +1 -1
  13. data/app/controllers/shipit/webhooks_controller.rb +2 -2
  14. data/app/helpers/shipit/deploys_helper.rb +9 -0
  15. data/app/helpers/shipit/shipit_helper.rb +17 -15
  16. data/app/helpers/shipit/stacks_helper.rb +6 -1
  17. data/app/jobs/shipit/destroy_stack_job.rb +4 -2
  18. data/app/jobs/shipit/fetch_deployed_revision_job.rb +1 -1
  19. data/app/jobs/shipit/github_sync_job.rb +1 -1
  20. data/app/jobs/shipit/merge_pull_requests_job.rb +3 -3
  21. data/app/jobs/shipit/perform_task_job.rb +3 -0
  22. data/app/jobs/shipit/purge_old_deliveries_job.rb +1 -0
  23. data/app/models/shipit/api_client.rb +1 -1
  24. data/app/models/shipit/commit.rb +29 -6
  25. data/app/models/shipit/commit_deployment.rb +1 -1
  26. data/app/models/shipit/commit_deployment_status.rb +1 -1
  27. data/app/models/shipit/deploy_spec.rb +19 -2
  28. data/app/models/shipit/deploy_spec/bundler_discovery.rb +1 -10
  29. data/app/models/shipit/deploy_spec/file_system.rb +6 -0
  30. data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +1 -1
  31. data/app/models/shipit/deploy_spec/lerna_discovery.rb +85 -0
  32. data/app/models/shipit/deploy_spec/npm_discovery.rb +103 -5
  33. data/app/models/shipit/deploy_spec/pypi_discovery.rb +4 -2
  34. data/app/models/shipit/deploy_spec/rubygems_discovery.rb +4 -2
  35. data/app/models/shipit/duration.rb +1 -1
  36. data/app/models/shipit/github_status.rb +1 -1
  37. data/app/models/shipit/hook.rb +4 -5
  38. data/app/models/shipit/output_chunk.rb +1 -1
  39. data/app/models/shipit/pull_request.rb +36 -15
  40. data/app/models/shipit/stack.rb +15 -9
  41. data/app/models/shipit/status/common.rb +4 -0
  42. data/app/models/shipit/status/group.rb +4 -0
  43. data/app/models/shipit/task.rb +20 -8
  44. data/app/models/shipit/task_definition.rb +2 -2
  45. data/app/models/shipit/undeployed_commit.rb +13 -2
  46. data/app/models/shipit/user.rb +1 -1
  47. data/app/serializers/shipit/pull_request_serializer.rb +1 -1
  48. data/app/serializers/shipit/tail_task_serializer.rb +1 -1
  49. data/app/views/shipit/ccmenu/project.xml.builder +9 -8
  50. data/app/views/shipit/deploys/_deploy.html.erb +3 -2
  51. data/app/views/shipit/stacks/_banners.html.erb +4 -1
  52. data/app/views/shipit/stacks/settings.html.erb +4 -0
  53. data/app/views/shipit/statuses/_group.html.erb +1 -1
  54. data/app/views/shipit/statuses/_status.html.erb +1 -1
  55. data/app/views/shipit/tasks/_task.html.erb +1 -2
  56. data/config/locales/en.yml +2 -0
  57. data/config/secrets.development.example.yml +0 -4
  58. data/config/secrets.development.shopify.yml +1 -5
  59. data/db/migrate/20170904103242_reindex_deliveries.rb +7 -0
  60. data/db/migrate/20171120161420_add_base_info_to_pull_request.rb +7 -0
  61. data/db/migrate/20180202220850_add_aborted_by_to_tasks.rb +5 -0
  62. data/lib/shipit.rb +15 -23
  63. data/lib/shipit/command.rb +11 -3
  64. data/lib/shipit/engine.rb +0 -4
  65. data/lib/shipit/stack_commands.rb +3 -1
  66. data/lib/shipit/version.rb +1 -1
  67. data/lib/snippets/assert-lerna-fixed-version-tag +21 -0
  68. data/lib/snippets/assert-lerna-independent-version-tags +28 -0
  69. data/lib/snippets/generate-local-npmrc +19 -0
  70. data/lib/snippets/misconfigured-npm-publish-config +8 -0
  71. data/lib/snippets/publish-lerna-independent-packages +39 -0
  72. data/lib/snippets/push-to-heroku +5 -5
  73. data/lib/tasks/cron.rake +1 -1
  74. data/lib/tasks/dev.rake +1 -1
  75. data/test/controllers/api/deploys_controller_test.rb +19 -0
  76. data/test/controllers/api/stacks_controller_test.rb +1 -1
  77. data/test/controllers/github_authentication_controller_test.rb +1 -1
  78. data/test/controllers/stacks_controller_test.rb +10 -0
  79. data/test/controllers/tasks_controller_test.rb +2 -0
  80. data/test/controllers/webhooks_controller_test.rb +0 -7
  81. data/test/dummy/config/secrets.yml +0 -2
  82. data/test/dummy/db/development.sqlite3 +0 -0
  83. data/test/dummy/db/schema.rb +5 -3
  84. data/test/dummy/db/test.sqlite3 +0 -0
  85. data/test/fixtures/shipit/commits.yml +53 -0
  86. data/test/fixtures/shipit/pull_requests.yml +52 -0
  87. data/test/fixtures/shipit/stacks.yml +35 -0
  88. data/test/fixtures/shipit/statuses.yml +27 -0
  89. data/test/fixtures/shipit/tasks.yml +14 -0
  90. data/test/helpers/queries_helper.rb +1 -1
  91. data/test/jobs/merge_pull_requests_job_test.rb +19 -2
  92. data/test/jobs/perform_task_job_test.rb +26 -2
  93. data/test/models/commits_test.rb +55 -6
  94. data/test/models/deploy_spec_test.rb +288 -52
  95. data/test/models/deploys_test.rb +7 -7
  96. data/test/models/hook_test.rb +4 -3
  97. data/test/models/pull_request_test.rb +78 -24
  98. data/test/models/stacks_test.rb +21 -17
  99. data/test/models/status/group_test.rb +6 -0
  100. data/test/models/undeployed_commits_test.rb +9 -0
  101. data/test/models/users_test.rb +2 -2
  102. data/test/test_helper.rb +1 -1
  103. metadata +211 -222
  104. data/app/assets/javascripts/shipit_bs.js.coffee +0 -2
  105. data/app/assets/stylesheets/shipit_bs.scss +0 -22
  106. data/app/views/bootstrap/shipit/missing_settings.html.erb +0 -97
  107. data/app/views/bootstrap/shipit/stacks/new.html.erb +0 -44
  108. data/app/views/layouts/shipit_bootstrap.html.erb +0 -44
  109. data/lib/shipit/template_renderer_extension.rb +0 -16
@@ -87,3 +87,55 @@ shipit_pending_merged:
87
87
  mergeable: null
88
88
  additions: 23
89
89
  deletions: 43
90
+
91
+ shipit_mergeable_pending_ci:
92
+ stack: shipit
93
+ number: 67
94
+ title: Super duper nice feature
95
+ merge_status: pending
96
+ merge_requested_at: <%= 5.minute.ago.to_s(:db) %>
97
+ revalidated_at: <%= 5.minute.ago.to_s(:db) %>
98
+ merge_requested_by: walrus
99
+ github_id: 424244242424241
100
+ api_url: https://api.github.com/repos/shopify/shipit-engine/pulls/67
101
+ state: open
102
+ branch: feature-67
103
+ head_id: 4
104
+ mergeable: true
105
+ additions: 23
106
+ deletions: 43
107
+
108
+ shipit_pending_expired:
109
+ stack: shipit
110
+ number: 68
111
+ title: Noice !
112
+ merge_status: pending
113
+ merge_requested_at: <%= 50.minute.ago.to_s(:db) %>
114
+ revalidated_at: <%= 25.minute.ago.to_s(:db) %>
115
+ merge_requested_by: walrus
116
+ github_id: 48484848484848
117
+ api_url: https://api.github.com/repos/shopify/shipit-engine/pulls/68
118
+ state: open
119
+ branch: feature-68
120
+ head_id: 8
121
+ mergeable: true
122
+ additions: 23
123
+ deletions: 43
124
+
125
+ cyclimse_pending_merged:
126
+ id: 99
127
+ stack: cyclimse
128
+ number: 66
129
+ merge_status: merged
130
+ merge_requested_by: walrus
131
+ merge_requested_at: <%= 3.minute.ago.to_s(:db) %>
132
+ merged_at: <%= 2.minute.ago.to_s(:db) %>
133
+ revalidated_at: <%= 3.minute.ago.to_s(:db) %>
134
+ github_id: 43243243243232
135
+ api_url: https://api.github.com/repos/shopify/cyclimse/pulls/66
136
+ state: closed
137
+ branch: feature-64
138
+ head_id: 8
139
+ mergeable: null
140
+ additions: 23
141
+ deletions: 43
@@ -123,3 +123,38 @@ undeployed_stack:
123
123
  }
124
124
  }
125
125
  updated_at: <%= 8.days.ago.to_s(:db) %>
126
+
127
+ soc:
128
+ repo_owner: "shopify"
129
+ repo_name: "soc"
130
+ environment: "production"
131
+ branch: master
132
+ tasks_count: 0
133
+ undeployed_commits_count: 2
134
+ cached_deploy_spec: >
135
+ {
136
+ "machine": {"environment": {}},
137
+ "review": {
138
+ "checklist": ["foo", "bar", "baz"],
139
+ "monitoring": [
140
+ {"image": "https://example.com/monitor.png", "width": 200, "height": 300}
141
+ ]
142
+ },
143
+ "dependencies": {"override": []},
144
+ "deploy": {"override": null},
145
+ "rollback": {"override": ["echo 'Rollback!'"]},
146
+ "fetch": ["echo '42'"],
147
+ "tasks": {
148
+ "restart": {
149
+ "action": "Restart application",
150
+ "description": "Restart app and job servers",
151
+ "steps": [
152
+ "cap $ENVIRONMENT deploy:restart"
153
+ ]
154
+ }
155
+ },
156
+ "ci": {
157
+ "blocking": ["soc/compliance"]
158
+ }
159
+ }
160
+ updated_at: <%= 8.days.ago.to_s(:db) %>
@@ -98,3 +98,30 @@ shipit_pending_pr_success_travis:
98
98
  created_at: <%= 9.days.ago.to_s(:db) %>
99
99
  state: success
100
100
  target_url: "http://www.example.com"
101
+
102
+ soc_first:
103
+ stack: soc
104
+ commit_id: 101 # soc_first
105
+ description: Woops
106
+ context: soc/compliance
107
+ created_at: <%= 9.days.ago.to_s(:db) %>
108
+ state: failure
109
+ target_url: "http://www.example.com"
110
+
111
+ soc_second:
112
+ stack: soc
113
+ commit_id: 102 # soc_second
114
+ description: All good
115
+ context: soc/compliance
116
+ created_at: <%= 7.days.ago.to_s(:db) %>
117
+ state: success
118
+ target_url: "http://www.example.com"
119
+
120
+ soc_third:
121
+ stack: soc
122
+ commit_id: 103 # soc_third
123
+ description: All good
124
+ context: soc/compliance
125
+ created_at: <%= 7.days.ago.to_s(:db) %>
126
+ state: success
127
+ target_url: "http://www.example.com"
@@ -120,3 +120,17 @@ shipit_rollback:
120
120
  created_at: <%= (60 - 8).minutes.ago.to_s(:db) %>
121
121
  started_at: <%= (60 - 8).minutes.ago.to_s(:db) %>
122
122
  ended_at: <%= (60 - 7).minutes.ago.to_s(:db) %>
123
+
124
+ soc_deploy:
125
+ id: 9
126
+ user: walrus
127
+ since_commit_id: 101 # soc_first
128
+ until_commit_id: 101 # soc_first
129
+ type: Shipit::Deploy
130
+ stack: soc
131
+ status: success
132
+ additions: 1
133
+ deletions: 1
134
+ created_at: <%= (60 - 1).minutes.ago.to_s(:db) %>
135
+ started_at: <%= (60 - 1).minutes.ago.to_s(:db) %>
136
+ ended_at: <%= (60 - 3).minutes.ago.to_s(:db) %>
@@ -50,7 +50,7 @@ module QueriesHelper
50
50
 
51
51
  # FIXME: this seems bad. we should probably have a better way to indicate
52
52
  # the query was cached
53
- return if 'CACHE' == values[:name] || ignore.any? { |x| x =~ sql }
53
+ return if values[:name] == 'CACHE' || ignore.any? { |x| x =~ sql }
54
54
  log << sql
55
55
  end
56
56
  end
@@ -11,6 +11,8 @@ module Shipit
11
11
  @not_ready_pr = shipit_pull_requests(:shipit_pending_not_mergeable_yet)
12
12
  @closed_pr = shipit_pull_requests(:shipit_pending_closed)
13
13
  @merged_pr = shipit_pull_requests(:shipit_pending_merged)
14
+ @expired_pr = shipit_pull_requests(:shipit_pending_expired)
15
+ @mergable_pending_ci = shipit_pull_requests(:shipit_mergeable_pending_ci)
14
16
  end
15
17
 
16
18
  test "#perform rejects unmergeable PRs and merge the others" do
@@ -50,6 +52,13 @@ module Shipit
50
52
  assert_predicate @pending_pr.reload, :pending?
51
53
  end
52
54
 
55
+ test "#perform revalidate PRs but do not attempt to merge any if the stack doesn't allow merges" do
56
+ PullRequest.any_instance.stubs(:refresh!)
57
+ @stack.update!(lock_reason: 'Maintenance')
58
+ @job.perform(@stack)
59
+ assert_predicate @expired_pr.reload, :revalidating?
60
+ end
61
+
53
62
  test "#perform schedules a new job if the first PR in the queue is not mergeable yet" do
54
63
  PullRequest.any_instance.stubs(:refresh!)
55
64
 
@@ -67,11 +76,19 @@ module Shipit
67
76
  assert_predicate @closed_pr.reload, :canceled?
68
77
  end
69
78
 
70
- test "#perform completes merge requests for already merged PRs" do
79
+ test "#perform cancels merge requests for manually merged PRs" do
80
+ @pending_pr.cancel!
81
+ PullRequest.any_instance.stubs(:refresh!)
82
+ @job.perform(@stack)
83
+ assert_predicate @merged_pr.reload, :canceled?
84
+ end
85
+
86
+ test "#perform does not reject pull requests with pending statuses" do
71
87
  @pending_pr.cancel!
72
88
  PullRequest.any_instance.stubs(:refresh!)
73
89
  @job.perform(@stack)
74
- assert_predicate @merged_pr.reload, :merged?
90
+ refute_predicate @mergable_pending_ci.reload, :rejected?
91
+ refute_predicate @mergable_pending_ci.reload, :merged?
75
92
  end
76
93
  end
77
94
  end
@@ -67,15 +67,39 @@ module Shipit
67
67
  @job.perform(@deploy)
68
68
  end
69
69
 
70
- test "mark deploy as error if a command timeout" do
71
- Command.any_instance.expects(:stream!).at_least_once.raises(Command::TimedOut)
70
+ test "mark deploy as error an unexpected exception is raised" do
71
+ Command.any_instance.expects(:stream!).at_least_once.raises(Command::Denied)
72
72
 
73
73
  @job.perform(@deploy)
74
74
 
75
75
  assert_equal 'failed', @deploy.reload.status
76
+ assert_includes @deploy.chunk_output, 'Denied'
77
+ end
78
+
79
+ test "mark deploy as timedout if a command timeout" do
80
+ Command.any_instance.expects(:stream!).at_least_once.raises(Command::TimedOut)
81
+
82
+ @job.perform(@deploy)
83
+
84
+ assert_equal 'timedout', @deploy.reload.status
76
85
  assert_includes @deploy.chunk_output, 'TimedOut'
77
86
  end
78
87
 
88
+ test "mark deploy as timedout if a command exit in one of the codes in Shipit.timeout_exit_codes" do
89
+ previous_exit_codes = Shipit.timeout_exit_codes
90
+ begin
91
+ Shipit.timeout_exit_codes = [70].freeze
92
+
93
+ Command.any_instance.expects(:stream!).at_least_once.raises(Command::Failed.new('Blah', 70))
94
+
95
+ @job.perform(@deploy)
96
+
97
+ assert_equal 'timedout', @deploy.reload.status
98
+ ensure
99
+ Shipit.timeout_exit_codes = previous_exit_codes
100
+ end
101
+ end
102
+
79
103
  test "records stack support for rollbacks and fetching deployed revision" do
80
104
  @job.stubs(:capture!)
81
105
  @commands = stub(:commands)
@@ -148,8 +148,8 @@ module Shipit
148
148
  author: walrus,
149
149
  committer: walrus,
150
150
  sha: "ab12",
151
- authored_at: DateTime.now,
152
- committed_at: DateTime.now,
151
+ authored_at: Time.now,
152
+ committed_at: Time.now,
153
153
  message: "more fish!",
154
154
  )
155
155
  end
@@ -176,8 +176,8 @@ module Shipit
176
176
  author: walrus,
177
177
  committer: walrus,
178
178
  sha: "ab12",
179
- authored_at: DateTime.now,
180
- committed_at: DateTime.now,
179
+ authored_at: Time.now,
180
+ committed_at: Time.now,
181
181
  message: "more fish!",
182
182
  )
183
183
  @stack.reload
@@ -217,8 +217,8 @@ module Shipit
217
217
  author: walrus,
218
218
  committer: walrus,
219
219
  sha: "ab12",
220
- authored_at: DateTime.now,
221
- committed_at: DateTime.now,
220
+ authored_at: Time.now,
221
+ committed_at: Time.now,
222
222
  message: "more fish!",
223
223
  )
224
224
  stack.reload
@@ -306,6 +306,37 @@ module Shipit
306
306
  refute_predicate commit, :deployable?
307
307
  end
308
308
 
309
+ test "#deployable? is false if a blocking status is missing on a previous undeployed commit" do
310
+ blocking_commit = shipit_commits(:soc_second)
311
+ blocking_commit.statuses.delete_all
312
+
313
+ assert_predicate blocking_commit, :pending?
314
+ assert_predicate blocking_commit, :blocking?
315
+
316
+ commit = shipit_commits(:soc_third)
317
+ refute_predicate commit, :deployable?
318
+ end
319
+
320
+ test "#deployable? is false if a blocking status is failing on a previous undeployed commit" do
321
+ blocking_commit = shipit_commits(:soc_second)
322
+ blocking_commit.statuses.update_all(state: 'failure')
323
+
324
+ assert_predicate blocking_commit, :failure?
325
+ assert_predicate blocking_commit, :blocking?
326
+
327
+ commit = shipit_commits(:soc_third)
328
+ refute_predicate commit, :deployable?
329
+ end
330
+
331
+ test "#deployable? is true if no blocking status is failing or missing on a previous undeployed commit" do
332
+ blocking_commit = shipit_commits(:soc_second)
333
+ assert_predicate blocking_commit, :success?
334
+ refute_predicate blocking_commit, :blocking?
335
+
336
+ commit = shipit_commits(:soc_third)
337
+ assert_predicate commit, :deployable?
338
+ end
339
+
309
340
  expected_webhook_transitions = { # we expect deployable_status to fire on these transitions, and not on any others
310
341
  'unknown' => %w(pending success failure error),
311
342
  'pending' => %w(success failure error),
@@ -551,6 +582,24 @@ module Shipit
551
582
  assert revert.revert_of?(commit)
552
583
  end
553
584
 
585
+ test "deploy_requested_at defaults to commit created_at" do
586
+ assert_equal @commit.deploy_requested_at, @commit.created_at
587
+ end
588
+
589
+ test "when merged via the queue, deploy_requested_at is merge_requested_at" do
590
+ commit = shipit_commits(:cyclimse_merged)
591
+ assert_predicate commit, :pull_request?
592
+ assert_equal commit.pull_request, shipit_pull_requests(:cyclimse_pending_merged)
593
+ assert_equal commit.deploy_requested_at, commit.pull_request.merge_requested_at
594
+ end
595
+
596
+ test "when merged manually after being queued, deploy_requested_at is created_at" do
597
+ pr = shipit_pull_requests(:cyclimse_pending_merged)
598
+ pr.cancel!
599
+ commit = shipit_commits(:cyclimse_merged)
600
+ assert_equal commit.deploy_requested_at, commit.created_at
601
+ end
602
+
554
603
  private
555
604
 
556
605
  def expect_event(stack)
@@ -284,11 +284,16 @@ module Shipit
284
284
  'require' => [],
285
285
  'ignore' => [],
286
286
  'revalidate_after' => nil,
287
+ 'max_divergence' => {
288
+ 'commits' => nil,
289
+ 'age' => nil,
290
+ },
287
291
  },
288
292
  'ci' => {
289
293
  'hide' => [],
290
294
  'allow_failures' => [],
291
295
  'require' => [],
296
+ 'blocking' => [],
292
297
  },
293
298
  'machine' => {'environment' => {}, 'directory' => nil, 'cleanup' => true},
294
299
  'review' => {'checklist' => [], 'monitoring' => [], 'checks' => []},
@@ -318,59 +323,19 @@ module Shipit
318
323
  assert_equal 'SAFETY_DISABLED', variable_definition.name
319
324
  end
320
325
 
321
- test "task definitions prepend bundle exec by default" do
322
- @spec.expects(:load_config).returns('tasks' => {'restart' => {'steps' => %w(foo)}})
323
- @spec.expects(:bundler?).returns(true).at_least_once
324
- assert_deprecated(/Automatically prepending `bundle exec`/) do
325
- definition = @spec.find_task_definition('restart')
326
- assert_equal ['bundle exec foo'], definition.steps
327
- end
328
- end
329
-
330
- test "task definitions prepend bundle exec if enabled" do
331
- Shipit.expects(:automatically_prepend_bundle_exec).returns(true).at_least_once
326
+ test "task definitions don't prepend bundle exec by default" do
332
327
  @spec.expects(:load_config).returns('tasks' => {'restart' => {'steps' => %w(foo)}})
333
- @spec.expects(:bundler?).returns(true).at_least_once
334
328
  definition = @spec.find_task_definition('restart')
335
-
336
- assert_equal ['bundle exec foo'], definition.steps
337
- end
338
-
339
- test "task definitions do not prepend bundle exec if disabled" do
340
- Shipit.expects(:automatically_prepend_bundle_exec).returns(false).at_least_once
341
- @spec.expects(:load_config).returns('tasks' => {'restart' => {'steps' => %w(foo)}})
342
- definition = @spec.find_task_definition('restart')
343
-
344
329
  assert_equal ['foo'], definition.steps
345
330
  end
346
331
 
347
- test "task definitions do not prepend bundle exec if the task already does" do
348
- Shipit.expects(:automatically_prepend_bundle_exec).returns(true).at_least_once
349
- @spec.expects(:load_config).returns('tasks' => {'restart' => {'steps' => ['bundle exec foo']}})
350
- @spec.stubs(:bundler?).returns(true)
351
- definition = @spec.find_task_definition('restart')
352
-
353
- assert_equal ['bundle exec foo'], definition.steps
354
- end
355
-
356
- test "task definitions do not prepend bundle exec if depedency step is overridden" do
357
- @spec.expects(:load_config).returns(
358
- 'dependencies' => {'override' => []},
359
- 'tasks' => {'restart' => {'steps' => %w(foo)}},
360
- )
361
- @spec.expects(:bundler?).returns(true).at_least_once
362
- definition = @spec.find_task_definition('restart')
363
-
364
- assert_equal ['foo'], definition.steps
365
- end
366
-
367
- test "task definitions prepend bundle exec before serialization" do
332
+ test "task definitions don't bundle exec before serialization" do
368
333
  @spec.expects(:discover_task_definitions).returns('restart' => {'steps' => %w(foo)})
369
334
  @spec.expects(:bundler?).returns(true).at_least_once
370
335
 
371
336
  cached_spec = DeploySpec.load(DeploySpec.dump(@spec))
372
337
  definition = cached_spec.find_task_definition('restart')
373
- assert_equal ['bundle exec foo'], definition.steps
338
+ assert_equal ['foo'], definition.steps
374
339
  end
375
340
 
376
341
  test "#task_definitions returns kubernetes commands as well as comands from the config" do
@@ -455,6 +420,16 @@ module Shipit
455
420
  assert_equal %w(ci/circleci ci/jenkins), @spec.hidden_statuses
456
421
  end
457
422
 
423
+ test "#required_statuses automatically includes #blocking_statuses" do
424
+ @spec.expects(:load_config).returns(
425
+ 'ci' => {
426
+ 'require' => %w(ci/circleci),
427
+ 'blocking' => %w(soc/compliance),
428
+ },
429
+ )
430
+ assert_equal %w(ci/circleci soc/compliance), @spec.required_statuses
431
+ end
432
+
458
433
  test "pull_request_ignored_statuses defaults to the union of ci.hide and ci.allow_failures" do
459
434
  @spec.expects(:load_config).returns(
460
435
  'ci' => {
@@ -573,26 +548,43 @@ module Shipit
573
548
  assert_equal ['fake gem task'], @spec.deploy_steps
574
549
  end
575
550
 
551
+ test 'lerna monorepos take priority over solo npm deploys' do
552
+ @spec.expects(:discover_npm_package).never
553
+ @spec.stubs(:discover_lerna_packages).returns(['fake monorepo task']).once
554
+
555
+ assert_equal ['fake monorepo task'], @spec.deploy_steps
556
+ end
557
+
558
+ test '#lerna? is false if there is no lerna.json' do
559
+ @spec.expects(:lerna_json).returns(Shipit::Engine.root.join("tmp-#{SecureRandom.hex}"))
560
+ refute @spec.lerna?
561
+ end
562
+
576
563
  test '#npm? is false if there is no package.json' do
577
564
  @spec.expects(:package_json).returns(Shipit::Engine.root.join("tmp-#{SecureRandom.hex}"))
578
565
  refute @spec.npm?
579
566
  end
580
567
 
581
- test '#npm? is false if npm package is private' do
568
+ test '#npm? is true if npm package is public' do
582
569
  file = Pathname.new('/tmp/fake_package.json')
583
- file.write('{"private": true}')
570
+ file.write('{"private": false}')
584
571
 
585
572
  @spec.expects(:package_json).returns(file)
586
- refute @spec.npm?
573
+
574
+ assert @spec.npm?
587
575
  end
588
576
 
589
- test '#npm? is true if npm package is public' do
577
+ test '#npm? is false if npm package is private' do
590
578
  file = Pathname.new('/tmp/fake_package.json')
591
- file.write('{"private": false}')
579
+ file.write('{"private": true}')
592
580
 
593
581
  @spec.expects(:package_json).returns(file)
582
+ refute @spec.npm?
583
+ end
594
584
 
595
- assert @spec.npm?
585
+ test 'lerna monorepos have a checklist' do
586
+ @spec.stubs(:lerna?).returns(true).at_least_once
587
+ assert_match(/lerna publish --skip-npm/, @spec.review_checklist[0])
596
588
  end
597
589
 
598
590
  test 'npm packages have a checklist' do
@@ -600,14 +592,223 @@ module Shipit
600
592
  assert_match(/npm version/, @spec.review_checklist[0])
601
593
  end
602
594
 
595
+ test '#lerna_version returns the monorepo root version number' do
596
+ file = Pathname.new('/tmp/fake_lerna.json')
597
+ file.write('{"version": "1.0.0-beta.1"}')
598
+
599
+ @spec.expects(:lerna_json).returns(file)
600
+ assert_equal '1.0.0-beta.1', @spec.lerna_version
601
+ end
602
+
603
+ test '#package_version returns the version number' do
604
+ file = Pathname.new('/tmp/fake_package.json')
605
+ file.write('{"version": "1.0.0-beta.1"}')
606
+
607
+ @spec.expects(:package_json).returns(file)
608
+ assert_equal '1.0.0-beta.1', @spec.package_version
609
+ end
610
+
611
+ test '#dist_tag returns "latest" if the version does not contains a standard pre-release tag' do
612
+ assert_equal 'latest', @spec.dist_tag('1.0.0')
613
+ assert_equal 'latest', @spec.dist_tag('1.0.0-shopifyv4')
614
+ end
615
+
616
+ test '#dist_tag returns "next" if the version contains a pre-release tag' do
617
+ assert_equal 'next', @spec.dist_tag('1.0.0-alpha.1')
618
+ assert_equal 'next', @spec.dist_tag('1.0.0-beta')
619
+ assert_equal 'next', @spec.dist_tag('1.0.0-rc.3')
620
+ assert_equal 'next', @spec.dist_tag('1.0.0-next')
621
+ end
622
+
623
+ test '#dependencies_steps returns lerna setup if a `lerna.json` is present' do
624
+ @spec.expects(:lerna?).returns(true).at_least_once
625
+ assert_equal ['npm install --no-progress', 'node_modules/.bin/lerna bootstrap'], @spec.dependencies_steps
626
+ end
627
+
603
628
  test '#dependencies_steps returns `npm install` if a `package.json` is present' do
604
629
  @spec.expects(:npm?).returns(true).at_least_once
605
630
  assert_equal ['npm install --no-progress'], @spec.dependencies_steps
606
631
  end
607
632
 
633
+ test '#publish_lerna_packages checks if independent version tags exist, and then invokes lerna deploy script' do
634
+ @spec.stubs(:lerna?).returns(true)
635
+ @spec.stubs(:lerna_version).returns('independent')
636
+ assert_equal 'assert-lerna-independent-version-tags', @spec.deploy_steps[0]
637
+ assert_equal 'publish-lerna-independent-packages', @spec.deploy_steps[1]
638
+ end
639
+
640
+ test '#publish_lerna_packages checks if fixed version tag exists, and then invokes lerna deploy script' do
641
+ @spec.stubs(:lerna?).returns(true)
642
+ @spec.stubs(:lerna_version).returns('1.0.0')
643
+ assert_equal 'assert-lerna-fixed-version-tag', @spec.deploy_steps[0]
644
+ assert_equal 'node_modules/.bin/lerna publish --yes --skip-git --repo-version 1.0.0 --force-publish=* --npm-tag latest', @spec.deploy_steps[1]
645
+ end
646
+
647
+ test '#enforce_publish_config? is false when Shipit.enforce_publish_config is nil' do
648
+ Shipit.stubs(:enforce_publish_config).returns(nil)
649
+ refute @spec.enforce_publish_config?
650
+ end
651
+
652
+ test '#enforce_publish_config? is false when Shipit.enforce_publish_config is 0' do
653
+ Shipit.stubs(:enforce_publish_config).returns('0')
654
+ refute @spec.enforce_publish_config?
655
+ end
656
+
657
+ test '#enforce_publish_config? is true when Shipit.enforce_publish_config is 1' do
658
+ Shipit.stubs(:enforce_publish_config).returns('1')
659
+ assert @spec.enforce_publish_config?
660
+ end
661
+
662
+ test '#valid_publish_config? is false when enforce_publish_config? is true and publishConfig is missing from package.json' do
663
+ Shipit.stubs(:private_npm_registry).returns('some_private_registry')
664
+ @spec.stubs(:enforce_publish_config?).returns(true)
665
+ @spec.stubs(:publish_config_access).returns('restricted')
666
+
667
+ package_json = Pathname.new('/tmp/fake_package.json')
668
+ package_json.write('{"name": "foo"}')
669
+
670
+ @spec.expects(:package_json).returns(package_json)
671
+ refute @spec.valid_publish_config?
672
+ end
673
+
674
+ test '#valid_publish_config? is true when enforce_publish_config? is true and publishConfig.access is public' do
675
+ Shipit.stubs(:private_npm_registry).returns('some_private_registry')
676
+ @spec.stubs(:enforce_publish_config?).returns(true)
677
+ @spec.stubs(:publish_config_access).returns('public')
678
+ @spec.stubs(:publish_config).returns('something')
679
+
680
+ assert @spec.valid_publish_config?
681
+ end
682
+
683
+ test '#valid_publish_config? is true when shipit does not enforce a publishConfig' do
684
+ @spec.stubs(:lerna_version).returns('1.0.0')
685
+ assert @spec.valid_publish_config?
686
+ end
687
+
688
+ test '#publish_config returns publishConfig from package.json' do
689
+ package_json = Pathname.new('/tmp/fake_package.json')
690
+ package_json.write('{"publishConfig": "foo"}')
691
+
692
+ @spec.expects(:package_json).returns(package_json)
693
+ assert_equal "foo", @spec.publish_config
694
+ end
695
+
696
+ test '#valid_publish_config_access? is false when publishConfig.access is invalid' do
697
+ @spec.stubs(:publish_config_access).returns('foo')
698
+ refute @spec.valid_publish_config_access?
699
+ end
700
+
701
+ test '#valid_publish_config_access? is true when publishConfig.access is public or restricted' do
702
+ @spec.stubs(:publish_config_access).returns('public')
703
+ assert @spec.valid_publish_config_access?
704
+
705
+ @spec.stubs(:publish_config_access).returns('restricted')
706
+ assert @spec.valid_publish_config_access?
707
+ end
708
+
709
+ test '#publish_config_access is restricted when enforce_publish_config? is true and publishConfig is missing' do
710
+ package_json = Pathname.new('/tmp/fake_package.json')
711
+ package_json.write('{"name": "@shopify/foo"}')
712
+
713
+ @spec.stubs(:enforce_publish_config?).returns(true)
714
+ @spec.expects(:package_json).returns(package_json)
715
+ assert_equal 'restricted', @spec.publish_config_access
716
+ end
717
+
718
+ test '#publish_config_access is public when enforce_publish_config? is false and publishConfig is missing' do
719
+ package_json = Pathname.new('/tmp/fake_package.json')
720
+ package_json.write('{"name": "@shopify/foo"}')
721
+
722
+ @spec.stubs(:enforce_publish_config?).returns(false)
723
+ @spec.expects(:package_json).returns(package_json)
724
+ assert_equal 'public', @spec.publish_config_access
725
+ end
726
+
727
+ test '#publish_config_access returns publishConfig.access from package.json when enforce_publish_config? is true' do
728
+ package_json = Pathname.new('/tmp/fake_package.json')
729
+ package_json.write('{
730
+ "name": "@shopify/foo",
731
+ "publishConfig": {
732
+ "access": "foo"
733
+ }
734
+ }')
735
+
736
+ @spec.stubs(:enforce_publish_config?).returns(false)
737
+ @spec.expects(:package_json).returns(package_json)
738
+ assert_equal 'foo', @spec.publish_config_access
739
+ end
740
+
741
+ test "#scoped_package? is false when Shipit.npm_org_scope is not set and the package is private" do
742
+ Shipit.stubs(:npm_org_scope).returns(nil)
743
+ @spec.stubs(:publish_config_access).returns('restricted')
744
+ refute @spec.scoped_package?
745
+ end
746
+
747
+ test "#scoped_package? is true when Shipit.npm_org_scope is set and package_name starts with scope and the package is private" do
748
+ Shipit.stubs(:npm_org_scope).returns('@shopify')
749
+ @spec.stubs(:publish_config_access).returns('restricted')
750
+ @spec.stubs(:package_name).returns('@shopify/polaris')
751
+ assert @spec.scoped_package?
752
+ end
753
+
754
+ test "#private_scoped_package? is false when private packages are not scoped" do
755
+ @spec.stubs(:scoped_package?).returns(false)
756
+ @spec.stubs(:publish_config_access).returns("restricted")
757
+ refute @spec.private_scoped_package?
758
+ end
759
+
760
+ test "#private_scoped_package? is true when private packages are scoped" do
761
+ @spec.stubs(:scoped_package?).returns(true)
762
+ @spec.stubs(:publish_config_access).returns("restricted")
763
+ assert @spec.private_scoped_package?
764
+ end
765
+
608
766
  test '#publish_npm_package checks if version tag exists, and then invokes npm deploy script' do
609
767
  @spec.stubs(:npm?).returns(true)
610
- assert_equal ['assert-npm-version-tag', 'npm publish'], @spec.deploy_steps
768
+ @spec.stubs(:package_version).returns('1.0.0')
769
+ @spec.stubs(:valid_publish_config?).returns(true)
770
+ @spec.stubs(:publish_config_access).returns('restricted')
771
+ @spec.stubs(:registry).returns("@private:registry=some_private_registry")
772
+ assert_equal ['assert-npm-version-tag', 'npm publish --tag latest --access restricted'], @spec.deploy_steps
773
+ end
774
+
775
+ test '#npmrc_contents returns a scoped private package configuration when the package is scoped and private' do
776
+ registry = "@shopify:registry=some_private_registry"
777
+ Shipit.stubs(:npm_org_scope).returns('@shopify')
778
+ Shipit.stubs(:private_npm_registry).returns('some_private_registry')
779
+ @spec.stubs(:scoped_package?).returns(true)
780
+ @spec.stubs(:publish_config_access).returns('restricted')
781
+ assert_equal registry, @spec.registry
782
+ end
783
+
784
+ test '#npmrc_contents returns a public scoped package configuration when the package is scoped and public' do
785
+ registry = "@shopify:registry=https://registry.npmjs.org/"
786
+ Shipit.stubs(:npm_org_scope).returns('@shopify')
787
+ @spec.stubs(:scoped_package?).returns(true)
788
+ @spec.stubs(:publish_config_access).returns('public')
789
+ assert_equal registry, @spec.registry
790
+ end
791
+
792
+ test '#npmrc_contents returns a public non-scoped package configuration when the package is not scoped and public' do
793
+ registry = "registry=https://registry.npmjs.org/"
794
+ @spec.stubs(:scoped_package?).returns(false)
795
+ @spec.stubs(:publish_config_access).returns('public')
796
+ assert_equal registry, @spec.registry
797
+ end
798
+
799
+ test '#publish_lerna_packages guesses npm tag' do
800
+ @spec.stubs(:lerna?).returns(true)
801
+ @spec.stubs(:lerna_version).returns('1.0.0-alpha.1')
802
+ assert_match(/--npm-tag next/, @spec.deploy_steps.last)
803
+ end
804
+
805
+ test '#publish_npm_package checks if version tag and a pre-release flag exist, and then invokes npm deploy script' do
806
+ @spec.stubs(:npm?).returns(true)
807
+ @spec.stubs(:package_version).returns('1.0.0-alpha.1')
808
+ @spec.stubs(:valid_publish_config?).returns(true)
809
+ @spec.stubs(:publish_config_access).returns('restricted')
810
+ @spec.stubs(:registry).returns("@private:registry=some_private_registry")
811
+ assert_equal ['assert-npm-version-tag', 'npm publish --tag next --access restricted'], @spec.deploy_steps
611
812
  end
612
813
 
613
814
  test 'bundler installs take priority over yarn installs' do
@@ -663,14 +864,49 @@ module Shipit
663
864
  assert_equal ['yarn install --no-progress'], @spec.dependencies_steps
664
865
  end
665
866
 
666
- test '#publish_yarn_package checks if version tag exists, and then invokes npm publish script' do
867
+ test '#publish_npm_package checks if version tag exists, and then invokes npm publish script' do
667
868
  @spec.stubs(:yarn?).returns(true).at_least_once
668
- assert_equal ['assert-npm-version-tag', 'npm publish'], @spec.deploy_steps
869
+ @spec.stubs(:package_version).returns('1.0.0')
870
+ @spec.stubs(:valid_publish_config?).returns(true)
871
+ @spec.stubs(:publish_config_access).returns('restricted')
872
+ @spec.stubs(:registry).returns("@private:registry=some_private_registry")
873
+ assert_equal ['assert-npm-version-tag', 'npm publish --tag latest --access restricted'], @spec.deploy_steps
874
+ end
875
+
876
+ test '#publish_npm_package checks if version tag exists, generates npmrc, and then invokes npm publish script when enforce_publish_config? is true' do
877
+ @spec.stubs(:yarn?).returns(true).at_least_once
878
+ @spec.stubs(:package_version).returns('1.0.0')
879
+ @spec.stubs(:valid_publish_config?).returns(true)
880
+
881
+ @spec.stubs(:publish_config_access).returns('restricted')
882
+ @spec.stubs(:enforce_publish_config?).returns(true)
883
+ @spec.stubs(:registry).returns('fake')
884
+
885
+ generate_npmrc = 'generate-local-npmrc "fake"'
886
+ npm_publish = 'npm publish --tag latest --access restricted'
887
+ deploy_steps = ['assert-npm-version-tag', generate_npmrc, npm_publish]
888
+ assert_equal deploy_steps, @spec.deploy_steps
669
889
  end
670
890
 
671
891
  test 'yarn checklist takes precedence over npm checklist' do
672
892
  @spec.stubs(:yarn?).returns(true).at_least_once
673
893
  assert_match(/yarn version/, @spec.review_checklist[0])
674
894
  end
895
+
896
+ test "max_divergence_commits defaults to `nil" do
897
+ @spec.expects(:load_config).returns({})
898
+ assert_nil @spec.max_divergence_commits
899
+ end
900
+
901
+ test "max_divergence_age defaults to `nil` if `merge.max_divergence.age` cannot be parsed" do
902
+ @spec.expects(:load_config).returns(
903
+ 'merge' => {
904
+ 'max_divergence' => {
905
+ 'age' => 'badbadbad',
906
+ },
907
+ },
908
+ )
909
+ assert_nil @spec.max_divergence_age
910
+ end
675
911
  end
676
912
  end