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
|
#!/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 (optional, defaults to current)') 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[:sha] ||= `git rev-parse HEAD`
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!
|