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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
|
#!/usr/bin/env ruby
# Target 2
# written by Alex Legler <a3li@gentoo.org>
# dependencies: app-portage/gentoolkit, dev-lang/ruby[ssl], dev-ruby/highline
# vim: set sw=2 ts=2:
require 'optparse'
require 'highline'
require 'fileutils'
require 'xmlrpc/client'
class Net::HTTP
alias_method :old_initialize, :initialize
def initialize(*args)
old_initialize(*args)
@ssl_context = OpenSSL::SSL::SSLContext.new
@ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
end
module GenSec
module Target
# These architectures don't stabilize packages
NOSTABLE_ARCHES = ['mips']
def main(argv)
$opts = {
:debug => false,
:force => false,
:liaisons => false,
:username => nil,
:prestable => false,
:quiet => false
}
$ui = HighLine.new
bug = nil
version = nil
slot = nil
optparse = OptionParser.new do |opts|
opts.on('-b', '--bug BUGNO', 'The number of the bug to change') do |b|
bug = Integer(b)
end
opts.on('-v', '--version VERSION', 'Use this version as stabilization target') do |v|
version = v
end
opts.on('-s', '--slot SLOT', 'Use ebuilds from this slot to find the best ebuild') do |s|
slot = s
end
opts.on('-l', '--liaisons', 'CC the arch liaisons instead of arch teams') do
$opts[:liaisons] = true
end
opts.on('-p', '--prestable', 'Use prestabling instructions') do
$opts[:prestable] = true
end
opts.on('-u', '--username USERNAME', 'Use this user name to log in at Bugzilla') do |username|
$opts[:username] = username
end
opts.on_tail('--debug', 'Print debug output.') do
$opts[:debug] = true
end
opts.on_tail('-f', '--force', 'Force the operation. Disables asking for confirmation and version checks.') do
$opts[:force] = true
end
opts.on_tail('-q', '--quiet', 'Be less noisy') do
$opts[:quiet] = true
end
opts.on_tail('-h', '--help', 'Display this screen') do
puts opts
exit
end
end
optparse.banner = "Usage: #{$0} [options] [package]\n\nAvailable options:\n"
cmd_options = optparse.parse!(argv)
if argv.length > 0
package = argv.shift
else
package = Dir.pwd.split('/').last(2).join('/')
end
metadata = get_metadata(package)
do_package(metadata, bug, version, slot)
end
def do_package(metadata, bug, version, slot)
if metadata[:package] == nil or metadata[:package] == ''
e("No package found.")
end
i("Package: #{$ui.color(metadata[:package], :green)}") unless $opts[:quiet]
if $opts[:debug]
require 'pp'
pp metadata
end
best_version = find_best_version(metadata, slot, version)
i("Target version: #{$ui.color(best_version, :green)}") unless $opts[:quiet]
# Cover a custom version string that is not there in the local tree
if metadata[:versions].include? best_version
already_stable = filter_unstable(metadata[:keywords][best_version]) - NOSTABLE_ARCHES
else
w($ui.color("Warning: Target version not found. Proceed with care.", :yellow))
already_stable = []
end
need_stable = filter_negative_keywords(metadata[:stable_arches] - NOSTABLE_ARCHES)
i("Arches this package was ever stable on: #{$ui.color(need_stable.join(', '), :red, :bold)}") unless $opts[:quiet]
if already_stable.length > 0
i("Target version is already stable on: #{$ui.color(already_stable.join(', '), :green, :bold)}") unless $opts[:quiet]
end
if $opts[:prestable]
msg = "Arch Security Liaisons, please test the attached ebuild and report it stable on this bug.\n"
elsif $opts[:liaisons] and not $opts[:prestable]
msg = "Arch Security Liaisons, please test and mark stable:\n"
else
msg = "Arches, please test and mark stable:\n"
end
if not $opts[:prestable]
msg += "=%s-%s\n" % [metadata[:package], best_version]
end
msg += "Target keywords : \"%s\"\n" % need_stable.join(' ')
if already_stable.length > 0 and not $opts[:prestable]
msg += "Already stable : \"%s\"\n" % (already_stable.join(' '))
msg += "Missing keywords: \"%s\"\n" % (metadata[:stable_arches] - already_stable).join(' ')
end
puts
puts msg
puts
if $opts[:liaisons]
require File.join(File.dirname(__FILE__), 'liaisons')
cc_list = need_stable.map {|arch| @liaisons[arch]}.flatten.map {|liaison| "#{liaison}@gentoo.org"}
else
cc_list = need_stable.map {|arch| "#{arch}@gentoo.org" }
end
puts "CC: %s" % cc_list.join(',')
exit if bug == nil
bugi = bug_info(bug)
new_whiteboard = update_whiteboard(bugi['whiteboard'])
puts "Whiteboard: '%s' -> '%s'" % [bugi['whiteboard'], new_whiteboard]
puts
if $opts[:force] or $ui.agree('Continue? (yes/no)')
update_bug(bug, new_whiteboard, cc_list, msg)
end
end
# Collects metadata information from equery meta
def get_metadata(ebuild = Dir.pwd.split('/').last(2).join('/'))
keywords = IO.popen("equery --no-color --no-pipe meta --keywords #{ebuild}")
result = {:slots => {}, :keywords => {}, :stable_arches => [], :versions => []}
keywords.lines.each do |line|
if line =~ /^ \* (\S*?)\/(\S*?) \[([^\]]*)\]$/
result[:package] = "#{$1}/#{$2}"
result[:repo] = $3
next
end
if line =~ /^(.*?):(.*?):(.*?)$/
version, slot, kws = $1, $2, $3
result[:versions] << version
result[:slots][slot] = [] unless result[:slots].include? slot
result[:slots][slot] << version
result[:keywords][version] = []
kws.strip.split(' ').each do |arch|
result[:keywords][version] << arch
if arch =~ /^[^~]*$/
result[:stable_arches] << arch
end
end
result[:keywords][version].sort!
next
end
raise RuntimeError, "Invalid line in equery output. Aborting."
end
result[:stable_arches].uniq!
result[:stable_arches].sort!
result
end
# Tries to find the best version following the needed specification
def find_best_version(metadata, slot, version)
if slot == nil and version == nil
return metadata[:versions].reject {|item| item =~ /^9999/}.last
elsif slot == nil
return version
else
if version == nil
return metadata[:slots][slot].reject {|item| item =~ /^9999/}.last
elsif metadata[:slots][slot].include?(version)
return version
else
return false
end
end
end
def update_whiteboard(old_wb)
old_wb.gsub(/(ebuild\+?|upstream\+?|stable)\??/, 'stable').gsub(/stable\/stable/, 'stable')
end
def update_bug(bug, whiteboard, cc_list, comment)
i("Updating bug #{bug}...")
client = xmlrpc_client
did_retry = false
begin
result = client.call('Bug.update', {
'ids' => [Integer(bug)],
'whiteboard' => whiteboard,
'cc' => {'add' => cc_list},
'keywords' => {'add' => 'STABLEREQ'},
'status' => 'IN_PROGRESS',
'comment' => {'body' => comment}
})
i("done!")
return true
rescue XMLRPC::FaultException => e
if did_retry
e "Failure updating bug information: #{e.message}"
return false
end
if e.faultCode == 410
log_in
did_retry = true
retry
else
e "Failure updating bug information: #{e.message}"
end
end
end
def bug_info(bugno)
client = xmlrpc_client
did_retry = false
begin
result = client.call('Bug.get', {'ids' => [Integer(bugno)]})
result['bugs'].first
rescue XMLRPC::FaultException => e
if did_retry
e "Failure reading bug information: #{e.message}"
return false
end
if e.faultCode == 410
log_in
did_retry = true
retry
else
e "Failure reading bug information: #{e.message}"
end
end
end
def log_in
client = xmlrpc_client
if $opts[:username] == nil
user = $ui.ask("Bugzilla login: ")
else
user = $opts[:username]
end
password = $ui.ask("Password: ") {|q| q.echo = false}
begin
i("Logging in...")
result = client.call('User.login', {
'login' => user,
'password' => password
})
cookie_file = File.join(ENV['HOME'], '.gensec-target-auth')
FileUtils.rm(cookie_file) if File.exist?(cookie_file)
FileUtils.touch(cookie_file)
File.chmod(0600, cookie_file)
File.open(cookie_file, 'w') {|f| f.write client.cookie }
return true
rescue XMLRPC::FaultException => e
e "Failure logging in: #{e.message}"
return false
end
end
def xmlrpc_client
client = XMLRPC::Client.new('bugs.gentoo.org', '/xmlrpc.cgi', 443, nil, nil, nil, nil, true)
client.http_header_extra = {'User-Agent' => "Target/2.0 (arch CC tool; http://security.gentoo.org/)"}
cookie_file = File.join(ENV['HOME'], '.gensec-target-auth')
if File.readable? cookie_file
client.cookie = File.read(cookie_file)
end
client
end
# Output and misc methods
def i(str)
$ui.say($ui.color(" * ", :green, :bold) + str)
end
def w(str)
$ui.say($ui.color(" * ", :yellow, :bold) + str)
end
def e(str)
$ui.say($ui.color(" * ", :red, :bold) + str)
exit 1
end
def filter_unstable(ary)
ary.reject {|item| item =~ /^[~-]/}
end
def filter_negative_keywords(ary)
ary.reject {|item| item =~ /^[-]/}
end
end
end
if __FILE__ == $0
include GenSec::Target
main(ARGV)
end
|