lib/release_tools/patch_release/blog_merge_request.rb (226 lines of code) (raw):

# frozen_string_literal: true module ReleaseTools module PatchRelease class BlogMergeRequest < MergeRequest include ::SemanticLogger::Loggable include ReleaseTools::Security::IssueHelper include ReleaseTools::Security::MergeRequestHelper # Projects that are not included in the table of fixes because their # fixes are often due to external CVEs NON_TABLE_OF_FIXES_PROJECTS = [ Project::OmnibusGitlab.security_id, Project::CNGImage.security_id ].freeze def project ReleaseTools::Project::WWWGitlabCom end def labels 'patch release post' end def title "Draft: Adding #{versions_str} blog post" end def assignee_ids if security_fixes? appsec_release_managers.collect(&:id) else release_managers.collect(&:id) end end def source_branch "create-#{hyphenated_version}-post" end def create create_blog_post_file return if SharedStatus.dry_run? super end def patch_issue_url return 'test.gitlab.com' if SharedStatus.dry_run? return unless patch_coordinator.single_version? Issue.new(version: versions.first).url end def generate_blog_content ERB.new(content_template_path, trim_mode: '-').result(binding) end def blog_post_filename if security_fixes? "#{Date.current + 1.day}-#{release_name_hyphen}-gitlab-#{hyphenated_version}-released.html.md" else "#{Date.current}-gitlab-#{hyphenated_version}-released.html.md" end end def metadata_canonical_path return if patch_coordinator.single_version? date = Time.now.utc + 1.day "/releases/#{date.strftime('%Y/%m/%d')}/#{release_name_hyphen}-gitlab-#{hyphenated_version}-released/" end def security_issue_header(security_issue) security_issue&.cves_issue&.title || security_issue.issue.title end def security_issue_blog_content(security_issue) cves_issue = security_issue.cves_issue if cves_issue && cves_issue.valid_yaml? <<~STR #{cves_issue.vulnerability_description.gsub(/[.]+\z/, '')}. This is a #{cves_issue.cvss_severity.downcase} severity issue ([`#{cves_issue.cvss_string}`](https://gitlab-com.gitlab.io/gl-security/product-security/appsec/cvss-calculator/explain#explain=#{cves_issue.cvss_string}), #{cves_issue.cvss_base_score}). #{cves_id_link(cves_issue)} #{cves_issue.credit.gsub(/[.]+\z/, '')}. STR else logger.warn "The content of the security issue (#{security_issue.web_url}) was skipped due to invalid CVES YAML or because of missing CVES." <<~STR TODO: add description for this issue STR end end def cves_id_link(cves_issue) if cves_issue.cve_id.blank? "We have requested a CVE ID and will update this blog post when it is assigned." else "It is now mitigated in the latest release and is assigned [#{cves_issue.cve_id}](https://cve.mitre.org/cgi-bin/cvename.cgi?name=#{cves_issue.cve_id})." end end def project_path # If the blog post contains security content, the blog MR needs # to be created in the security mirror. return project.security_path if security_fixes? super end def sorted_security_content security_fixes .sort_by do |issue| if issue.cves_issue&.valid_yaml? issue.cves_issue&.cvss_base_score || -1 else -1 end end .reverse end def sorted_table_content sorted_security_content.reject do |issue| NON_TABLE_OF_FIXES_PROJECTS.include?(issue.project_id) end end def security_issue_slug(security_issue) security_issue_header(security_issue) .downcase .scan(/\w|\d| |-/) .join .tr(' ', '-') end def displayed_severity(security_issue) return 'TODO' unless security_issue.cves_issue&.valid_yaml? severity = security_issue.cves_issue&.cvss_severity return 'TODO' if severity == 'None' severity end def blog_metadata_title "GitLab #{title_header} #{release_name}: #{versions_str}".squish end def blog_metadata_description "Learn more about GitLab #{title_header} #{release_name}: #{versions_str} for GitLab Community Edition (CE) and Enterprise Edition (EE).".squish end def blog_metadata_tags 'security' unless patch_coordinator.single_version? end def exists? if security_fixes? security_blog_merge_request.present? else super end end def bug_fixes patch_coordinator.merge_requests(with_patch_version: true) end def security_fixes? security_fixes.present? end def bug_fixes? patch_coordinator.pressure.positive? end protected def release_name 'Patch Release' end def release_name_hyphen release_name.downcase.tr(' ', '-') end def hyphenated_version versions.first.tr(', ', '--').tr('.', '-') end def template_path File.expand_path('../../../templates/patch_blog_post_merge_request_description.md.erb', __dir__) end def content_template_path file_name = if patch_coordinator.single_version? 'patch_release_single_version_template' else 'patch_release_template' end File.read("templates/blog_posts/#{file_name}.html.md.erb") end def create_blog_post_file file_content = generate_blog_content filepath = blog_post_filepath + blog_post_filename logger.info('Created blog post file') unless SharedStatus.dry_run? Retriable.with_context(:api) do GitlabClient.find_or_create_branch(source_branch, target_branch, project_path) end end logger.info('Created branch', branch_name: source_branch, project: project_path) commit(file_content, filepath) end def commit(file_content, filepath, action = 'create', message = nil) message ||= "Adding #{versions_str} blog post" actions = [{ action: action, file_path: filepath, content: file_content }] logger.info('Committing blog content', project: project_path, branch: source_branch) return if SharedStatus.dry_run? Retriable.with_context(:api) do GitlabClient.create_commit( project_path, source_branch, message, actions ) end end def blog_post_filepath "sites/uncategorized/source/releases/posts/" end def release_managers schedule.active_release_managers rescue ReleaseManagers::Schedule::VersionNotFoundError logger.fatal('Could not find active release managers') nil end def appsec_release_managers schedule.active_appsec_release_managers rescue ReleaseManagers::Schedule::VersionNotFoundError logger.fatal('Could not find active appsec release managers') nil end def schedule @schedule ||= ReleaseManagers::Schedule.new end def versions return [bug_fixes.first[:version]] if patch_coordinator.single_version? if security_fixes? bug_fixes.map { |release| release[:version] } else bug_fixes.filter_map do |release| release[:version] if release[:pressure].positive? end end end def versions_str versions.join(', ') end def critical_patch_release? ReleaseTools::SharedStatus.critical_patch_release? end def title_header critical_patch_release? ? 'Critical' : '' end end end end