#!/usr/bin/env ruby # frozen_string_literal: false require 'active_support/core_ext/object/to_query' require 'optparse' require 'open3' require 'rainbow/refinement' using Rainbow module Secpick BRANCH_PREFIX = 'security'.freeze STABLE_SUFFIX = 'stable'.freeze DEFAULT_REMOTE = 'dev'.freeze SECURITY_REMOTE = 'security'.freeze NEW_MR_URL = 'https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/new'.freeze SECURITY_MR_URL = 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/new'.freeze class SecurityFix def initialize @options = self.class.options end def ee? File.exist?(File.expand_path('../ee/app/models/license.rb', __dir__)) end def dry_run? @options[:try] == true end def original_branch @options[:branch].strip end def source_branch branch = "#{original_branch}-#{@options[:version]}" branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-") branch.freeze end def stable_branch "#{@options[:version]}-#{STABLE_SUFFIX}".tap do |name| name << "-ee" if ee? end.freeze end def git_commands ["git fetch #{@options[:remote]} #{stable_branch}", "git checkout -B #{source_branch} #{@options[:remote]}/#{stable_branch} --no-track", "git cherry-pick #{@options[:sha]}", "git push #{@options[:remote]} #{source_branch}", "git checkout #{original_branch}"] end def gitlab_params { issuable_template: 'Security Release', merge_request: { source_branch: source_branch, target_branch: stable_branch } } end def new_mr_url if @options[:security_remote] if ee? SECURITY_MR_URL else SECURITY_MR_URL.sub('/gitlab/', '/gitlab-foss/') end else if ee? NEW_MR_URL.sub('gitlabhq', 'gitlab-ee') else NEW_MR_URL end end end def create! if dry_run? puts "\nGit commands:".blue puts git_commands.join("\n") puts "\nMerge request URL:".blue puts new_mr_url puts "\nMerge request params:".blue pp gitlab_params else cmd = git_commands.join(' && ') stdin, stdout, stderr, wait_thr = Open3.popen3(cmd) puts stdout.read&.green puts stderr.read&.red if wait_thr.value.success? puts "#{new_mr_url}?#{gitlab_params.to_query}".blue end stdin.close stdout.close stderr.close end end def self.options { version: nil, branch: nil, sha: nil }.tap do |options| parser = OptionParser.new do |opts| opts.banner = "Usage: #{$0} [options]" opts.on('-v', '--version 10.0', 'Version') do |version| options[:version] = version&.tr('.', '-') end opts.on('-b', '--branch security-fix-branch', 'Original branch name (optional, defaults to current)') do |branch| options[:branch] = branch end opts.on('-s', '--sha abcd', 'SHA or SHA range to cherry pick') do |sha| options[:sha] = sha end opts.on('-r', '--remote abcd', 'Git remote name of dev.gitlab.org (optional, defaults to `dev`)') do |remote| options[:remote] = remote end opts.on('--security-remote', 'Use the new Security group-based workflow on gitlab.com (note: mutually exclusive to --remote)') do unless options[:remote].nil? abort('Cannot use --security-remote with --remote') end options[:security_remote] = true options[:remote] = SECURITY_REMOTE end opts.on('-d', '--dry-run', 'Only show Git commands, without calling them') do options[:try] = true end opts.on('-h', '--help', 'Displays Help') do puts opts puts puts 'NOTE: If `--security-remote` is used, commands will default ' \ 'to using a `security` remote, and merge requests will be created ' \ 'on gitlab.com/gitlab-org/security/ rather than dev.gitlab.org.' exit end end parser.parse! options[:branch] ||= `git rev-parse --abbrev-ref HEAD` options[:remote] ||= DEFAULT_REMOTE abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.value?(nil) abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/ end end end end Secpick::SecurityFix.new.create!