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