shipit-engine 0.20.0 → 0.21.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.
- checksums.yaml +5 -5
- data/README.md +43 -6
- data/app/assets/stylesheets/_base/_base.scss +4 -0
- data/app/assets/stylesheets/_pages/_commits.scss +3 -1
- data/app/assets/stylesheets/_pages/_deploy.scss +4 -2
- data/app/controllers/concerns/shipit/authentication.rb +1 -1
- data/app/controllers/shipit/api/base_controller.rb +6 -1
- data/app/controllers/shipit/api/pull_requests_controller.rb +1 -1
- data/app/controllers/shipit/commit_checks_controller.rb +1 -1
- data/app/controllers/shipit/shipit_controller.rb +1 -5
- data/app/controllers/shipit/stacks_controller.rb +2 -0
- data/app/controllers/shipit/tasks_controller.rb +1 -1
- data/app/controllers/shipit/webhooks_controller.rb +2 -2
- data/app/helpers/shipit/deploys_helper.rb +9 -0
- data/app/helpers/shipit/shipit_helper.rb +17 -15
- data/app/helpers/shipit/stacks_helper.rb +6 -1
- data/app/jobs/shipit/destroy_stack_job.rb +4 -2
- data/app/jobs/shipit/fetch_deployed_revision_job.rb +1 -1
- data/app/jobs/shipit/github_sync_job.rb +1 -1
- data/app/jobs/shipit/merge_pull_requests_job.rb +3 -3
- data/app/jobs/shipit/perform_task_job.rb +3 -0
- data/app/jobs/shipit/purge_old_deliveries_job.rb +1 -0
- data/app/models/shipit/api_client.rb +1 -1
- data/app/models/shipit/commit.rb +29 -6
- data/app/models/shipit/commit_deployment.rb +1 -1
- data/app/models/shipit/commit_deployment_status.rb +1 -1
- data/app/models/shipit/deploy_spec/bundler_discovery.rb +1 -10
- data/app/models/shipit/deploy_spec/file_system.rb +6 -0
- data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +1 -1
- data/app/models/shipit/deploy_spec/lerna_discovery.rb +85 -0
- data/app/models/shipit/deploy_spec/npm_discovery.rb +103 -5
- data/app/models/shipit/deploy_spec/pypi_discovery.rb +4 -2
- data/app/models/shipit/deploy_spec/rubygems_discovery.rb +4 -2
- data/app/models/shipit/deploy_spec.rb +19 -2
- data/app/models/shipit/duration.rb +1 -1
- data/app/models/shipit/github_status.rb +1 -1
- data/app/models/shipit/hook.rb +4 -5
- data/app/models/shipit/output_chunk.rb +1 -1
- data/app/models/shipit/pull_request.rb +37 -16
- data/app/models/shipit/stack.rb +17 -11
- data/app/models/shipit/status/common.rb +4 -0
- data/app/models/shipit/status/group.rb +4 -0
- data/app/models/shipit/task.rb +20 -8
- data/app/models/shipit/task_definition.rb +2 -2
- data/app/models/shipit/undeployed_commit.rb +13 -2
- data/app/models/shipit/user.rb +1 -1
- data/app/serializers/shipit/pull_request_serializer.rb +1 -1
- data/app/serializers/shipit/tail_task_serializer.rb +1 -1
- data/app/views/shipit/ccmenu/project.xml.builder +9 -8
- data/app/views/shipit/deploys/_deploy.html.erb +3 -2
- data/app/views/shipit/stacks/_banners.html.erb +4 -1
- data/app/views/shipit/stacks/settings.html.erb +4 -0
- data/app/views/shipit/statuses/_group.html.erb +1 -1
- data/app/views/shipit/statuses/_status.html.erb +1 -1
- data/app/views/shipit/tasks/_task.html.erb +1 -2
- data/config/locales/en.yml +2 -0
- data/config/secrets.development.example.yml +0 -4
- data/config/secrets.development.shopify.yml +1 -5
- data/db/migrate/20170904103242_reindex_deliveries.rb +7 -0
- data/db/migrate/20171120161420_add_base_info_to_pull_request.rb +7 -0
- data/db/migrate/20180202220850_add_aborted_by_to_tasks.rb +5 -0
- data/lib/shipit/command.rb +11 -3
- data/lib/shipit/engine.rb +0 -4
- data/lib/shipit/stack_commands.rb +3 -1
- data/lib/shipit/version.rb +1 -1
- data/lib/shipit.rb +15 -23
- data/lib/snippets/assert-lerna-fixed-version-tag +21 -0
- data/lib/snippets/assert-lerna-independent-version-tags +28 -0
- data/lib/snippets/generate-local-npmrc +19 -0
- data/lib/snippets/misconfigured-npm-publish-config +8 -0
- data/lib/snippets/publish-lerna-independent-packages +39 -0
- data/lib/snippets/push-to-heroku +5 -5
- data/lib/tasks/cron.rake +1 -1
- data/lib/tasks/dev.rake +1 -1
- data/test/controllers/api/deploys_controller_test.rb +19 -0
- data/test/controllers/api/stacks_controller_test.rb +1 -1
- data/test/controllers/github_authentication_controller_test.rb +1 -1
- data/test/controllers/stacks_controller_test.rb +10 -0
- data/test/controllers/tasks_controller_test.rb +2 -0
- data/test/controllers/webhooks_controller_test.rb +0 -7
- data/test/dummy/config/secrets.yml +0 -2
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/schema.rb +5 -3
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/fixtures/shipit/commits.yml +53 -0
- data/test/fixtures/shipit/pull_requests.yml +52 -0
- data/test/fixtures/shipit/stacks.yml +35 -0
- data/test/fixtures/shipit/statuses.yml +27 -0
- data/test/fixtures/shipit/tasks.yml +14 -0
- data/test/helpers/queries_helper.rb +1 -1
- data/test/jobs/merge_pull_requests_job_test.rb +19 -2
- data/test/jobs/perform_task_job_test.rb +26 -2
- data/test/models/commits_test.rb +55 -6
- data/test/models/deploy_spec_test.rb +288 -52
- data/test/models/deploys_test.rb +7 -7
- data/test/models/hook_test.rb +7 -1
- data/test/models/pull_request_test.rb +78 -24
- data/test/models/stacks_test.rb +23 -19
- data/test/models/status/group_test.rb +6 -0
- data/test/models/undeployed_commits_test.rb +9 -0
- data/test/models/users_test.rb +2 -2
- data/test/test_helper.rb +1 -1
- metadata +211 -224
- data/app/assets/javascripts/shipit_bs.js.coffee +0 -2
- data/app/assets/stylesheets/shipit_bs.scss +0 -22
- data/app/views/bootstrap/shipit/missing_settings.html.erb +0 -97
- data/app/views/bootstrap/shipit/stacks/new.html.erb +0 -44
- data/app/views/layouts/shipit_bootstrap.html.erb +0 -44
- data/lib/shipit/template_renderer_extension.rb +0 -16
- data/test/dummy/db/test.sqlite3-journal +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module Shipit
|
|
4
|
+
class DeploySpec
|
|
5
|
+
module LernaDiscovery
|
|
6
|
+
def discover_dependencies_steps
|
|
7
|
+
discover_lerna_json || super
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def discover_lerna_json
|
|
11
|
+
lerna_install if lerna?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def lerna_install
|
|
15
|
+
[js_command('install --no-progress'), 'node_modules/.bin/lerna bootstrap']
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def discover_review_checklist
|
|
19
|
+
discover_lerna_checklist || super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def discover_lerna_checklist
|
|
23
|
+
if lerna?
|
|
24
|
+
[%(
|
|
25
|
+
<strong>Don't forget version and tag before publishing!</strong>
|
|
26
|
+
You can do this with:<br/>
|
|
27
|
+
<pre>
|
|
28
|
+
lerna publish --skip-npm
|
|
29
|
+
&& git add -A
|
|
30
|
+
&& git push --follow-tags
|
|
31
|
+
</pre>
|
|
32
|
+
)]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def lerna?
|
|
37
|
+
lerna_json.exist?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def lerna_json
|
|
41
|
+
file('lerna.json')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def lerna_version
|
|
45
|
+
lerna_config = lerna_json.read
|
|
46
|
+
JSON.parse(lerna_config)['version']
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def discover_lerna_packages
|
|
50
|
+
publish_lerna_packages if lerna?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def discover_deploy_steps
|
|
54
|
+
discover_lerna_packages || super
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def publish_lerna_packages
|
|
58
|
+
return publish_independent_packages if lerna_version == 'independent'
|
|
59
|
+
publish_fixed_version_packages
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def publish_independent_packages
|
|
63
|
+
[
|
|
64
|
+
'assert-lerna-independent-version-tags',
|
|
65
|
+
'publish-lerna-independent-packages',
|
|
66
|
+
]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def publish_fixed_version_packages
|
|
70
|
+
check_tags = 'assert-lerna-fixed-version-tag'
|
|
71
|
+
# `yarn publish` requires user input, so always use npm.
|
|
72
|
+
version = lerna_version
|
|
73
|
+
publish =
|
|
74
|
+
"node_modules/.bin/lerna publish " \
|
|
75
|
+
"--yes " \
|
|
76
|
+
"--skip-git " \
|
|
77
|
+
"--repo-version #{version} " \
|
|
78
|
+
"--force-publish=* " \
|
|
79
|
+
"--npm-tag #{dist_tag(version)}"
|
|
80
|
+
|
|
81
|
+
[check_tags, publish]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -3,6 +3,12 @@ require 'json'
|
|
|
3
3
|
module Shipit
|
|
4
4
|
class DeploySpec
|
|
5
5
|
module NpmDiscovery
|
|
6
|
+
# https://docs.npmjs.com/cli/publish
|
|
7
|
+
PUBLIC = 'public'.freeze
|
|
8
|
+
PRIVATE = 'restricted'.freeze
|
|
9
|
+
VALID_ACCESS = [PUBLIC, PRIVATE].freeze
|
|
10
|
+
NPM_REGISTRY = "https://registry.npmjs.org/".freeze
|
|
11
|
+
|
|
6
12
|
def discover_dependencies_steps
|
|
7
13
|
discover_package_json || super
|
|
8
14
|
end
|
|
@@ -20,13 +26,19 @@ module Shipit
|
|
|
20
26
|
end
|
|
21
27
|
|
|
22
28
|
def discover_yarn_checklist
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
if yarn?
|
|
30
|
+
[%(<strong>Don't forget version and tag before publishing!</strong> You can do this with:<br/>
|
|
31
|
+
yarn version --new-version <strong><major|minor|patch></strong>
|
|
32
|
+
&& git push --follow-tags</pre>)]
|
|
33
|
+
end
|
|
25
34
|
end
|
|
26
35
|
|
|
27
36
|
def discover_npm_checklist
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
if npm?
|
|
38
|
+
[%(<strong>Don't forget version and tag before publishing!</strong> You can do this with:<br/>
|
|
39
|
+
npm version <strong><major|minor|patch></strong>
|
|
40
|
+
&& git push --follow-tags</pre>)]
|
|
41
|
+
end
|
|
30
42
|
end
|
|
31
43
|
|
|
32
44
|
def npm?
|
|
@@ -40,10 +52,23 @@ module Shipit
|
|
|
40
52
|
JSON.parse(file.read)['private'].blank?
|
|
41
53
|
end
|
|
42
54
|
|
|
55
|
+
def dist_tag(version)
|
|
56
|
+
# Pre-release SemVer tags such as 'beta', 'alpha', 'rc' and 'next'
|
|
57
|
+
# are treated as 'next' npm dist-tags.
|
|
58
|
+
# An 1.0.0-beta.1 would be installable using both:
|
|
59
|
+
# `yarn add package@1.0.0-beta.1` and `yarn add package@next`
|
|
60
|
+
return 'next' if ['-beta', '-alpha', '-rc', '-next'].any? { |tag| version.include? tag }
|
|
61
|
+
'latest'
|
|
62
|
+
end
|
|
63
|
+
|
|
43
64
|
def package_json
|
|
44
65
|
file('package.json')
|
|
45
66
|
end
|
|
46
67
|
|
|
68
|
+
def package_json_contents
|
|
69
|
+
@package_json_contents ||= JSON.parse(package_json.read)
|
|
70
|
+
end
|
|
71
|
+
|
|
47
72
|
def yarn?
|
|
48
73
|
yarn_lock.exist? && public?
|
|
49
74
|
end
|
|
@@ -60,11 +85,84 @@ module Shipit
|
|
|
60
85
|
discover_npm_package || super
|
|
61
86
|
end
|
|
62
87
|
|
|
88
|
+
def package_name
|
|
89
|
+
package_json_contents['name']
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def package_version
|
|
93
|
+
package_json_contents['version']
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def publish_config
|
|
97
|
+
package_json_contents['publishConfig']
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def publish_config_access
|
|
101
|
+
config = publish_config
|
|
102
|
+
|
|
103
|
+
# default to private deploy when we enforce a publishConfig
|
|
104
|
+
if enforce_publish_config?
|
|
105
|
+
return PRIVATE if config.blank?
|
|
106
|
+
config['access'] || PRIVATE
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
return PUBLIC if config.blank?
|
|
110
|
+
config['access'] || PUBLIC
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def scoped_package?
|
|
114
|
+
return false if Shipit.npm_org_scope.nil?
|
|
115
|
+
package_name.start_with?(Shipit.npm_org_scope)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def enforce_publish_config?
|
|
119
|
+
enforce = Shipit.enforce_publish_config
|
|
120
|
+
return false if enforce.nil? || enforce.to_s == "0"
|
|
121
|
+
true
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def valid_publish_config?
|
|
125
|
+
return true unless enforce_publish_config?
|
|
126
|
+
return false if Shipit.private_npm_registry.nil?
|
|
127
|
+
return false if publish_config.blank?
|
|
128
|
+
return true if publish_config_access == PUBLIC
|
|
129
|
+
|
|
130
|
+
valid_publish_config_access? && private_scoped_package?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def valid_publish_config_access?
|
|
134
|
+
VALID_ACCESS.include?(publish_config_access)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# ensure private packages are scoped
|
|
138
|
+
def private_scoped_package?
|
|
139
|
+
publish_config_access == PRIVATE && scoped_package?
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def local_npmrc
|
|
143
|
+
file(".npmrc")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def registry
|
|
147
|
+
scope = Shipit.npm_org_scope
|
|
148
|
+
prefix = scoped_package? ? "#{scope}:registry" : "registry"
|
|
149
|
+
|
|
150
|
+
if publish_config_access == PUBLIC
|
|
151
|
+
return "#{prefix}=#{NPM_REGISTRY}"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
"#{prefix}=#{Shipit.private_npm_registry}"
|
|
155
|
+
end
|
|
156
|
+
|
|
63
157
|
def publish_npm_package
|
|
158
|
+
return ['misconfigured-npm-publish-config'] unless valid_publish_config?
|
|
159
|
+
|
|
160
|
+
generate_npmrc = "generate-local-npmrc \"#{registry}\""
|
|
64
161
|
check_tags = 'assert-npm-version-tag'
|
|
65
162
|
# `yarn publish` requires user input, so always use npm.
|
|
66
|
-
publish =
|
|
163
|
+
publish = "npm publish --tag #{dist_tag(package_version)} --access #{publish_config_access}"
|
|
67
164
|
|
|
165
|
+
return [check_tags, generate_npmrc, publish] if enforce_publish_config?
|
|
68
166
|
[check_tags, publish]
|
|
69
167
|
end
|
|
70
168
|
|
|
@@ -14,8 +14,10 @@ module Shipit
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def discover_pypi_checklist
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if egg?
|
|
18
|
+
[%(<strong>Don't forget to add a tag before deploying!</strong> You can do this with:
|
|
19
|
+
git tag -a -m "Version <strong>x.y.z</strong>" v<strong>x.y.z</strong> && git push --tags)]
|
|
20
|
+
end
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def egg?
|
|
@@ -14,8 +14,10 @@ module Shipit
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def discover_gem_checklist
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if gem?
|
|
18
|
+
[%(<strong>Don't forget to add a tag before deploying!</strong> You can do this with:
|
|
19
|
+
git tag v<strong>x.y.z</strong> && git push --tags)]
|
|
20
|
+
end
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def gem?
|
|
@@ -15,7 +15,7 @@ module Shipit
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def bundle_path
|
|
18
|
-
Rails.root.join('data
|
|
18
|
+
Rails.root.join('data', 'bundler')
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -135,13 +135,17 @@ module Shipit
|
|
|
135
135
|
end
|
|
136
136
|
|
|
137
137
|
def required_statuses
|
|
138
|
-
Array.wrap(config('ci', 'require'))
|
|
138
|
+
(Array.wrap(config('ci', 'require')) + blocking_statuses).uniq
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
def soft_failing_statuses
|
|
142
142
|
Array.wrap(config('ci', 'allow_failures'))
|
|
143
143
|
end
|
|
144
144
|
|
|
145
|
+
def blocking_statuses
|
|
146
|
+
Array.wrap(config('ci', 'blocking'))
|
|
147
|
+
end
|
|
148
|
+
|
|
145
149
|
def pull_request_required_statuses
|
|
146
150
|
if config('merge', 'require') || config('merge', 'ignore')
|
|
147
151
|
Array.wrap(config('merge', 'require'))
|
|
@@ -167,6 +171,19 @@ module Shipit
|
|
|
167
171
|
end
|
|
168
172
|
end
|
|
169
173
|
|
|
174
|
+
def max_divergence_commits
|
|
175
|
+
config('merge', 'max_divergence', 'commits')
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def max_divergence_age
|
|
179
|
+
if timeout = config('merge', 'max_divergence', 'age')
|
|
180
|
+
begin
|
|
181
|
+
Duration.parse(timeout)
|
|
182
|
+
rescue Duration::ParseError
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
170
187
|
def review_checks
|
|
171
188
|
config('review', 'checks') || []
|
|
172
189
|
end
|
data/app/models/shipit/hook.rb
CHANGED
|
@@ -31,8 +31,8 @@ module Shipit
|
|
|
31
31
|
serialize :events, Shipit::CSVSerializer
|
|
32
32
|
|
|
33
33
|
scope :global, -> { where(stack_id: nil) }
|
|
34
|
-
scope :scoped_to, ->
|
|
35
|
-
scope :for_stack, ->
|
|
34
|
+
scope :scoped_to, ->(stack) { where(stack_id: stack.id) }
|
|
35
|
+
scope :for_stack, ->(stack_id) { where(stack_id: [nil, stack_id]) }
|
|
36
36
|
|
|
37
37
|
class << self
|
|
38
38
|
def emit(event, stack, payload)
|
|
@@ -80,9 +80,8 @@ module Shipit
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def purge_old_deliveries!(keep: DELIVERIES_LOG_SIZE)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
end
|
|
83
|
+
delivery_ids = deliveries.sent.order(id: :desc).offset(keep).pluck(:id)
|
|
84
|
+
deliveries.where(id: delivery_ids).delete_all
|
|
86
85
|
end
|
|
87
86
|
|
|
88
87
|
private
|
|
@@ -2,7 +2,7 @@ module Shipit
|
|
|
2
2
|
class OutputChunk < ActiveRecord::Base
|
|
3
3
|
belongs_to :task
|
|
4
4
|
|
|
5
|
-
scope :tail, ->
|
|
5
|
+
scope :tail, ->(start) { order(id: :asc).where('id > ?', start || 0) }
|
|
6
6
|
|
|
7
7
|
def text=(string)
|
|
8
8
|
super(string.force_encoding(Encoding::UTF_8).scrub)
|
|
@@ -4,7 +4,7 @@ module Shipit
|
|
|
4
4
|
|
|
5
5
|
WAITING_STATUSES = %w(fetching pending).freeze
|
|
6
6
|
QUEUED_STATUSES = %w(pending revalidating).freeze
|
|
7
|
-
REJECTION_REASONS = %w(ci_failing merge_conflict).freeze
|
|
7
|
+
REJECTION_REASONS = %w(ci_failing merge_conflict requires_rebase).freeze
|
|
8
8
|
InvalidTransition = Class.new(StandardError)
|
|
9
9
|
NotReady = Class.new(StandardError)
|
|
10
10
|
|
|
@@ -37,6 +37,7 @@ module Shipit
|
|
|
37
37
|
|
|
38
38
|
belongs_to :stack
|
|
39
39
|
belongs_to :head, class_name: 'Shipit::Commit', optional: true
|
|
40
|
+
belongs_to :base_commit, class_name: 'Shipit::Commit', optional: true
|
|
40
41
|
belongs_to :merge_requested_by, class_name: 'Shipit::User', optional: true
|
|
41
42
|
has_one :merge_commit, class_name: 'Shipit::Commit'
|
|
42
43
|
|
|
@@ -102,7 +103,7 @@ module Shipit
|
|
|
102
103
|
end
|
|
103
104
|
|
|
104
105
|
def self.schedule_merges
|
|
105
|
-
Shipit::Stack.where(
|
|
106
|
+
Shipit::Stack.where(merge_queue_enabled: true).find_each(&:schedule_merges)
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
def self.extract_number(stack, number_or_url)
|
|
@@ -112,7 +113,7 @@ module Shipit
|
|
|
112
113
|
when %r{\Ahttps://#{Regexp.escape(Shipit.github_domain)}/([^/]+)/([^/]+)/pull/(\d+)}
|
|
113
114
|
return unless $1.downcase == stack.repo_owner.downcase
|
|
114
115
|
return unless $2.downcase == stack.repo_name.downcase
|
|
115
|
-
|
|
116
|
+
$3.to_i
|
|
116
117
|
end
|
|
117
118
|
end
|
|
118
119
|
|
|
@@ -146,7 +147,8 @@ module Shipit
|
|
|
146
147
|
|
|
147
148
|
def reject_unless_mergeable!
|
|
148
149
|
return reject!('merge_conflict') if merge_conflict?
|
|
149
|
-
return reject!('ci_failing')
|
|
150
|
+
return reject!('ci_failing') if any_status_checks_failed?
|
|
151
|
+
return reject!('requires_rebase') if stale?
|
|
150
152
|
false
|
|
151
153
|
end
|
|
152
154
|
|
|
@@ -154,10 +156,6 @@ module Shipit
|
|
|
154
156
|
raise InvalidTransition unless pending?
|
|
155
157
|
|
|
156
158
|
raise NotReady if not_mergeable_yet?
|
|
157
|
-
if need_revalidation?
|
|
158
|
-
revalidate!
|
|
159
|
-
return false
|
|
160
|
-
end
|
|
161
159
|
|
|
162
160
|
Shipit.github_api.merge_pull_request(
|
|
163
161
|
stack.github_repo_name,
|
|
@@ -184,9 +182,15 @@ module Shipit
|
|
|
184
182
|
end
|
|
185
183
|
|
|
186
184
|
def all_status_checks_passed?
|
|
185
|
+
return false unless head
|
|
187
186
|
StatusChecker.new(head, head.statuses, stack.cached_deploy_spec).success?
|
|
188
187
|
end
|
|
189
188
|
|
|
189
|
+
def any_status_checks_failed?
|
|
190
|
+
status = StatusChecker.new(head, head.statuses, stack.cached_deploy_spec)
|
|
191
|
+
status.failure? || status.error?
|
|
192
|
+
end
|
|
193
|
+
|
|
190
194
|
def waiting?
|
|
191
195
|
WAITING_STATUSES.include?(merge_status)
|
|
192
196
|
end
|
|
@@ -221,6 +225,7 @@ module Shipit
|
|
|
221
225
|
update!(github_pull_request: Shipit.github_api.pull_request(stack.github_repo_name, number))
|
|
222
226
|
head.refresh_statuses!
|
|
223
227
|
fetched! if fetching?
|
|
228
|
+
@comparison = nil
|
|
224
229
|
end
|
|
225
230
|
|
|
226
231
|
def github_pull_request=(github_pull_request)
|
|
@@ -234,6 +239,8 @@ module Shipit
|
|
|
234
239
|
self.branch = github_pull_request.head.ref
|
|
235
240
|
self.head = find_or_create_commit_from_github_by_sha!(github_pull_request.head.sha, detached: true)
|
|
236
241
|
self.merged_at = github_pull_request.merged_at
|
|
242
|
+
self.base_ref = github_pull_request.base.ref
|
|
243
|
+
self.base_commit = find_or_create_commit_from_github_by_sha!(github_pull_request.base.sha, detached: true)
|
|
237
244
|
end
|
|
238
245
|
|
|
239
246
|
def merge_message
|
|
@@ -241,16 +248,30 @@ module Shipit
|
|
|
241
248
|
"#{title}\n\nMerge-Requested-By: #{merge_requested_by.login}\n"
|
|
242
249
|
end
|
|
243
250
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
251
|
+
def stale?
|
|
252
|
+
return false unless base_commit
|
|
253
|
+
spec = stack.cached_deploy_spec
|
|
254
|
+
if max_branch_age = spec.max_divergence_age
|
|
255
|
+
return true if Time.now.utc - head.committed_at > max_branch_age
|
|
249
256
|
end
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
@merge_status_changed ||= merge_status_changed?
|
|
257
|
+
if commit_count_limit = spec.max_divergence_commits
|
|
258
|
+
return true if comparison.behind_by > commit_count_limit
|
|
253
259
|
end
|
|
260
|
+
false
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def comparison
|
|
264
|
+
@comparison ||= Shipit.github_api.compare(
|
|
265
|
+
stack.github_repo_name,
|
|
266
|
+
base_ref,
|
|
267
|
+
head.sha,
|
|
268
|
+
)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
private
|
|
272
|
+
|
|
273
|
+
def record_merge_status_change
|
|
274
|
+
@merge_status_changed ||= saved_change_to_attribute?(:merge_status)
|
|
254
275
|
end
|
|
255
276
|
|
|
256
277
|
def emit_hooks
|
data/app/models/shipit/stack.rb
CHANGED
|
@@ -44,6 +44,7 @@ module Shipit
|
|
|
44
44
|
|
|
45
45
|
before_validation :update_defaults
|
|
46
46
|
before_destroy :clear_local_files
|
|
47
|
+
before_save :set_locked_since
|
|
47
48
|
after_commit :emit_lock_hooks
|
|
48
49
|
after_commit :emit_added_hooks, on: :create
|
|
49
50
|
after_commit :emit_updated_hooks, on: :update
|
|
@@ -198,14 +199,14 @@ module Shipit
|
|
|
198
199
|
commits.reachable.first.try!(:sha)
|
|
199
200
|
end
|
|
200
201
|
|
|
201
|
-
def merge_status(backlog_leniency_factor:
|
|
202
|
+
def merge_status(backlog_leniency_factor: 2.0)
|
|
202
203
|
return 'locked' if locked?
|
|
203
204
|
return 'failure' if %w(failure error).freeze.include?(branch_status)
|
|
204
205
|
return 'backlogged' if backlogged?(backlog_leniency_factor: backlog_leniency_factor)
|
|
205
206
|
'success'
|
|
206
207
|
end
|
|
207
208
|
|
|
208
|
-
def backlogged?(backlog_leniency_factor:
|
|
209
|
+
def backlogged?(backlog_leniency_factor: 2.0)
|
|
209
210
|
maximum_commits_per_deploy && (undeployed_commits_count > maximum_commits_per_deploy * backlog_leniency_factor)
|
|
210
211
|
end
|
|
211
212
|
|
|
@@ -255,11 +256,7 @@ module Shipit
|
|
|
255
256
|
end
|
|
256
257
|
|
|
257
258
|
def last_deployed_commit
|
|
258
|
-
|
|
259
|
-
deploy.until_commit
|
|
260
|
-
else
|
|
261
|
-
NoDeployedCommit
|
|
262
|
-
end
|
|
259
|
+
last_successful_deploy.try!(:until_commit) || NoDeployedCommit
|
|
263
260
|
end
|
|
264
261
|
|
|
265
262
|
def deployable?
|
|
@@ -287,7 +284,7 @@ module Shipit
|
|
|
287
284
|
end
|
|
288
285
|
|
|
289
286
|
def base_path
|
|
290
|
-
Rails.root.join('data
|
|
287
|
+
Rails.root.join('data', 'stacks', repo_owner, repo_name, environment)
|
|
291
288
|
end
|
|
292
289
|
|
|
293
290
|
def deploys_path
|
|
@@ -362,7 +359,6 @@ module Shipit
|
|
|
362
359
|
|
|
363
360
|
def lock(reason, user)
|
|
364
361
|
params = {lock_reason: reason, lock_author: user}
|
|
365
|
-
params[:locked_since] = Time.current if locked_since.nil?
|
|
366
362
|
update!(params)
|
|
367
363
|
end
|
|
368
364
|
|
|
@@ -384,8 +380,8 @@ module Shipit
|
|
|
384
380
|
end
|
|
385
381
|
|
|
386
382
|
delegate :plugins, :task_definitions, :hidden_statuses, :required_statuses, :soft_failing_statuses,
|
|
387
|
-
:deploy_variables, :filter_task_envs, :filter_deploy_envs,
|
|
388
|
-
:pause_between_deploys, to: :cached_deploy_spec
|
|
383
|
+
:blocking_statuses, :deploy_variables, :filter_task_envs, :filter_deploy_envs,
|
|
384
|
+
:maximum_commits_per_deploy, :pause_between_deploys, to: :cached_deploy_spec
|
|
389
385
|
|
|
390
386
|
def monitoring?
|
|
391
387
|
monitoring.present?
|
|
@@ -486,6 +482,16 @@ module Shipit
|
|
|
486
482
|
self.branch = 'master' if branch.blank?
|
|
487
483
|
end
|
|
488
484
|
|
|
485
|
+
def set_locked_since
|
|
486
|
+
return unless lock_reason_changed?
|
|
487
|
+
|
|
488
|
+
if lock_reason.blank?
|
|
489
|
+
self.locked_since = nil
|
|
490
|
+
else
|
|
491
|
+
self.locked_since ||= Time.now
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
489
495
|
def schedule_merges_if_necessary
|
|
490
496
|
if previous_changes.include?('lock_reason') && previous_changes['lock_reason'].last.blank?
|
|
491
497
|
schedule_merges
|
data/app/models/shipit/task.rb
CHANGED
|
@@ -7,13 +7,14 @@ module Shipit
|
|
|
7
7
|
PRESENCE_CHECK_TIMEOUT = 15
|
|
8
8
|
ACTIVE_STATUSES = %w(pending running aborting).freeze
|
|
9
9
|
COMPLETED_STATUSES = %w(success error failed flapping aborted).freeze
|
|
10
|
-
UNSUCCESSFUL_STATUSES = %w(error failed aborted flapping).freeze
|
|
10
|
+
UNSUCCESSFUL_STATUSES = %w(error failed aborted flapping timedout).freeze
|
|
11
11
|
|
|
12
12
|
attr_accessor :pid
|
|
13
13
|
|
|
14
14
|
belongs_to :deploy, foreign_key: :parent_id, required: false # required for fixtures
|
|
15
15
|
|
|
16
16
|
belongs_to :user, optional: true
|
|
17
|
+
belongs_to :aborted_by, class_name: 'User', optional: true
|
|
17
18
|
belongs_to :stack, counter_cache: true
|
|
18
19
|
belongs_to :until_commit, class_name: 'Commit'
|
|
19
20
|
belongs_to :since_commit, class_name: 'Commit'
|
|
@@ -56,11 +57,11 @@ module Shipit
|
|
|
56
57
|
task.started_at ||= Time.now.utc
|
|
57
58
|
end
|
|
58
59
|
|
|
59
|
-
before_transition any => %i(success failed error) do |task|
|
|
60
|
+
before_transition any => %i(success failed error timedout) do |task|
|
|
60
61
|
task.ended_at ||= Time.now.utc
|
|
61
62
|
end
|
|
62
63
|
|
|
63
|
-
after_transition any => %i(success failed error) do |task|
|
|
64
|
+
after_transition any => %i(success failed error timedout) do |task|
|
|
64
65
|
task.async_refresh_deployed_revision
|
|
65
66
|
end
|
|
66
67
|
|
|
@@ -88,6 +89,10 @@ module Shipit
|
|
|
88
89
|
transition all => :error
|
|
89
90
|
end
|
|
90
91
|
|
|
92
|
+
event :giveup do # :timeout would cause a name clash
|
|
93
|
+
transition all => :timedout
|
|
94
|
+
end
|
|
95
|
+
|
|
91
96
|
event :aborting do
|
|
92
97
|
transition all - %i(aborted) => :aborting
|
|
93
98
|
end
|
|
@@ -97,7 +102,7 @@ module Shipit
|
|
|
97
102
|
end
|
|
98
103
|
|
|
99
104
|
event :flap do
|
|
100
|
-
transition %i(failed error success) => :flapping
|
|
105
|
+
transition %i(failed error timedout success) => :flapping
|
|
101
106
|
end
|
|
102
107
|
|
|
103
108
|
state :pending
|
|
@@ -105,6 +110,7 @@ module Shipit
|
|
|
105
110
|
state :failed
|
|
106
111
|
state :success
|
|
107
112
|
state :error
|
|
113
|
+
state :timedout
|
|
108
114
|
state :aborting
|
|
109
115
|
state :aborted
|
|
110
116
|
state :flapping
|
|
@@ -114,15 +120,21 @@ module Shipit
|
|
|
114
120
|
status.in?(ACTIVE_STATUSES)
|
|
115
121
|
end
|
|
116
122
|
|
|
117
|
-
def report_failure!(
|
|
123
|
+
def report_failure!(error)
|
|
118
124
|
reload
|
|
119
125
|
if aborting?
|
|
120
126
|
aborted!
|
|
127
|
+
elsif error.respond_to?(:exit_code) && Shipit.timeout_exit_codes.include?(error.exit_code)
|
|
128
|
+
giveup!
|
|
121
129
|
else
|
|
122
130
|
failure!
|
|
123
131
|
end
|
|
124
132
|
end
|
|
125
133
|
|
|
134
|
+
def report_timeout!(_error)
|
|
135
|
+
giveup!
|
|
136
|
+
end
|
|
137
|
+
|
|
126
138
|
def report_error!(error)
|
|
127
139
|
write("#{error.class}: #{error.message}\n\t#{error.backtrace.join("\n\t")}\n")
|
|
128
140
|
error!
|
|
@@ -177,7 +189,7 @@ module Shipit
|
|
|
177
189
|
def output
|
|
178
190
|
gzip = self[:gzip_output]
|
|
179
191
|
|
|
180
|
-
if gzip.
|
|
192
|
+
if gzip.blank?
|
|
181
193
|
''
|
|
182
194
|
else
|
|
183
195
|
ActiveSupport::Gzip.decompress(gzip)
|
|
@@ -236,8 +248,8 @@ module Shipit
|
|
|
236
248
|
end
|
|
237
249
|
end
|
|
238
250
|
|
|
239
|
-
def abort!(rollback_once_aborted: false)
|
|
240
|
-
update!(rollback_once_aborted: rollback_once_aborted)
|
|
251
|
+
def abort!(rollback_once_aborted: false, aborted_by:)
|
|
252
|
+
update!(rollback_once_aborted: rollback_once_aborted, aborted_by_id: aborted_by.id)
|
|
241
253
|
|
|
242
254
|
if alive?
|
|
243
255
|
aborting
|