aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'bin/target')
-rwxr-xr-xbin/target346
1 files changed, 346 insertions, 0 deletions
diff --git a/bin/target b/bin/target
new file mode 100755
index 0000000..22001dd
--- /dev/null
+++ b/bin/target
@@ -0,0 +1,346 @@
+#!/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 = {
+ :auth_cache => true,
+ :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('-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("Using #{metadata[:package]}") unless $opts[:quiet]
+ #puts metadata.inspect
+
+ best_version = find_best_version(metadata, slot, version)
+ i("Target version: #{best_version}") unless $opts[:quiet]
+
+ # Cover a custom version string that is not there in the local tree
+ if metadata[:keywords].include? best_version
+ already_stable = filter_unstable(metadata[:keywords][best_version]) - NOSTABLE_ARCHES
+ else
+ already_stable = []
+ end
+
+ need_stable = 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" % metadata[:stable_arches].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
+ end
+end
+
+if __FILE__ == $0
+ include GenSec::Target
+ main(ARGV)
+end