shipit-engine 0.29.0 → 0.30.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -2
  3. data/app/assets/images/archive-solid.svg +1 -0
  4. data/app/assets/stylesheets/_pages/_stacks.scss +76 -0
  5. data/app/controllers/shipit/api/stacks_controller.rb +20 -1
  6. data/app/controllers/shipit/api_clients_controller.rb +49 -0
  7. data/app/controllers/shipit/merge_status_controller.rb +8 -4
  8. data/app/controllers/shipit/stacks_controller.rb +58 -9
  9. data/app/controllers/shipit/webhooks_controller.rb +2 -130
  10. data/app/helpers/shipit/stacks_helper.rb +4 -0
  11. data/app/jobs/shipit/background_job/unique.rb +3 -1
  12. data/app/jobs/shipit/continuous_delivery_job.rb +1 -0
  13. data/app/jobs/shipit/destroy_stack_job.rb +2 -2
  14. data/app/models/shipit/commit.rb +21 -9
  15. data/app/models/shipit/commit_deployment.rb +15 -11
  16. data/app/models/shipit/commit_deployment_status.rb +6 -2
  17. data/app/models/shipit/deploy.rb +48 -7
  18. data/app/models/shipit/deploy_stats.rb +57 -0
  19. data/app/models/shipit/repository.rb +38 -0
  20. data/app/models/shipit/stack.rb +41 -34
  21. data/app/models/shipit/task.rb +26 -4
  22. data/app/models/shipit/webhooks.rb +32 -0
  23. data/app/models/shipit/webhooks/handlers/check_suite_handler.rb +19 -0
  24. data/app/models/shipit/webhooks/handlers/handler.rb +40 -0
  25. data/app/models/shipit/webhooks/handlers/membership_handler.rb +45 -0
  26. data/app/models/shipit/webhooks/handlers/push_handler.rb +20 -0
  27. data/app/models/shipit/webhooks/handlers/status_handler.rb +26 -0
  28. data/app/serializers/shipit/stack_serializer.rb +6 -1
  29. data/app/validators/ascii_only_validator.rb +3 -3
  30. data/app/views/layouts/_head.html.erb +0 -0
  31. data/app/views/layouts/shipit.html.erb +4 -2
  32. data/app/views/shipit/api_clients/index.html.erb +36 -0
  33. data/app/views/shipit/api_clients/new.html.erb +33 -0
  34. data/app/views/shipit/api_clients/show.html.erb +35 -0
  35. data/app/views/shipit/merge_status/logged_out.erb +1 -1
  36. data/app/views/shipit/stacks/_header.html.erb +12 -7
  37. data/app/views/shipit/stacks/_links.html.erb +1 -0
  38. data/app/views/shipit/stacks/index.html.erb +7 -2
  39. data/app/views/shipit/stacks/settings.html.erb +19 -0
  40. data/app/views/shipit/stacks/statistics.html.erb +82 -0
  41. data/config/locales/en.yml +14 -2
  42. data/config/routes.rb +4 -0
  43. data/db/migrate/20191209231045_create_shipit_repositories.rb +12 -0
  44. data/db/migrate/20191209231307_add_repository_reference_to_stacks.rb +15 -0
  45. data/db/migrate/20191216162728_backfill_repository_data.rb +22 -0
  46. data/db/migrate/20191216163010_remove_repository_information_from_stacks.rb +20 -0
  47. data/db/migrate/20191219205202_add_archived_since_to_stacks.rb +6 -0
  48. data/db/migrate/20200102175621_optional_task_commits.rb +6 -0
  49. data/db/migrate/20200109132519_add_sha_to_commit_deployments.rb +5 -0
  50. data/lib/shipit/github_app.rb +32 -3
  51. data/lib/shipit/task_commands.rb +10 -2
  52. data/lib/shipit/version.rb +1 -1
  53. data/test/controllers/api/ccmenu_controller_test.rb +1 -1
  54. data/test/controllers/api/stacks_controller_test.rb +14 -6
  55. data/test/controllers/api_clients_controller_test.rb +103 -0
  56. data/test/controllers/merge_status_controller_test.rb +21 -4
  57. data/test/controllers/stacks_controller_test.rb +35 -0
  58. data/test/controllers/webhooks_controller_test.rb +26 -0
  59. data/test/dummy/config/environments/development.rb +22 -4
  60. data/test/dummy/db/schema.rb +17 -6
  61. data/test/dummy/db/seeds.rb +20 -6
  62. data/test/fixtures/shipit/commit_deployment_statuses.yml +4 -4
  63. data/test/fixtures/shipit/commit_deployments.yml +8 -8
  64. data/test/fixtures/shipit/commits.yml +23 -0
  65. data/test/fixtures/shipit/repositories.yml +23 -0
  66. data/test/fixtures/shipit/stacks.yml +100 -16
  67. data/test/fixtures/shipit/tasks.yml +66 -3
  68. data/test/jobs/destroy_stack_job_test.rb +9 -0
  69. data/test/models/commit_deployment_status_test.rb +33 -4
  70. data/test/models/commit_deployment_test.rb +8 -11
  71. data/test/models/commits_test.rb +22 -2
  72. data/test/models/deploy_stats_test.rb +112 -0
  73. data/test/models/deploys_test.rb +55 -17
  74. data/test/models/pull_request_test.rb +1 -1
  75. data/test/models/shipit/repository_test.rb +76 -0
  76. data/test/models/shipit/wehbooks/handlers_test.rb +26 -0
  77. data/test/models/stacks_test.rb +44 -51
  78. data/test/models/undeployed_commits_test.rb +13 -0
  79. data/test/test_helper.rb +3 -1
  80. data/test/unit/deploy_commands_test.rb +9 -0
  81. data/test/unit/github_app_test.rb +136 -0
  82. metadata +161 -128
@@ -0,0 +1,103 @@
1
+
2
+ require 'test_helper'
3
+
4
+ module Shipit
5
+ class ApiClientsControllerTest < ActionController::TestCase
6
+ setup do
7
+ @routes = Shipit::Engine.routes
8
+ @api_client = shipit_api_clients(:here_come_the_walrus)
9
+ session[:user_id] = shipit_users(:walrus).id
10
+ end
11
+
12
+ test "GitHub authentication is mandatory" do
13
+ session[:user_id] = nil
14
+ get :index
15
+ assert_redirected_to '/github/auth/github?origin=http%3A%2F%2Ftest.host%2Fapi_clients'
16
+ end
17
+
18
+ test "current_user must be a member of at least a Shipit.github_teams" do
19
+ session[:user_id] = shipit_users(:bob).id
20
+ Shipit.stubs(:github_teams).returns([shipit_teams(:cyclimse_cooks), shipit_teams(:shopify_developers)])
21
+ get :index
22
+ assert_response :forbidden
23
+ assert_equal(
24
+ 'You must be a member of cyclimse/cooks or shopify/developers to access this application.',
25
+ response.body,
26
+ )
27
+ end
28
+
29
+ test "#index is successful with a valid user" do
30
+ get :index
31
+ assert_response :ok
32
+ end
33
+
34
+ test "#new is success" do
35
+ get :new
36
+ assert_response :ok
37
+ end
38
+
39
+ test "#create creates a new api_client" do
40
+ assert_difference "ApiClient.count", +1 do
41
+ post :create, params: {
42
+ api_client: {
43
+ name: 'walrus_app',
44
+ permissions: [
45
+ 'read:stack',
46
+ 'lock:stack',
47
+ ],
48
+ },
49
+ }
50
+ end
51
+
52
+ assert_redirected_to api_client_path(ApiClient.last)
53
+ end
54
+
55
+ test "#create attaches the current user to the created api client" do
56
+ post :create, params: {
57
+ api_client: {
58
+ name: 'walrus_app',
59
+ permissions: [
60
+ 'read:stack',
61
+ 'lock:stack',
62
+ ],
63
+ },
64
+ }
65
+
66
+ assert_equal shipit_users(:walrus).id, ApiClient.last.creator.id
67
+ end
68
+
69
+ test "#create when not valid renders new" do
70
+ assert_no_difference "Stack.count" do
71
+ post :create, params: {api_client: {name: 'walrus_app', permissions: ['invalid']}}
72
+ end
73
+
74
+ assert flash[:warning]
75
+ assert_response :success
76
+ end
77
+
78
+ test "#show is success" do
79
+ get :show, params: {id: @api_client.id}
80
+ assert_response :ok
81
+ end
82
+
83
+ test "#update updates an existing api_client" do
84
+ new_permissions = [
85
+ 'read:stack',
86
+ 'lock:stack',
87
+ ]
88
+
89
+ assert_difference "ApiClient.count", 0 do
90
+ patch :update, params: {
91
+ id: @api_client.id,
92
+ api_client: {
93
+ permissions: new_permissions,
94
+ },
95
+ }
96
+ end
97
+ @api_client.reload
98
+
99
+ assert_redirected_to api_client_path(@api_client)
100
+ assert_equal new_permissions, @api_client.permissions
101
+ end
102
+ end
103
+ end
@@ -37,13 +37,11 @@ module Shipit
37
37
  test "GET show prefers stacks with merge_queue_enabled" do
38
38
  existing = shipit_stacks(:shipit)
39
39
  Shipit::Stack.where(
40
- repo_owner: existing.repo_owner,
41
- repo_name: existing.repo_name,
40
+ repository: existing.repository,
42
41
  ).update_all(merge_queue_enabled: false)
43
42
 
44
43
  Shipit::Stack.create(
45
- repo_owner: existing.repo_owner,
46
- repo_name: existing.repo_name,
44
+ repository: existing.repository,
47
45
  environment: 'foo',
48
46
  branch: existing.branch,
49
47
  merge_queue_enabled: true,
@@ -53,5 +51,24 @@ module Shipit
53
51
  assert_response :ok
54
52
  assert_includes response.body, 'shipit-engine/foo'
55
53
  end
54
+
55
+ test "GET show prefers locked stacks above all else" do
56
+ existing = shipit_stacks(:shipit)
57
+ Shipit::Stack.where(
58
+ repository: existing.repository,
59
+ ).update_all(lock_reason: 'testing', merge_queue_enabled: false, locked_since: Time.now.utc)
60
+
61
+ # Shipit would otherwise prefer this, because it has the merge queue enabled
62
+ Shipit::Stack.create(
63
+ repository: existing.repository,
64
+ environment: 'foo',
65
+ branch: existing.branch,
66
+ merge_queue_enabled: true,
67
+ )
68
+
69
+ get :show, params: {referrer: 'https://github.com/Shopify/shipit-engine/pull/42', branch: 'master'}
70
+ assert_response :ok
71
+ assert_includes response.body, 'locked'
72
+ end
56
73
  end
57
74
  end
@@ -110,6 +110,17 @@ module Shipit
110
110
  assert_response :success
111
111
  end
112
112
 
113
+ test "#statistics is success" do
114
+ get :statistics, params: {id: @stack.to_param}
115
+ assert_response :success
116
+ end
117
+
118
+ test "#statistics redirects to #show if no deploys are present" do
119
+ @stack.deploys.destroy_all
120
+ get :statistics, params: {id: @stack.to_param}
121
+ assert_redirected_to stack_path(@stack)
122
+ end
123
+
113
124
  test "#update allows to lock the stack" do
114
125
  refute @stack.locked?
115
126
 
@@ -129,6 +140,30 @@ module Shipit
129
140
  assert_instance_of AnonymousUser, @stack.lock_author
130
141
  end
131
142
 
143
+ test "#update allows to archive the stack" do
144
+ refute @stack.archived?
145
+ refute @stack.locked?
146
+
147
+ patch :update, params: {id: @stack.to_param, stack: {archived: "true"}}
148
+ @stack.reload
149
+ assert @stack.archived?
150
+ assert @stack.locked?
151
+ assert_equal shipit_users(:walrus), @stack.lock_author
152
+ assert_equal "Archived", @stack.lock_reason
153
+ end
154
+
155
+ test "#update allows to dearchive the stack" do
156
+ @stack.archive!(shipit_users(:walrus))
157
+ assert @stack.locked?
158
+ assert @stack.archived?
159
+
160
+ patch :update, params: {id: @stack.to_param, stack: {archived: "false"}}
161
+ @stack.reload
162
+ refute @stack.archived?
163
+ refute @stack.locked?
164
+ assert_instance_of AnonymousUser, @stack.lock_author
165
+ end
166
+
132
167
  test "#refresh queues a RefreshStatusesJob and a GithubSyncJob" do
133
168
  request.env['HTTP_REFERER'] = stack_settings_path(@stack)
134
169
 
@@ -7,6 +7,17 @@ module Shipit
7
7
  GithubHook.any_instance.stubs(:verify_signature).returns(true)
8
8
  end
9
9
 
10
+ test "create github repository which is not yet present in the datastore" do
11
+ request.headers['X-Github-Event'] = 'push'
12
+ unknown_repo_payload = JSON.parse(payload(:push_master))
13
+ unknown_repo_payload["repository"]["full_name"] = "owner/unknown-repository"
14
+ unknown_repo_payload = unknown_repo_payload.to_json
15
+
16
+ assert_nothing_raised do
17
+ post :create, body: unknown_repo_payload, as: :json
18
+ end
19
+ end
20
+
10
21
  test ":push with the target branch queues a GithubSyncJob" do
11
22
  request.headers['X-Github-Event'] = 'push'
12
23
 
@@ -142,8 +153,23 @@ module Shipit
142
153
  end
143
154
  end
144
155
 
156
+ test "other events trigger custom handlers" do
157
+ event = 'pull_request'
158
+ mock_handler = mock
159
+ mock_handler.expects(:call).with(pull_request_params.stringify_keys).once
160
+ Shipit::Webhooks.register_handler(event, mock_handler)
161
+
162
+ @request.headers['X-Github-Event'] = event
163
+ post :create, body: pull_request_params.to_json, as: :json
164
+ assert_response :ok
165
+ end
166
+
145
167
  private
146
168
 
169
+ def pull_request_params
170
+ {action: 'opened', number: 2, pull_request: 'foobar'}
171
+ end
172
+
147
173
  def membership_params
148
174
  {action: 'added', team: team_params, organization: {login: 'shopify'}, member: {login: 'walrus'}}
149
175
  end
@@ -9,9 +9,24 @@ Rails.application.configure do
9
9
  # Do not eager load code on boot.
10
10
  config.eager_load = false
11
11
 
12
- # Show full error reports and disable caching.
13
- config.consider_all_requests_local = true
14
- config.action_controller.perform_caching = false
12
+ # Show full error reports
13
+ config.consider_all_requests_local = true
14
+
15
+ # Enable/disable caching. By default caching is disabled.
16
+ # Run rails dev:cache to toggle caching.
17
+ if Shipit::Engine.root.join("tmp", "caching-dev.txt").exist?
18
+ config.action_controller.perform_caching = true
19
+ config.action_controller.enable_fragment_cache_logging = true
20
+
21
+ config.cache_store = :memory_store
22
+ config.public_file_server.headers = {
23
+ "Cache-Control" => "public, max-age=#{2.days.to_i}",
24
+ }
25
+ else
26
+ config.action_controller.perform_caching = false
27
+
28
+ config.cache_store = :null_store
29
+ end
15
30
 
16
31
  # Don't care if the mailer can't send.
17
32
  config.action_mailer.raise_delivery_errors = false
@@ -39,8 +54,11 @@ Rails.application.configure do
39
54
  # Raises error for missing translations
40
55
  # config.action_view.raise_on_missing_translations = true
41
56
  if Rails.application.config_for(:database)['adapter'] == 'sqlite3'
42
- Shipit::DeferredTouch.enabled = false
57
+ config.to_prepare do
58
+ Shipit::DeferredTouch.enabled = false
59
+ end
43
60
  end
61
+
44
62
  config.active_job.queue_adapter = :async
45
63
 
46
64
  Pubsubstub.use_persistent_connections = false
@@ -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: 2019_05_02_020249) do
13
+ ActiveRecord::Schema.define(version: 2020_01_09_132519) do
14
14
 
15
15
  create_table "api_clients", force: :cascade do |t|
16
16
  t.text "permissions", limit: 65535
@@ -55,6 +55,7 @@ ActiveRecord::Schema.define(version: 2019_05_02_020249) do
55
55
  t.string "api_url"
56
56
  t.datetime "created_at", null: false
57
57
  t.datetime "updated_at", null: false
58
+ t.string "sha", limit: 40
58
59
  t.index ["commit_id", "task_id"], name: "index_commit_deployments_on_commit_id_and_task_id", unique: true
59
60
  t.index ["task_id"], name: "index_commit_deployments_on_task_id"
60
61
  end
@@ -190,9 +191,15 @@ ActiveRecord::Schema.define(version: 2019_05_02_020249) do
190
191
  t.index ["user_id"], name: "index_deploy_statuses_on_user_id"
191
192
  end
192
193
 
194
+ create_table "repositories", force: :cascade do |t|
195
+ t.string "owner", limit: 39, null: false
196
+ t.string "name", limit: 100, null: false
197
+ t.datetime "created_at", precision: 6, null: false
198
+ t.datetime "updated_at", precision: 6, null: false
199
+ t.index ["owner", "name"], name: "repository_unicity", unique: true
200
+ end
201
+
193
202
  create_table "stacks", force: :cascade do |t|
194
- t.string "repo_name", limit: 100, null: false
195
- t.string "repo_owner", limit: 39, null: false
196
203
  t.string "environment", limit: 50, default: "production", null: false
197
204
  t.datetime "created_at"
198
205
  t.datetime "updated_at"
@@ -211,7 +218,11 @@ ActiveRecord::Schema.define(version: 2019_05_02_020249) do
211
218
  t.datetime "locked_since"
212
219
  t.boolean "merge_queue_enabled", default: false, null: false
213
220
  t.datetime "last_deployed_at"
214
- t.index ["repo_owner", "repo_name", "environment"], name: "stack_unicity", unique: true
221
+ t.integer "repository_id", null: false
222
+ t.datetime "archived_since"
223
+ t.index ["archived_since"], name: "index_stacks_on_archived_since"
224
+ t.index ["repository_id", "environment"], name: "stack_unicity", unique: true
225
+ t.index ["repository_id"], name: "index_stacks_on_repository_id"
215
226
  end
216
227
 
217
228
  create_table "statuses", force: :cascade do |t|
@@ -228,8 +239,8 @@ ActiveRecord::Schema.define(version: 2019_05_02_020249) do
228
239
 
229
240
  create_table "tasks", force: :cascade do |t|
230
241
  t.integer "stack_id", limit: 4, null: false
231
- t.integer "since_commit_id", limit: 4, null: false
232
- t.integer "until_commit_id", limit: 4, null: false
242
+ t.integer "since_commit_id", limit: 4
243
+ t.integer "until_commit_id", limit: 4
233
244
  t.string "status", limit: 10, default: "pending", null: false
234
245
  t.datetime "created_at"
235
246
  t.datetime "updated_at"
@@ -14,6 +14,8 @@ module Shipit
14
14
  Commit.send(:define_method, :refresh_statuses!) {}
15
15
  Commit.send(:define_method, :refresh_check_runs!) {}
16
16
  ReleaseStatus.send(:define_method, :create_status_on_github!) {}
17
+ CommitDeployment.send(:define_method, :schedule_create_on_github) {}
18
+ CommitDeploymentStatus.send(:define_method, :schedule_create_on_github) {}
17
19
 
18
20
  users = 3.times.map do
19
21
  User.create!(
@@ -25,8 +27,10 @@ module Shipit
25
27
 
26
28
  stacks = 3.times.map do
27
29
  Stack.create!(
28
- repo_name: Faker::Internet.domain_name.parameterize,
29
- repo_owner: Faker::Company.name.parameterize,
30
+ repository: Repository.find_or_create_by(
31
+ name: Faker::Internet.domain_name.parameterize,
32
+ owner: Faker::Company.name.parameterize
33
+ ),
30
34
  deploy_url: "https://#{Faker::Internet.domain_name.parameterize}.#{Faker::Internet.domain_suffix}/",
31
35
  cached_deploy_spec: DeploySpec.load(%(
32
36
  {
@@ -195,9 +199,19 @@ module Shipit
195
199
  )
196
200
  write_output(rollback)
197
201
 
198
- task = stack.tasks.create!(
199
- since_commit_id: stack.last_deployed_commit.id,
200
- until_commit_id: stack.last_deployed_commit.id,
202
+ provision_task = stack.tasks.create!(
203
+ status: "pending",
204
+ user: users.sample,
205
+ definition: TaskDefinition.new('provision',
206
+ 'action' => 'Provision some resources',
207
+ 'description' => 'Provisions servers for the application to be deployed to',
208
+ 'steps' => ['./provision.sh'],
209
+ ),
210
+ started_at: Random.rand(15.minutes.to_i).seconds.ago,
211
+ )
212
+ write_output(provision_task)
213
+
214
+ restart_task = stack.tasks.create!(
201
215
  status: "success",
202
216
  user: users.sample,
203
217
  definition: TaskDefinition.new('restart',
@@ -208,6 +222,6 @@ module Shipit
208
222
  started_at: Random.rand(15.minutes.to_i).seconds.ago,
209
223
  ended_at: Time.now.utc,
210
224
  )
211
- write_output(rollback)
225
+ write_output(restart_task)
212
226
  end
213
227
  end
@@ -1,6 +1,6 @@
1
- shipit_deploy_second_pending:
1
+ shipit_deploy_second_in_progress:
2
2
  commit_deployment: shipit_deploy_second
3
- status: pending
3
+ status: in_progress
4
4
  github_id: 42
5
5
  api_url: https://api.github.com/repos/shopify/shipit-engine/deployments/1/statuses/42
6
6
 
@@ -10,9 +10,9 @@ shipit_deploy_second_success:
10
10
  github_id: 43
11
11
  api_url: https://api.github.com/repos/shopify/shipit-engine/deployments/1/statuses/43
12
12
 
13
- shipit2_deploy_third_pending:
13
+ shipit2_deploy_third_in_progress:
14
14
  commit_deployment: shipit2_deploy_third
15
- status: pending
15
+ status: in_progress
16
16
 
17
17
  shipit2_deploy_third_failure:
18
18
  commit_deployment: shipit2_deploy_third
@@ -1,37 +1,37 @@
1
1
  shipit_deploy_second:
2
- commit_id: 2 # second
2
+ sha: f890fd8b5f2be05d1fedb763a3605ee461c39074 # second
3
3
  task_id: 1 # shipit
4
4
  api_url: https://api.github.com/repos/shopify/shipit-engine/deployments/1
5
5
  github_id: 1
6
6
 
7
7
  shipit2_deploy_third:
8
- commit_id: 3 # third
8
+ sha: 367578b362bf2b4df5903e1c7960929361c39074 # third
9
9
  task_id: 2 # shipit2
10
10
  api_url: https://api.github.com/repos/shopify/shipit-engine/deployments/2
11
11
  github_id: 2
12
12
 
13
13
  shipit_pending_third:
14
- commit_id: 3 # third
14
+ sha: 367578b362bf2b4df5903e1c7960929361c39074 # third
15
15
  task_id: 4 # shipit_pending
16
16
  api_url: https://api.github.com/repos/shopify/shipit-engine/deployments/3
17
17
  github_id: 3
18
18
 
19
19
  shipit_pending_fourth:
20
- commit_id: 4 # fourth
20
+ sha: 467578b362bf2b4df5903e1c7960929361c3435a # fourth
21
21
  task_id: 4 # shipit_pending
22
22
 
23
23
  shipit_running_fourth:
24
- commit_id: 4 # fourth
24
+ sha: 467578b362bf2b4df5903e1c7960929361c3435a # fourth
25
25
  task_id: 5 # shipit_running
26
26
 
27
27
  shipit_complete_fourth:
28
- commit_id: 4 # fourth
28
+ sha: 467578b362bf2b4df5903e1c7960929361c3435a # fourth
29
29
  task_id: 6 # shipit_complete
30
30
 
31
31
  shipit_aborted_fourth:
32
- commit_id: 4 # fourth
32
+ sha: 467578b362bf2b4df5903e1c7960929361c3435a # fourth
33
33
  task_id: 7 # shipit_aborted
34
34
 
35
35
  shipit_rollback_fourth:
36
- commit_id: 4 # fourth
36
+ sha: 467578b362bf2b4df5903e1c7960929361c3435a # fourth
37
37
  task_id: 8 # shipit_rollback