diff options
author | 2011-05-17 17:27:55 +0000 | |
---|---|---|
committer | 2011-05-17 17:27:55 +0000 | |
commit | 22546d7465a9c58a7bb3487d5611b33e93b1f6cc (patch) | |
tree | 3e8f0dee105a1b0b88c4103d7973e0c0b42c67c7 | |
parent | New target tool (diff) | |
download | security-22546d7465a9c58a7bb3487d5611b33e93b1f6cc.tar.gz security-22546d7465a9c58a7bb3487d5611b33e93b1f6cc.tar.bz2 security-22546d7465a9c58a7bb3487d5611b33e93b1f6cc.zip |
acutally add the new tool
svn path=/; revision=2228
-rwxr-xr-x | bin/target | 346 |
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 |