lib/release_tools/security/merge_requests_validator.rb (85 lines of code) (raw):
# frozen_string_literal: true
module ReleaseTools
module Security
# Validating of multiple security merge requests in batches
class MergeRequestsValidator
include ::SemanticLogger::Loggable
include ParallelMethods
ERROR_TEMPLATE = <<~TEMPLATE.strip
@%<author_username>s
This security merge request does not meet our requirements for
security merge requests. Please take the following steps to ensure
this merge request can be merged:
1. Resolve all the errors listed below
2. Assign the merge request back to @%<bot_username>s as an Assignee
3. Make sure your security implementation issue has the ~"security-target" label
applied or your issue will not be linked to the security tracking issue.
See https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/engineer.md#process
for the full process.
## Errors
The following errors were detected:
%<errors>s
#{Messaging::COMMENT_FOOTNOTE}
TEMPLATE
# @param [ReleaseTools::Security::Client|ReleaseTools::Security::DevClient] client
def initialize(client)
@client = client
@valid = []
@invalid = []
end
# Validates all security merge requests, returning those that were valid.
#
# The valid and invalid merge requests are returned so that other code can
# use these MRs, for example by merging them.
#
# The return value is an Array of Arrays, in the following format:
#
# [
# [valid_merge_request1, valid_merge_request2, ...],
# [invalid_merge_request1, invalid_merge_request2, ...]
# ]
#
def execute(merge_requests: [])
validate_merge_requests(merge_requests)
[@valid, @invalid]
end
# @param [Gitlab::ObjectifiedHash] basic_mr
def validate_merge_request(basic_mr)
logger.trace(__method__, merge_request: basic_mr.web_url)
# Merge requests retrieved using the MR list API do not include all data
# we need, such as pipeline details. To work around this we must perform
# an additional request for every merge request to get this data.
mr = @client.merge_request(basic_mr.project_id, basic_mr.iid)
validator = MergeRequestValidator.new(mr, @client)
validator.validate
if validator.errors.any?
reassign_with_errors(mr, validator.errors)
cancel_mwps(mr)
[false, mr]
else
[true, mr]
end
end
# @param [Gitlab::ObjectifiedHash] mr
# @param [Array<String>] errors
def reassign_with_errors(mr, errors)
logger.trace(__method__, merge_request: mr.web_url, errors: errors.count)
return if SharedStatus.dry_run?
project_id = mr.project_id
iid = mr.iid
sec_release_tracking_issue_search_link = 'https://gitlab.com/gitlab-org/gitlab/-/issues?label_name%5B%5D=upcoming%20security%20release'
@client.create_merge_request_note(
project_id,
iid,
format(
ERROR_TEMPLATE,
author_username: mr.author.username,
bot_username: Client::RELEASE_TOOLS_BOT_USERNAME,
sec_release_tracking_issue_search_link: sec_release_tracking_issue_search_link,
errors: errors.join("\n\n")
)
)
@client.update_merge_request(project_id, iid, assignee_id: mr.author.id)
end
private
def cancel_mwps(merge_request)
return unless merge_request.merge_when_pipeline_succeeds
return if SharedStatus.dry_run?
logger.info('Canceling MWPS', merge_request: merge_request.web_url)
Retriable.with_context(:api) do
@client.cancel_merge_when_pipeline_succeeds(merge_request)
end
rescue ::Gitlab::Error::MethodNotAllowed, ::Gitlab::Error::NotAcceptable
# 405 MethodNotAllowed is returned when the MR is already merged or closed
# 406 NotAcceptable is returned when MWPS is not set
# We can ignore these errors and continue in these cases
logger.info('MWPS is not set or MR is already merged or closed. Skipping.')
nil
end
def validate_merge_requests(merge_requests)
parallel_map(merge_requests) do |merge_request|
is_valid, mr = validate_merge_request(merge_request)
if is_valid
@valid << mr
else
@invalid << mr
end
end
end
end
end
end