blob: be668731cb48dfedf97ad2697d93d9f2177702d2 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
# frozen_string_literal: true
module Tooling
module Danger
module StableBranch
VersionApiError = Class.new(StandardError)
STABLE_BRANCH_REGEX = %r{\A(?<version>\d+-\d+)-stable-ee\z}.freeze
FAILING_PACKAGE_AND_TEST_STATUSES = %w[manual canceled].freeze
# rubocop:disable Lint/MixedRegexpCaptureTypes
VERSION_REGEX = %r{
\A(?<major>\d+)
\.(?<minor>\d+)
(\.(?<patch>\d+))?
(-(?<rc>rc(?<rc_number>\d*)))?
(-\h+\.\h+)?
(-ee|\.ee\.\d+)?\z
}x.freeze
# rubocop:enable Lint/MixedRegexpCaptureTypes
MAINTENANCE_POLICY_URL = 'https://docs.gitlab.com/ee/policy/maintenance.html'
MAINTENANCE_POLICY_MESSAGE = <<~MSG
See the [release and maintenance policy](#{MAINTENANCE_POLICY_URL}) for more information.
MSG
FEATURE_ERROR_MESSAGE = <<~MSG
This MR includes the `type::feature` label. Features do not qualify for patch releases. #{MAINTENANCE_POLICY_MESSAGE}
MSG
BUG_ERROR_MESSAGE = <<~MSG
This branch is meant for backporting bug fixes. If this MR qualifies please add the `type::bug` label. #{MAINTENANCE_POLICY_MESSAGE}
MSG
VERSION_WARNING_MESSAGE = <<~MSG
Backporting to older releases requires an [exception request process](https://docs.gitlab.com/ee/policy/maintenance.html#backporting-to-older-releases)
MSG
FAILED_VERSION_REQUEST_MESSAGE = <<~MSG
There was a problem checking if this is a qualified version for backporting. Re-running this job may fix the problem.
MSG
PIPELINE_EXPEDITE_ERROR_MESSAGE = <<~MSG
~"pipeline:expedite" is not allowed on stable branches because it causes the `e2e:package-and-test` job to be skipped.
MSG
NEEDS_PACKAGE_AND_TEST_MESSAGE = <<~MSG
The `e2e:package-and-test` job is not present, has been canceled, or needs to be automatically triggered.
Please ensure the job is present in the latest pipeline, if necessary, retry the `danger-review` job.
Read the "QA e2e:package-and-test" section for more details.
MSG
WARN_PACKAGE_AND_TEST_MESSAGE = <<~MSG
**The `e2e:package-and-test` job needs to succeed or have approval from a Software Engineer in Test.**
Read the "QA e2e:package-and-test" section for more details.
MSG
# rubocop:disable Style/SignalException
def check!
return unless valid_stable_branch?
fail FEATURE_ERROR_MESSAGE if has_feature_label?
fail BUG_ERROR_MESSAGE unless bug_fixes_only?
warn VERSION_WARNING_MESSAGE unless targeting_patchable_version?
return if has_flaky_failure_label? || has_only_documentation_changes?
fail PIPELINE_EXPEDITE_ERROR_MESSAGE if has_pipeline_expedite_label?
status = package_and_test_bridge_and_pipeline_status
if status.nil? || FAILING_PACKAGE_AND_TEST_STATUSES.include?(status) # rubocop:disable Style/GuardClause
fail NEEDS_PACKAGE_AND_TEST_MESSAGE
else
warn WARN_PACKAGE_AND_TEST_MESSAGE unless status == 'success'
end
end
# rubocop:enable Style/SignalException
def encourage_package_and_qa_execution?
valid_stable_branch? &&
!has_only_documentation_changes? &&
!has_flaky_failure_label?
end
private
def valid_stable_branch?
!!stable_target_branch && !helper.security_mr?
end
def package_and_test_bridge_and_pipeline_status
mr_head_pipeline_id = gitlab.mr_json.dig('head_pipeline', 'id')
return unless mr_head_pipeline_id
bridge = package_and_test_bridge(mr_head_pipeline_id)
return unless bridge
if bridge['status'] == 'created'
bridge['status']
else
bridge.fetch('downstream_pipeline')&.fetch('status')
end
end
def package_and_test_bridge(mr_head_pipeline_id)
gitlab
.api
.pipeline_bridges(helper.mr_target_project_id, mr_head_pipeline_id)
&.find { |bridge| bridge['name'].include?('package-and-test') }
end
def stable_target_branch
helper.mr_target_branch.match(STABLE_BRANCH_REGEX)
end
def has_feature_label?
helper.mr_has_labels?('type::feature')
end
def has_bug_label?
helper.mr_has_labels?('type::bug')
end
def has_pipeline_expedite_label?
helper.mr_has_labels?('pipeline:expedite')
end
def has_flaky_failure_label?
helper.mr_has_labels?('failure::flaky-test')
end
def bug_fixes_only?
has_bug_label? || has_only_documentation_changes?
end
def has_only_documentation_changes?
categories_changed = helper.changes_by_category.keys
return false unless categories_changed.size == 1
return true if categories_changed.first == :docs
false
end
def targeting_patchable_version?
raise VersionApiError if last_three_minor_versions.empty?
last_three_minor_versions.include?(targeted_version)
rescue VersionApiError
warn FAILED_VERSION_REQUEST_MESSAGE
true
end
def last_three_minor_versions
return [] unless versions
current_version = versions.first.match(VERSION_REGEX)
version_1 = previous_minor_version(current_version)
version_2 = previous_minor_version(version_1)
[
version_to_minor_string(current_version),
version_to_minor_string(version_1),
version_to_minor_string(version_2)
]
end
def targeted_version
stable_target_branch[1].tr('-', '.')
end
def versions(page = 1)
version_api_endpoint = "https://version.gitlab.com/api/v1/versions?per_page=50&page=#{page}"
response = HTTParty.get(version_api_endpoint) # rubocop:disable Gitlab/HTTParty
raise VersionApiError unless response.success?
version_list = response.parsed_response.map { |v| v['version'] } # rubocop:disable Rails/Pluck
version_list.sort_by { |v| Gem::Version.new(v) }.reverse
end
def previous_minor_version(version)
previous_minor = version[:minor].to_i - 1
return "#{version[:major]}.#{previous_minor}".match(VERSION_REGEX) if previous_minor >= 0
fetch_last_minor_version_for_major(version[:major].to_i - 1)
end
def fetch_last_minor_version_for_major(major)
page = 1
last_minor_version = nil
while last_minor_version.nil?
last_minor_version = versions(page).find do |version|
version.split('.').first.to_i == major
end
break if page > 10
page += 1
end
raise VersionApiError if last_minor_version.nil?
last_minor_version.match(VERSION_REGEX)
end
def version_to_minor_string(version)
"#{version[:major]}.#{version[:minor]}"
end
end
end
end
|