shipit-engine 0.32.0 → 0.35.1
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.
- checksums.yaml +4 -4
- data/README.md +13 -2
- data/app/assets/images/magic-solid.svg +1 -0
- data/app/assets/javascripts/shipit/repositories_search.js.coffee +60 -0
- data/app/assets/javascripts/shipit/{search.js.coffee → stack_search.js.coffee} +0 -0
- data/app/assets/stylesheets/_pages/_deploy.scss +2 -3
- data/app/assets/stylesheets/_pages/_repositories.scss +148 -0
- data/app/assets/stylesheets/_pages/_stacks.scss +19 -0
- data/app/assets/stylesheets/shipit.scss +1 -0
- data/app/controllers/shipit/api/ccmenu_controller.rb +1 -1
- data/app/controllers/shipit/api/deploys_controller.rb +2 -0
- data/app/controllers/shipit/api/{pull_requests_controller.rb → merge_requests_controller.rb} +8 -8
- data/app/controllers/shipit/api/rollbacks_controller.rb +2 -1
- data/app/controllers/shipit/api/stacks_controller.rb +15 -1
- data/app/controllers/shipit/deploys_controller.rb +1 -1
- data/app/controllers/shipit/merge_requests_controller.rb +31 -0
- data/app/controllers/shipit/merge_status_controller.rb +15 -15
- data/app/controllers/shipit/repositories_controller.rb +74 -0
- data/app/controllers/shipit/stacks_controller.rb +2 -2
- data/app/controllers/shipit/tasks_controller.rb +2 -2
- data/app/controllers/shipit/webhooks_controller.rb +23 -4
- data/app/helpers/shipit/chunks_helper.rb +2 -2
- data/app/helpers/shipit/github_url_helper.rb +8 -0
- data/app/helpers/shipit/shipit_helper.rb +0 -1
- data/app/helpers/shipit/stacks_helper.rb +4 -0
- data/app/jobs/shipit/create_on_github_job.rb +1 -0
- data/app/jobs/shipit/deliver_hook_job.rb +1 -1
- data/app/jobs/shipit/destroy_repository_job.rb +24 -0
- data/app/jobs/shipit/destroy_stack_job.rb +2 -2
- data/app/jobs/shipit/github_sync_job.rb +13 -9
- data/app/jobs/shipit/perform_task_job.rb +4 -98
- data/app/jobs/shipit/process_merge_requests_job.rb +32 -0
- data/app/jobs/shipit/refresh_merge_request_job.rb +11 -0
- data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +1 -1
- data/app/models/shipit/anonymous_user.rb +10 -2
- data/app/models/shipit/check_run.rb +38 -2
- data/app/models/shipit/command_line_user.rb +4 -0
- data/app/models/shipit/commit.rb +31 -20
- data/app/models/shipit/commit_checks.rb +14 -13
- data/app/models/shipit/commit_deployment.rb +3 -3
- data/app/models/shipit/commit_deployment_status.rb +3 -3
- data/app/models/shipit/deploy.rb +17 -11
- data/app/models/shipit/deploy_spec/file_system.rb +11 -5
- data/app/models/shipit/deploy_spec/lerna_discovery.rb +12 -4
- data/app/models/shipit/deploy_spec.rb +16 -4
- data/app/models/shipit/duration.rb +2 -0
- data/app/models/shipit/hook.rb +28 -2
- data/app/models/shipit/merge_request.rb +304 -0
- data/app/models/shipit/provisioning_handler/base.rb +30 -0
- data/app/models/shipit/provisioning_handler/unregistered_provisioning_handler.rb +35 -0
- data/app/models/shipit/provisioning_handler.rb +32 -0
- data/app/models/shipit/pull_request.rb +26 -265
- data/app/models/shipit/pull_request_assignment.rb +10 -0
- data/app/models/shipit/release_status.rb +1 -1
- data/app/models/shipit/repository.rb +63 -3
- data/app/models/shipit/review_stack.rb +130 -0
- data/app/models/shipit/review_stack_provisioning_queue.rb +39 -0
- data/app/models/shipit/rollback.rb +5 -0
- data/app/models/shipit/stack.rb +78 -30
- data/app/models/shipit/status/group.rb +1 -1
- data/app/models/shipit/task.rb +62 -9
- data/app/models/shipit/task_execution_strategy/base.rb +20 -0
- data/app/models/shipit/task_execution_strategy/default.rb +109 -0
- data/app/models/shipit/team.rb +4 -2
- data/app/models/shipit/user.rb +10 -1
- data/app/models/shipit/webhooks/handlers/pull_request/assigned_handler.rb +74 -0
- data/app/models/shipit/webhooks/handlers/pull_request/closed_handler.rb +68 -0
- data/app/models/shipit/webhooks/handlers/pull_request/edited_handler.rb +74 -0
- data/app/models/shipit/webhooks/handlers/pull_request/label_capturing_handler.rb +127 -0
- data/app/models/shipit/webhooks/handlers/pull_request/labeled_handler.rb +106 -0
- data/app/models/shipit/webhooks/handlers/pull_request/opened_handler.rb +83 -0
- data/app/models/shipit/webhooks/handlers/pull_request/reopened_handler.rb +88 -0
- data/app/models/shipit/webhooks/handlers/pull_request/review_stack_adapter.rb +103 -0
- data/app/models/shipit/webhooks/handlers/pull_request/unlabeled_handler.rb +107 -0
- data/app/models/shipit/webhooks/handlers/push_handler.rb +4 -1
- data/app/models/shipit/webhooks.rb +10 -0
- data/app/serializers/shipit/deploy_serializer.rb +6 -0
- data/app/serializers/shipit/merge_request_serializer.rb +21 -0
- data/app/serializers/shipit/pull_request_serializer.rb +5 -8
- data/app/serializers/shipit/review_stack_serializer.rb +7 -0
- data/app/serializers/shipit/stack_serializer.rb +7 -6
- data/app/serializers/shipit/tail_task_serializer.rb +10 -2
- data/app/serializers/shipit/task_serializer.rb +1 -1
- data/app/validators/subset_validator.rb +1 -1
- data/app/views/layouts/merge_status.html.erb +1 -1
- data/app/views/shipit/merge_requests/_merge_request.html.erb +29 -0
- data/app/views/shipit/{pull_requests → merge_requests}/index.html.erb +2 -2
- data/app/views/shipit/merge_requests/merge_requests/_pull_request.html.erb +29 -0
- data/app/views/shipit/merge_requests/merge_requests/index.html.erb +20 -0
- data/app/views/shipit/merge_status/_merge_queue_button.html.erb +3 -3
- data/app/views/shipit/merge_status/backlogged.html.erb +1 -1
- data/app/views/shipit/merge_status/failure.html.erb +1 -1
- data/app/views/shipit/merge_status/locked.html.erb +1 -1
- data/app/views/shipit/merge_status/success.html.erb +2 -2
- data/app/views/shipit/repositories/_header.html.erb +19 -0
- data/app/views/shipit/repositories/index.html.erb +31 -0
- data/app/views/shipit/repositories/new.html.erb +23 -0
- data/app/views/shipit/repositories/settings.html.erb +53 -0
- data/app/views/shipit/repositories/show.html.erb +30 -0
- data/app/views/shipit/stacks/_banners.html.erb +15 -1
- data/app/views/shipit/stacks/_header.html.erb +5 -2
- data/app/views/shipit/stacks/_stack.html.erb +8 -0
- data/app/views/shipit/stacks/index.html.erb +2 -1
- data/app/views/shipit/stacks/new.html.erb +1 -1
- data/app/views/shipit/stacks/settings.html.erb +5 -5
- data/app/views/shipit/stacks/show.html.erb +1 -1
- data/app/views/shipit/tasks/_task_output.html.erb +1 -1
- data/config/routes.rb +15 -5
- data/config/secrets.development.example.yml +24 -0
- data/config/secrets.development.shopify.yml +20 -9
- data/db/migrate/20200706145406_add_review_stacks.rb +12 -0
- data/db/migrate/20200804144639_rename_pull_request_to_merge_request.rb +7 -0
- data/db/migrate/20200804161512_rename_commits_pull_request_id_to_merge_request_id.rb +5 -0
- data/db/migrate/20200813134712_recreate_shipit_pull_requests.rb +22 -0
- data/db/migrate/20200813194056_create_pull_request_assignments.rb +8 -0
- data/db/migrate/20201001125502_add_provision_pr_stacks_flag_to_repositories.rb +7 -0
- data/db/migrate/20201008145809_add_retry_attempt_to_tasks.rb +5 -0
- data/db/migrate/20201008152744_add_max_retries_to_tasks.rb +5 -0
- data/db/migrate/20210325194053_remove_stacks_branch_default.rb +5 -0
- data/db/migrate/20210504200438_add_github_updated_at_to_check_runs.rb +5 -0
- data/db/migrate/20210823075617_change_check_runs_github_updated_at_default.rb +5 -0
- data/lib/shipit/command.rb +7 -6
- data/lib/shipit/commands.rb +18 -5
- data/lib/shipit/engine.rb +2 -0
- data/lib/shipit/flock.rb +8 -1
- data/lib/shipit/github_app.rb +8 -6
- data/lib/shipit/octokit_iterator.rb +3 -3
- data/lib/shipit/review_stack_commands.rb +8 -0
- data/lib/shipit/simple_message_verifier.rb +2 -2
- data/lib/shipit/stack_commands.rb +36 -7
- data/lib/shipit/task_commands.rb +8 -1
- data/lib/shipit/version.rb +1 -1
- data/lib/shipit.rb +50 -16
- data/lib/snippets/publish-lerna-independent-packages +35 -34
- data/lib/snippets/publish-lerna-independent-packages-legacy +39 -0
- data/lib/tasks/cron.rake +11 -2
- data/test/controllers/api/ccmenu_controller_test.rb +1 -1
- data/test/controllers/api/deploys_controller_test.rb +17 -0
- data/test/controllers/api/{pull_requests_controller_test.rb → merge_requests_controller_test.rb} +12 -12
- data/test/controllers/api/outputs_controller_test.rb +1 -0
- data/test/controllers/api/rollback_controller_test.rb +1 -1
- data/test/controllers/api/stacks_controller_test.rb +42 -8
- data/test/controllers/{pull_requests_controller_test.rb → merge_requests_controller_test.rb} +6 -6
- data/test/controllers/repositories_controller_test.rb +71 -0
- data/test/controllers/stacks_controller_test.rb +9 -1
- data/test/controllers/tasks_controller_test.rb +14 -2
- data/test/controllers/webhooks_controller_test.rb +27 -12
- data/test/dummy/app/assets/config/manifest.js +3 -0
- data/test/dummy/config/application.rb +7 -2
- data/test/dummy/config/database.yml +9 -0
- data/test/dummy/config/environments/development.rb +1 -4
- data/test/dummy/config/environments/test.rb +0 -5
- data/test/dummy/config/secrets_double_github_app.yml +79 -0
- data/test/dummy/db/schema.rb +56 -17
- data/test/dummy/db/seeds.rb +2 -1
- data/test/fixtures/payloads/check_suite_master.json +4 -32
- data/test/fixtures/payloads/invalid_pull_request.json +117 -0
- data/test/fixtures/payloads/provision_disabled_pull_request.json +454 -0
- data/test/fixtures/payloads/pull_request_assigned.json +480 -0
- data/test/fixtures/payloads/pull_request_closed.json +454 -0
- data/test/fixtures/payloads/pull_request_labeled.json +461 -0
- data/test/fixtures/payloads/pull_request_opened.json +454 -0
- data/test/fixtures/payloads/pull_request_reopened.json +454 -0
- data/test/fixtures/payloads/pull_request_unlabeled.json +454 -0
- data/test/fixtures/payloads/pull_request_with_no_repo.json +454 -0
- data/test/fixtures/payloads/push_master.json +1 -1
- data/test/fixtures/payloads/push_not_master.json +1 -1
- data/test/fixtures/shipit/commits.yml +17 -4
- data/test/fixtures/shipit/hooks.yml +1 -0
- data/test/fixtures/shipit/merge_requests.yml +141 -0
- data/test/fixtures/shipit/pull_request_assignments.yml +3 -0
- data/test/fixtures/shipit/pull_requests.yml +10 -131
- data/test/fixtures/shipit/repositories.yml +1 -0
- data/test/fixtures/shipit/stacks.yml +145 -0
- data/test/fixtures/shipit/statuses.yml +9 -0
- data/test/fixtures/shipit/tasks.yml +4 -1
- data/test/fixtures/shipit/users.yml +7 -0
- data/test/helpers/json_helper.rb +5 -1
- data/test/helpers/payloads_helper.rb +4 -0
- data/test/jobs/chunk_rollup_job_test.rb +15 -1
- data/test/jobs/destroy_repository_job_test.rb +27 -0
- data/test/jobs/github_sync_job_test.rb +2 -1
- data/test/jobs/perform_task_job_test.rb +8 -8
- data/test/jobs/{merge_pull_requests_job_test.rb → process_merge_requests_job_test.rb} +18 -18
- data/test/lib/shipit/deploy_commands_test.rb +16 -0
- data/test/lib/shipit/task_commands_test.rb +17 -0
- data/test/models/commit_deployment_status_test.rb +3 -3
- data/test/models/commits_test.rb +24 -13
- data/test/models/deploy_spec_test.rb +64 -24
- data/test/models/deploys_test.rb +188 -14
- data/test/models/hook_test.rb +30 -1
- data/test/models/{pull_request_test.rb → merge_request_test.rb} +49 -34
- data/test/models/pull_request_assignment_test.rb +16 -0
- data/test/models/shipit/check_run_test.rb +124 -5
- data/test/models/shipit/provisioning_handler/base_test.rb +33 -0
- data/test/models/shipit/provisioning_handler/unregistered_provisioning_handler_test.rb +49 -0
- data/test/models/shipit/provisioning_handler_test.rb +64 -0
- data/test/models/shipit/pull_request_test.rb +52 -0
- data/test/models/shipit/repository_test.rb +5 -1
- data/test/models/shipit/review_stack_provision_status_test.rb +77 -0
- data/test/models/shipit/review_stack_provisioning_queue_test.rb +63 -0
- data/test/models/shipit/review_stack_test.rb +91 -0
- data/test/models/{stacks_test.rb → shipit/stacks_test.rb} +52 -8
- data/test/models/shipit/webhooks/handlers/pull_request/assigned_handler_test.rb +45 -0
- data/test/models/shipit/webhooks/handlers/pull_request/closed_handler_test.rb +192 -0
- data/test/models/shipit/webhooks/handlers/pull_request/edited_handler_test.rb +47 -0
- data/test/models/shipit/webhooks/handlers/pull_request/label_capturing_handler_test.rb +209 -0
- data/test/models/shipit/webhooks/handlers/pull_request/labeled_handler_test.rb +332 -0
- data/test/models/shipit/webhooks/handlers/pull_request/opened_handler_test.rb +238 -0
- data/test/models/shipit/webhooks/handlers/pull_request/reopened_handler_test.rb +282 -0
- data/test/models/shipit/webhooks/handlers/pull_request/review_stack_adapter_test.rb +107 -0
- data/test/models/shipit/webhooks/handlers/pull_request/unlabeled_handler_test.rb +324 -0
- data/test/models/shipit/{wehbooks → webhooks}/handlers_test.rb +0 -0
- data/test/models/tasks_test.rb +66 -3
- data/test/serializers/shipit/pull_request_serializer_test.rb +29 -0
- data/test/test_helper.rb +15 -0
- data/test/unit/anonymous_user_serializer_test.rb +1 -1
- data/test/unit/command_test.rb +8 -3
- data/test/unit/commit_serializer_test.rb +1 -1
- data/test/unit/deploy_commands_test.rb +73 -17
- data/test/unit/deploy_serializer_test.rb +1 -1
- data/test/unit/github_app_test.rb +2 -3
- data/test/unit/github_apps_test.rb +416 -0
- data/test/unit/github_url_helper_test.rb +5 -0
- data/test/unit/shipit_deployment_checks_test.rb +77 -0
- data/test/unit/shipit_task_execution_strategy_test.rb +47 -0
- data/test/unit/shipit_test.rb +14 -0
- data/test/unit/user_serializer_test.rb +1 -1
- metadata +306 -188
- data/app/controllers/shipit/pull_requests_controller.rb +0 -31
- data/app/jobs/shipit/merge_pull_requests_job.rb +0 -32
- data/app/jobs/shipit/refresh_pull_request_job.rb +0 -11
- data/app/views/shipit/pull_requests/_pull_request.html.erb +0 -29
- data/test/fixtures/shipit/output_chunks.yml +0 -47
- data/test/models/output_chunk_test.rb +0 -21
|
@@ -20,15 +20,15 @@ module Shipit
|
|
|
20
20
|
return if github_id?
|
|
21
21
|
|
|
22
22
|
response = begin
|
|
23
|
-
create_deployment_on_github(
|
|
23
|
+
create_deployment_on_github(stack.github_api)
|
|
24
24
|
rescue Octokit::ClientError
|
|
25
|
-
raise if Shipit.github.api ==
|
|
25
|
+
raise if Shipit.github(organization: stack.repository.owner).api == stack.github_api
|
|
26
26
|
# If the deploy author didn't gave us the permission to create the deployment we falback the the main shipit
|
|
27
27
|
# user.
|
|
28
28
|
#
|
|
29
29
|
# Octokit currently raise NotFound, but I'm convinced it should be Forbidden if the user can see the repository.
|
|
30
30
|
# So to be future proof I catch boths.
|
|
31
|
-
create_deployment_on_github(
|
|
31
|
+
create_deployment_on_github(stack.github_api)
|
|
32
32
|
end
|
|
33
33
|
update!(github_id: response.id, api_url: response.url)
|
|
34
34
|
end
|
|
@@ -12,15 +12,15 @@ module Shipit
|
|
|
12
12
|
def create_on_github!
|
|
13
13
|
return if github_id?
|
|
14
14
|
response = begin
|
|
15
|
-
create_status_on_github(
|
|
15
|
+
create_status_on_github(stack.github_api)
|
|
16
16
|
rescue Octokit::ClientError
|
|
17
|
-
raise if Shipit.github.api ==
|
|
17
|
+
raise if Shipit.github(organization: stack.repository.owner).api == stack.github_api
|
|
18
18
|
# If the deploy author didn't gave us the permission to create the deployment we falback the the main shipit
|
|
19
19
|
# user.
|
|
20
20
|
#
|
|
21
21
|
# Octokit currently raise NotFound, but I'm convinced it should be Forbidden if the user can see the repository.
|
|
22
22
|
# So to be future proof I catch boths.
|
|
23
|
-
create_status_on_github(
|
|
23
|
+
create_status_on_github(stack.github_api)
|
|
24
24
|
end
|
|
25
25
|
update!(github_id: response.id, api_url: response.url)
|
|
26
26
|
end
|
data/app/models/shipit/deploy.rb
CHANGED
|
@@ -15,8 +15,8 @@ module Shipit
|
|
|
15
15
|
after_transition any => any, do: :update_last_deploy_time
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
belongs_to :until_commit, class_name: 'Commit', required: true
|
|
19
|
-
belongs_to :since_commit, class_name: 'Commit', required: true
|
|
18
|
+
belongs_to :until_commit, class_name: 'Commit', required: true
|
|
19
|
+
belongs_to :since_commit, class_name: 'Commit', required: true
|
|
20
20
|
has_many :commit_deployments, dependent: :destroy, inverse_of: :task, foreign_key: :task_id do
|
|
21
21
|
GITHUB_STATUSES = {
|
|
22
22
|
'pending' => 'pending',
|
|
@@ -92,18 +92,21 @@ module Shipit
|
|
|
92
92
|
env: env&.to_h || {},
|
|
93
93
|
allow_concurrency: force,
|
|
94
94
|
ignored_safeties: force,
|
|
95
|
+
max_retries: stack.retries_on_rollback,
|
|
95
96
|
)
|
|
96
97
|
end
|
|
97
98
|
|
|
98
99
|
# Rolls the stack back to this deploy
|
|
99
|
-
def trigger_rollback(user = AnonymousUser.new, env: nil, force: false)
|
|
100
|
+
def trigger_rollback(user = AnonymousUser.new, env: nil, force: false, lock: true)
|
|
100
101
|
rollback = build_rollback(user, env: env, force: force)
|
|
101
102
|
rollback.save!
|
|
102
103
|
rollback.enqueue
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
if lock
|
|
106
|
+
lock_reason = "A rollback for #{rollback.since_commit.sha} has been triggered. " \
|
|
107
|
+
"Please make sure the reason for the rollback has been addressed before deploying again."
|
|
108
|
+
stack.update!(lock_reason: lock_reason, lock_author_id: user.id)
|
|
109
|
+
end
|
|
107
110
|
|
|
108
111
|
rollback
|
|
109
112
|
end
|
|
@@ -208,7 +211,7 @@ module Shipit
|
|
|
208
211
|
end
|
|
209
212
|
|
|
210
213
|
def report_complete!
|
|
211
|
-
if stack.release_status? && stack.release_status_delay.
|
|
214
|
+
if stack.release_status? && !stack.release_status_delay.zero?
|
|
212
215
|
enter_validation!
|
|
213
216
|
else
|
|
214
217
|
super
|
|
@@ -268,10 +271,13 @@ module Shipit
|
|
|
268
271
|
when 'aborted', 'aborting'
|
|
269
272
|
append_release_status('failure', "The deploy on #{stack.environment} was canceled")
|
|
270
273
|
when 'validating'
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
274
|
+
append_release_status(
|
|
275
|
+
'pending',
|
|
276
|
+
"The deploy on #{stack.environment} succeeded"
|
|
277
|
+
) unless stack.release_status_delay.zero?
|
|
278
|
+
|
|
279
|
+
MarkDeployHealthyJob.set(wait: stack.release_status_delay)
|
|
280
|
+
.perform_later(self) if stack.release_status_delay.positive?
|
|
275
281
|
when 'success'
|
|
276
282
|
if stack.release_status_delay.zero?
|
|
277
283
|
append_release_status('success', "The deploy on #{stack.environment} succeeded")
|
|
@@ -13,6 +13,7 @@ module Shipit
|
|
|
13
13
|
def initialize(app_dir, env)
|
|
14
14
|
@app_dir = Pathname(app_dir)
|
|
15
15
|
@env = env
|
|
16
|
+
super(nil)
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def cacheable
|
|
@@ -32,10 +33,10 @@ module Shipit
|
|
|
32
33
|
def cacheable_config
|
|
33
34
|
(config || {}).deep_merge(
|
|
34
35
|
'merge' => {
|
|
35
|
-
'require' =>
|
|
36
|
-
'ignore' =>
|
|
37
|
-
'revalidate_after' =>
|
|
38
|
-
'method' =>
|
|
36
|
+
'require' => merge_request_required_statuses,
|
|
37
|
+
'ignore' => merge_request_ignored_statuses,
|
|
38
|
+
'revalidate_after' => revalidate_merge_requests_after&.to_i,
|
|
39
|
+
'method' => merge_request_merge_method,
|
|
39
40
|
'max_divergence' => {
|
|
40
41
|
'commits' => max_divergence_commits&.to_i,
|
|
41
42
|
'age' => max_divergence_age&.to_i,
|
|
@@ -63,13 +64,18 @@ module Shipit
|
|
|
63
64
|
'delay' => release_status_delay,
|
|
64
65
|
},
|
|
65
66
|
'dependencies' => { 'override' => dependencies_steps },
|
|
67
|
+
'provision' => { 'handler_name' => provisioning_handler_name },
|
|
66
68
|
'deploy' => {
|
|
67
69
|
'override' => deploy_steps,
|
|
68
70
|
'variables' => deploy_variables.map(&:to_h),
|
|
69
71
|
'max_commits' => maximum_commits_per_deploy,
|
|
70
72
|
'interval' => pause_between_deploys,
|
|
73
|
+
'retries' => retries_on_deploy,
|
|
74
|
+
},
|
|
75
|
+
'rollback' => {
|
|
76
|
+
'override' => rollback_steps,
|
|
77
|
+
'retries' => retries_on_rollback,
|
|
71
78
|
},
|
|
72
|
-
'rollback' => { 'override' => rollback_steps },
|
|
73
79
|
'fetch' => fetch_deployed_revision_steps,
|
|
74
80
|
'tasks' => cacheable_tasks,
|
|
75
81
|
)
|
|
@@ -76,10 +76,18 @@ module Shipit
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def publish_independent_packages
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
command = if lerna_lerna >= LATEST_MAJOR_VERSION
|
|
80
|
+
[
|
|
81
|
+
'assert-lerna-independent-version-tags',
|
|
82
|
+
'publish-lerna-independent-packages',
|
|
83
|
+
]
|
|
84
|
+
else
|
|
85
|
+
[
|
|
86
|
+
'assert-lerna-independent-version-tags',
|
|
87
|
+
'publish-lerna-independent-packages-legacy',
|
|
88
|
+
]
|
|
89
|
+
end
|
|
90
|
+
command
|
|
83
91
|
end
|
|
84
92
|
|
|
85
93
|
def publish_fixed_version_packages
|
|
@@ -104,6 +104,10 @@ module Shipit
|
|
|
104
104
|
Duration.parse(config('deploy', 'interval') { 0 })
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
+
def provisioning_handler_name
|
|
108
|
+
config('provision', 'handler_name')
|
|
109
|
+
end
|
|
110
|
+
|
|
107
111
|
def deploy_steps
|
|
108
112
|
around_steps('deploy') do
|
|
109
113
|
config('deploy', 'override') { discover_deploy_steps }
|
|
@@ -122,6 +126,10 @@ module Shipit
|
|
|
122
126
|
deploy_variables.map { |v| [v.name, v.default] }.to_h
|
|
123
127
|
end
|
|
124
128
|
|
|
129
|
+
def retries_on_deploy
|
|
130
|
+
config('deploy', 'retries') { nil }
|
|
131
|
+
end
|
|
132
|
+
|
|
125
133
|
def rollback_steps
|
|
126
134
|
around_steps('rollback') do
|
|
127
135
|
config('rollback', 'override') { discover_rollback_steps }
|
|
@@ -132,6 +140,10 @@ module Shipit
|
|
|
132
140
|
rollback_steps || cant_detect!(:rollback)
|
|
133
141
|
end
|
|
134
142
|
|
|
143
|
+
def retries_on_rollback
|
|
144
|
+
config('rollback', 'retries') { nil }
|
|
145
|
+
end
|
|
146
|
+
|
|
135
147
|
def fetch_deployed_revision_steps
|
|
136
148
|
config('fetch') || discover_fetch_deployed_revision_steps
|
|
137
149
|
end
|
|
@@ -179,12 +191,12 @@ module Shipit
|
|
|
179
191
|
Array.wrap(config('ci', 'blocking'))
|
|
180
192
|
end
|
|
181
193
|
|
|
182
|
-
def
|
|
194
|
+
def merge_request_merge_method
|
|
183
195
|
method = config('merge', 'method')
|
|
184
196
|
method if %w(merge rebase squash).include?(method)
|
|
185
197
|
end
|
|
186
198
|
|
|
187
|
-
def
|
|
199
|
+
def merge_request_required_statuses
|
|
188
200
|
if config('merge', 'require') || config('merge', 'ignore')
|
|
189
201
|
Array.wrap(config('merge', 'require'))
|
|
190
202
|
else
|
|
@@ -192,7 +204,7 @@ module Shipit
|
|
|
192
204
|
end
|
|
193
205
|
end
|
|
194
206
|
|
|
195
|
-
def
|
|
207
|
+
def merge_request_ignored_statuses
|
|
196
208
|
if config('merge', 'require') || config('merge', 'ignore')
|
|
197
209
|
Array.wrap(config('merge', 'ignore')) + [release_status_context].compact
|
|
198
210
|
else
|
|
@@ -200,7 +212,7 @@ module Shipit
|
|
|
200
212
|
end
|
|
201
213
|
end
|
|
202
214
|
|
|
203
|
-
def
|
|
215
|
+
def revalidate_merge_requests_after
|
|
204
216
|
if timeout = config('merge', 'revalidate_after')
|
|
205
217
|
begin
|
|
206
218
|
Duration.parse(timeout)
|
data/app/models/shipit/hook.rb
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module Shipit
|
|
3
3
|
class Hook < Record
|
|
4
|
+
class DeliverySigner
|
|
5
|
+
attr_reader :secret
|
|
6
|
+
|
|
7
|
+
ALGORITHM = 'sha256'
|
|
8
|
+
|
|
9
|
+
def initialize(secret)
|
|
10
|
+
@secret = secret
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def sign(payload)
|
|
14
|
+
hmac = OpenSSL::HMAC.hexdigest(ALGORITHM, secret, payload)
|
|
15
|
+
"#{ALGORITHM}=#{hmac}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
4
19
|
class DeliverySpec
|
|
5
|
-
def initialize(event:, url:, content_type:, payload:)
|
|
20
|
+
def initialize(event:, url:, content_type:, payload:, secret:)
|
|
6
21
|
@event = event
|
|
7
22
|
@url = url
|
|
8
23
|
@content_type = content_type
|
|
9
24
|
@payload = payload
|
|
25
|
+
@secret = secret
|
|
10
26
|
end
|
|
11
27
|
|
|
12
28
|
def send!
|
|
@@ -15,7 +31,7 @@ module Shipit
|
|
|
15
31
|
|
|
16
32
|
private
|
|
17
33
|
|
|
18
|
-
attr_reader :event, :url, :content_type, :payload
|
|
34
|
+
attr_reader :event, :url, :content_type, :payload, :secret
|
|
19
35
|
|
|
20
36
|
def http
|
|
21
37
|
Faraday::Connection.new do |connection|
|
|
@@ -29,9 +45,16 @@ module Shipit
|
|
|
29
45
|
'User-Agent' => 'Shipit Webhook',
|
|
30
46
|
'Content-Type' => content_type,
|
|
31
47
|
'X-Shipit-Event' => event,
|
|
48
|
+
'X-Shipit-Signature' => signature,
|
|
32
49
|
'Accept' => '*/*',
|
|
33
50
|
}
|
|
34
51
|
end
|
|
52
|
+
|
|
53
|
+
def signature
|
|
54
|
+
return nil if secret.blank?
|
|
55
|
+
|
|
56
|
+
DeliverySigner.new(secret).sign(payload)
|
|
57
|
+
end
|
|
35
58
|
end
|
|
36
59
|
|
|
37
60
|
default_scope { order :id }
|
|
@@ -45,6 +68,7 @@ module Shipit
|
|
|
45
68
|
|
|
46
69
|
EVENTS = %w(
|
|
47
70
|
stack
|
|
71
|
+
review_stack
|
|
48
72
|
task
|
|
49
73
|
deploy
|
|
50
74
|
rollback
|
|
@@ -53,6 +77,7 @@ module Shipit
|
|
|
53
77
|
deployable_status
|
|
54
78
|
merge_status
|
|
55
79
|
merge
|
|
80
|
+
pull_request
|
|
56
81
|
).freeze
|
|
57
82
|
|
|
58
83
|
belongs_to :stack, required: false
|
|
@@ -117,6 +142,7 @@ module Shipit
|
|
|
117
142
|
url: delivery_url,
|
|
118
143
|
content_type: CONTENT_TYPES[content_type],
|
|
119
144
|
payload: serialize_payload(payload),
|
|
145
|
+
secret: secret,
|
|
120
146
|
)
|
|
121
147
|
end
|
|
122
148
|
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Shipit
|
|
3
|
+
class MergeRequest < ApplicationRecord
|
|
4
|
+
include DeferredTouch
|
|
5
|
+
|
|
6
|
+
MERGE_REQUEST_FIELD = 'Merge-Requested-By'
|
|
7
|
+
|
|
8
|
+
WAITING_STATUSES = %w(fetching pending).freeze
|
|
9
|
+
QUEUED_STATUSES = %w(pending revalidating).freeze
|
|
10
|
+
REJECTION_REASONS = %w(ci_missing ci_failing merge_conflict requires_rebase).freeze
|
|
11
|
+
InvalidTransition = Class.new(StandardError)
|
|
12
|
+
NotReady = Class.new(StandardError)
|
|
13
|
+
|
|
14
|
+
class StatusChecker < Status::Group
|
|
15
|
+
def initialize(commit, statuses, deploy_spec)
|
|
16
|
+
@deploy_spec = deploy_spec
|
|
17
|
+
super(commit, statuses)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
attr_reader :deploy_spec
|
|
23
|
+
|
|
24
|
+
def reject_hidden(statuses)
|
|
25
|
+
statuses.reject { |s| ignored_statuses.include?(s.context) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def reject_allowed_to_fail(statuses)
|
|
29
|
+
statuses.reject { |s| ignored_statuses.include?(s.context) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ignored_statuses
|
|
33
|
+
deploy_spec&.merge_request_ignored_statuses || []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def required_statuses
|
|
37
|
+
deploy_spec&.merge_request_required_statuses || []
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
belongs_to :stack
|
|
42
|
+
belongs_to :head, class_name: 'Shipit::Commit', optional: true
|
|
43
|
+
belongs_to :base_commit, class_name: 'Shipit::Commit', optional: true
|
|
44
|
+
belongs_to :merge_requested_by, class_name: 'Shipit::User', optional: true
|
|
45
|
+
has_one :merge_commit, class_name: 'Shipit::Commit'
|
|
46
|
+
|
|
47
|
+
deferred_touch stack: :updated_at
|
|
48
|
+
|
|
49
|
+
validates :number, presence: true, uniqueness: { scope: :stack_id }
|
|
50
|
+
|
|
51
|
+
scope :waiting, -> { where(merge_status: WAITING_STATUSES) }
|
|
52
|
+
scope :pending, -> { where(merge_status: 'pending') }
|
|
53
|
+
scope :to_be_merged, -> { pending.order(merge_requested_at: :asc) }
|
|
54
|
+
scope :queued, -> { where(merge_status: QUEUED_STATUSES).order(merge_requested_at: :asc) }
|
|
55
|
+
|
|
56
|
+
after_save :record_merge_status_change
|
|
57
|
+
after_commit :emit_hooks
|
|
58
|
+
|
|
59
|
+
state_machine :merge_status, initial: :fetching do
|
|
60
|
+
state :fetching
|
|
61
|
+
state :pending
|
|
62
|
+
state :rejected
|
|
63
|
+
state :canceled
|
|
64
|
+
state :merged
|
|
65
|
+
state :revalidating
|
|
66
|
+
|
|
67
|
+
event :fetched do
|
|
68
|
+
transition fetching: :pending
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
event :reject do
|
|
72
|
+
transition pending: :rejected
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
event :revalidate do
|
|
76
|
+
transition pending: :revalidating
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
event :cancel do
|
|
80
|
+
transition any => :canceled
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
event :complete do
|
|
84
|
+
transition pending: :merged
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
event :retry do
|
|
88
|
+
transition %i(rejected canceled revalidating) => :pending
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
before_transition rejected: any do |pr|
|
|
92
|
+
pr.rejection_reason = nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
before_transition %i(fetching rejected canceled) => :pending do |pr|
|
|
96
|
+
pr.merge_requested_at = Time.now.utc
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
before_transition any => :pending do |pr|
|
|
100
|
+
pr.revalidated_at = Time.now.utc
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
before_transition %i(pending) => :merged do |pr|
|
|
104
|
+
Stack.increment_counter(:undeployed_commits_count, pr.stack_id)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.schedule_merges
|
|
109
|
+
Shipit::Stack.where(merge_queue_enabled: true).find_each(&:schedule_merges)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def self.extract_number(stack, number_or_url)
|
|
113
|
+
org = stack.repository.owner
|
|
114
|
+
case number_or_url
|
|
115
|
+
when /\A#?(\d+)\z/
|
|
116
|
+
$1.to_i
|
|
117
|
+
when %r{\Ahttps://#{Regexp.escape(Shipit.github(organization: org).domain)}/([^/]+)/([^/]+)/pull/(\d+)}
|
|
118
|
+
return unless $1.downcase == stack.repo_owner.downcase
|
|
119
|
+
return unless $2.downcase == stack.repo_name.downcase
|
|
120
|
+
$3.to_i
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.request_merge!(stack, number, user)
|
|
125
|
+
now = Time.now.utc
|
|
126
|
+
merge_request = begin
|
|
127
|
+
create_with(
|
|
128
|
+
merge_requested_at: now,
|
|
129
|
+
merge_requested_by: user.presence,
|
|
130
|
+
).find_or_create_by!(
|
|
131
|
+
stack: stack,
|
|
132
|
+
number: number,
|
|
133
|
+
)
|
|
134
|
+
rescue ActiveRecord::RecordNotUnique
|
|
135
|
+
retry
|
|
136
|
+
end
|
|
137
|
+
merge_request.update!(merge_requested_by: user.presence)
|
|
138
|
+
merge_request.retry! if merge_request.rejected? || merge_request.canceled? || merge_request.revalidating?
|
|
139
|
+
merge_request.schedule_refresh!
|
|
140
|
+
merge_request
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def reject!(reason)
|
|
144
|
+
unless REJECTION_REASONS.include?(reason)
|
|
145
|
+
raise ArgumentError, "invalid reason: #{reason.inspect}, must be one of: #{REJECTION_REASONS.inspect}"
|
|
146
|
+
end
|
|
147
|
+
self.rejection_reason = reason.presence
|
|
148
|
+
super()
|
|
149
|
+
true
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def reject_unless_mergeable!
|
|
153
|
+
return reject!('merge_conflict') if merge_conflict?
|
|
154
|
+
return reject!('ci_missing') if any_status_checks_missing?
|
|
155
|
+
return reject!('ci_failing') if any_status_checks_failed?
|
|
156
|
+
return reject!('requires_rebase') if stale?
|
|
157
|
+
false
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def merge!
|
|
161
|
+
raise InvalidTransition unless pending?
|
|
162
|
+
|
|
163
|
+
raise NotReady if not_mergeable_yet?
|
|
164
|
+
|
|
165
|
+
stack.github_api.merge_pull_request(
|
|
166
|
+
stack.github_repo_name,
|
|
167
|
+
number,
|
|
168
|
+
merge_message,
|
|
169
|
+
sha: head.sha,
|
|
170
|
+
commit_message: 'Merged by Shipit',
|
|
171
|
+
merge_method: stack.merge_method,
|
|
172
|
+
)
|
|
173
|
+
begin
|
|
174
|
+
if stack.github_api.pull_requests(stack.github_repo_name, base: branch).empty?
|
|
175
|
+
stack.github_api.delete_branch(stack.github_repo_name, branch)
|
|
176
|
+
end
|
|
177
|
+
rescue Octokit::UnprocessableEntity
|
|
178
|
+
# branch was already deleted somehow
|
|
179
|
+
end
|
|
180
|
+
complete!
|
|
181
|
+
true
|
|
182
|
+
rescue Octokit::MethodNotAllowed # merge conflict
|
|
183
|
+
reject!('merge_conflict')
|
|
184
|
+
false
|
|
185
|
+
rescue Octokit::Conflict # shas didn't match, PR was updated.
|
|
186
|
+
raise NotReady
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def all_status_checks_passed?
|
|
190
|
+
return false unless head
|
|
191
|
+
StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec).success?
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def any_status_checks_failed?
|
|
195
|
+
status = StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec)
|
|
196
|
+
status.failure? || status.error?
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def any_status_checks_missing?
|
|
200
|
+
StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec).missing?
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def waiting?
|
|
204
|
+
WAITING_STATUSES.include?(merge_status)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def need_revalidation?
|
|
208
|
+
timeout = stack.cached_deploy_spec&.revalidate_merge_requests_after
|
|
209
|
+
return false unless timeout
|
|
210
|
+
(revalidated_at + timeout).past?
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def merge_conflict?
|
|
214
|
+
mergeable == false
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def not_mergeable_yet?
|
|
218
|
+
mergeable.nil?
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def schedule_refresh!
|
|
222
|
+
RefreshMergeRequestJob.perform_later(self)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def closed?
|
|
226
|
+
state == "closed"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def merged_upstream?
|
|
230
|
+
closed? && merged_at
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def refresh!
|
|
234
|
+
update!(github_pull_request: stack.github_api.pull_request(stack.github_repo_name, number))
|
|
235
|
+
head.refresh_statuses!
|
|
236
|
+
head.refresh_check_runs!
|
|
237
|
+
fetched! if fetching?
|
|
238
|
+
@comparison = nil
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def github_pull_request=(github_pull_request)
|
|
242
|
+
self.github_id = github_pull_request.id
|
|
243
|
+
self.api_url = github_pull_request.url
|
|
244
|
+
self.title = github_pull_request.title
|
|
245
|
+
self.state = github_pull_request.state
|
|
246
|
+
self.mergeable = github_pull_request.mergeable
|
|
247
|
+
self.additions = github_pull_request.additions
|
|
248
|
+
self.deletions = github_pull_request.deletions
|
|
249
|
+
self.branch = github_pull_request.head.ref
|
|
250
|
+
self.head = find_or_create_commit_from_github_by_sha!(github_pull_request.head.sha, detached: true)
|
|
251
|
+
self.merged_at = github_pull_request.merged_at
|
|
252
|
+
self.base_ref = github_pull_request.base.ref
|
|
253
|
+
self.base_commit = find_or_create_commit_from_github_by_sha!(github_pull_request.base.sha, detached: true)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def merge_message
|
|
257
|
+
return title unless merge_requested_by
|
|
258
|
+
"#{title}\n\n#{MERGE_REQUEST_FIELD}: #{merge_requested_by.login}\n"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def stale?
|
|
262
|
+
return false unless base_commit
|
|
263
|
+
spec = stack.cached_deploy_spec
|
|
264
|
+
if max_branch_age = spec.max_divergence_age
|
|
265
|
+
return true if Time.now.utc - head.committed_at > max_branch_age
|
|
266
|
+
end
|
|
267
|
+
if commit_count_limit = spec.max_divergence_commits
|
|
268
|
+
return true if comparison.behind_by > commit_count_limit
|
|
269
|
+
end
|
|
270
|
+
false
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def comparison
|
|
274
|
+
@comparison ||= stack.github_api.compare(
|
|
275
|
+
stack.github_repo_name,
|
|
276
|
+
base_ref,
|
|
277
|
+
head.sha,
|
|
278
|
+
)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
private
|
|
282
|
+
|
|
283
|
+
def record_merge_status_change
|
|
284
|
+
@merge_status_changed ||= saved_change_to_attribute?(:merge_status)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def emit_hooks
|
|
288
|
+
return unless @merge_status_changed
|
|
289
|
+
@merge_status_changed = nil
|
|
290
|
+
Hook.emit('merge', stack, merge_request: self, status: merge_status, stack: stack)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def find_or_create_commit_from_github_by_sha!(sha, attributes)
|
|
294
|
+
if commit = stack.commits.by_sha(sha)
|
|
295
|
+
commit
|
|
296
|
+
else
|
|
297
|
+
github_commit = stack.github_api.commit(stack.github_repo_name, sha)
|
|
298
|
+
stack.commits.create_from_github!(github_commit, attributes)
|
|
299
|
+
end
|
|
300
|
+
rescue ActiveRecord::RecordNotUnique
|
|
301
|
+
retry
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|