diff options
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/addcustomfield.pl | 63 | ||||
-rwxr-xr-x | scripts/bugzilla-queue.rhel | 115 | ||||
-rwxr-xr-x | scripts/clear-memcached.pl | 27 | ||||
-rwxr-xr-x | scripts/clear-templates.pl | 40 | ||||
-rwxr-xr-x | scripts/fix_all_open_status_queries.pl | 144 | ||||
-rwxr-xr-x | scripts/fixgroupqueries.pl | 123 | ||||
-rwxr-xr-x | scripts/fixperms.pl | 28 | ||||
-rwxr-xr-x | scripts/fixqueries.pl | 136 | ||||
-rwxr-xr-x | scripts/issue-api-key.pl | 33 | ||||
-rwxr-xr-x | scripts/merge-users.pl | 271 | ||||
-rwxr-xr-x | scripts/moco-ldap-check.pl | 542 | ||||
-rwxr-xr-x | scripts/move_flag_types.pl | 172 | ||||
-rwxr-xr-x | scripts/move_os.pl | 81 | ||||
-rwxr-xr-x | scripts/movebugs.pl | 190 | ||||
-rwxr-xr-x | scripts/movecomponent.pl | 154 | ||||
-rwxr-xr-x | scripts/nagios_blocker_checker.pl | 201 | ||||
-rwxr-xr-x | scripts/nuke-bugs.pl | 69 | ||||
-rwxr-xr-x | scripts/reassign_open_bugs.pl | 87 | ||||
-rwxr-xr-x | scripts/reset_default_user.pl | 145 | ||||
-rwxr-xr-x | scripts/sanitizeme.pl | 230 | ||||
-rwxr-xr-x | scripts/sendunsentbugmail.pl | 60 | ||||
-rwxr-xr-x | scripts/syncflags.pl | 88 | ||||
-rwxr-xr-x | scripts/syncmsandversions.pl | 122 |
23 files changed, 3121 insertions, 0 deletions
diff --git a/scripts/addcustomfield.pl b/scripts/addcustomfield.pl new file mode 100755 index 000000000..886d1ac5c --- /dev/null +++ b/scripts/addcustomfield.pl @@ -0,0 +1,63 @@ +#!/usr/bin/perl -wT +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# Contributor(s): Frédéric Buclin <LpSolit@gmail.com> +# David Miller <justdave@mozilla.com> + +use strict; +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Field; + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +my %types = ( + 'freetext' => FIELD_TYPE_FREETEXT, + 'single_select' => FIELD_TYPE_SINGLE_SELECT, + 'multi_select' => FIELD_TYPE_MULTI_SELECT, + 'textarea' => FIELD_TYPE_TEXTAREA, + 'datetime' => FIELD_TYPE_DATETIME, + 'date' => FIELD_TYPE_DATE, + 'bug_id' => FIELD_TYPE_BUG_ID, + 'bug_urls' => FIELD_TYPE_BUG_URLS, + 'keywords' => FIELD_TYPE_KEYWORDS, +); + +my $syntax = + "syntax: addcustomfield.pl <field name> [field type]\n\n" . + "valid field types:\n " . join("\n ", sort keys %types) . "\n\n" . + "the default field type is single_select\n"; + +my $name = shift || die $syntax; +my $type = lc(shift || 'single_select'); +exists $types{$type} || die "Invalid field type '$type'.\n\n$syntax"; +$type = $types{$type}; + +Bugzilla::Field->create({ + name => $name, + description => 'Please give me a description!', + type => $type, + mailhead => 0, + enter_bug => 0, + obsolete => 1, + custom => 1, + buglist => 1, +}); +print "Done!\n"; + +my $urlbase = Bugzilla->params->{urlbase}; +print "Please visit ${urlbase}editfields.cgi?action=edit&name=$name to finish setting up this field.\n"; diff --git a/scripts/bugzilla-queue.rhel b/scripts/bugzilla-queue.rhel new file mode 100755 index 000000000..dd7a2c664 --- /dev/null +++ b/scripts/bugzilla-queue.rhel @@ -0,0 +1,115 @@ +#!/bin/bash +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. +# +# bugzilla-queue This starts, stops, and restarts the Bugzilla jobqueue.pl +# daemon, which manages sending queued mail and possibly +# other queued tasks in the future. +# +# chkconfig: 345 85 15 +# description: Bugzilla queue runner +# +### BEGIN INIT INFO +# Provides: bugzilla-queue +# Required-Start: $local_fs $syslog MTA mysqld +# Required-Stop: $local_fs $syslog MTA mysqld +# Default-Start: 3 5 +# Default-Stop: 0 1 2 6 +# Short-Description: Start and stop the Bugzilla queue runner. +# Description: The Bugzilla queue runner (jobqueue.pl) sends any mail +# that Bugzilla has queued to be sent in the background. If you +# have enabled the use_mailer_queue parameter in Bugzilla, you +# must run this daemon. +### END INIT INFO + +NAME=`basename $0` + +################# +# Configuration # +################# + +# This should be the path to your Bugzilla +BUGZILLA=/var/www/html/bugzilla +# Who owns the Bugzilla directory and files? +USER=root + +# If you want to pass any options to the daemon (like -d for debugging) +# specify it here. +OPTIONS="" + +# You can also override the configuration by creating a +# /etc/sysconfig/bugzilla-queue file so that you don't +# have to edit this script. +if [ -r /etc/sysconfig/$NAME ]; then + . /etc/sysconfig/$NAME +fi + +########## +# Script # +########## + +RETVAL=0 +BIN=$BUGZILLA/jobqueue.pl +PIDFILE=/var/run/$NAME.pid + +# Source function library. +. /etc/rc.d/init.d/functions + +usage () +{ + echo "Usage: service $NAME {start|stop|status|restart|condrestart}" + RETVAL=1 +} + + +start () +{ + if [ -f "$PIDFILE" ]; then + checkpid `cat $PIDFILE` && return 0 + fi + echo -n "Starting $NAME: " + touch $PIDFILE + chown $USER $PIDFILE + daemon --user=$USER \ + "$BIN ${OPTIONS} -p '$PIDFILE' -n $NAME start > /dev/null" + ret=$? + [ $ret -eq "0" ] && touch /var/lock/subsys/$NAME + echo + return $ret +} + +stop () +{ + [ -f /var/lock/subsys/$NAME ] || return 0 + echo -n "Killing $NAME: " + killproc $NAME + echo + rm -f /var/lock/subsys/$NAME +} + +restart () +{ + stop + start +} + +condrestart () +{ + [ -e /var/lock/subsys/$NAME ] && restart || return 0 +} + + +case "$1" in + start) start; RETVAL=$? ;; + stop) stop; RETVAL=$? ;; + status) $BIN -p $PIDFILE -n $NAME check; RETVAL=$?;; + restart) restart; RETVAL=$? ;; + condrestart) condrestart; RETVAL=$? ;; + *) usage ; RETVAL=2 ;; +esac + +exit $RETVAL diff --git a/scripts/clear-memcached.pl b/scripts/clear-memcached.pl new file mode 100755 index 000000000..01202ce7c --- /dev/null +++ b/scripts/clear-memcached.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use strict; +use warnings; + +use FindBin qw($Bin); +use lib "$Bin/.."; +use lib "$Bin/../lib"; + +use Bugzilla; +use Bugzilla::Constants; + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +if (Bugzilla->memcached->{memcached}) { + Bugzilla->memcached->clear_all(); + print "memcached cleared\n"; +} else { + print "memcached is not enabled\n"; +} diff --git a/scripts/clear-templates.pl b/scripts/clear-templates.pl new file mode 100755 index 000000000..8b0864d46 --- /dev/null +++ b/scripts/clear-templates.pl @@ -0,0 +1,40 @@ +#!/usr/bin/perl -w + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use strict; +use warnings; +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Install::Filesystem qw(fix_dir_permissions); +use File::Path qw(mkpath rmtree); + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); +$| = 1; + +# rename the current directory and create a new empty one +# the templates will lazy-compile on demand + +my $path = bz_locations()->{'template_cache'}; +my $delete_path = "$path.deleteme"; + +print "clearing $path\n"; + +rmtree("$delete_path") if -e "$delete_path"; +rename($path, $delete_path) + or die "renaming '$path' to '$delete_path' failed: $!\n"; + +mkpath($path) + or die "creating '$path' failed: $!\n"; +fix_dir_permissions($path); + +# delete the temp directory (it's ok if this fails) + +rmtree("$delete_path"); diff --git a/scripts/fix_all_open_status_queries.pl b/scripts/fix_all_open_status_queries.pl new file mode 100755 index 000000000..e10fdd44d --- /dev/null +++ b/scripts/fix_all_open_status_queries.pl @@ -0,0 +1,144 @@ +#!/usr/bin/perl -w +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use strict; + +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Status; +use Bugzilla::Util; + +sub usage() { + print <<USAGE; +Usage: fix_all_open_status_queries.pl <new_open_status> + +E.g.: fix_all_open_status_queries.pl READY +This will add a new open state to user queries which currently look for +all open bugs by listing every open status in their query criteria. +For users who only look for bug_status=__open__, they will get the new +open status automatically. +USAGE +} + +sub do_namedqueries { + my ($new_status) = @_; + my $dbh = Bugzilla->dbh; + my $replace_count = 0; + + my $query = $dbh->selectall_arrayref("SELECT id, query FROM namedqueries"); + + if ($query) { + $dbh->bz_start_transaction(); + + my $sth = $dbh->prepare("UPDATE namedqueries SET query = ? WHERE id = ?"); + + foreach my $row (@$query) { + my ($id, $old_query) = @$row; + my $new_query = all_open_states($new_status, $old_query); + if ($new_query) { + trick_taint($new_query); + $sth->execute($new_query, $id); + $replace_count++; + } + } + + $dbh->bz_commit_transaction(); + } + + print "namedqueries: $replace_count replacements made.\n"; +} + +# series +sub do_series { + my ($new_status) = @_; + my $dbh = Bugzilla->dbh; + my $replace_count = 0; + + my $query = $dbh->selectall_arrayref("SELECT series_id, query FROM series"); + + if ($query) { + $dbh->bz_start_transaction(); + + my $sth = $dbh->prepare("UPDATE series SET query = ? WHERE series_id = ?"); + + foreach my $row (@$query) { + my ($series_id, $old_query) = @$row; + my $new_query = all_open_states($new_status, $old_query); + if ($new_query) { + trick_taint($new_query); + $sth->execute($new_query, $series_id); + $replace_count++; + } + } + + $dbh->bz_commit_transaction(); + } + + print "series: $replace_count replacements made.\n"; +} + +sub all_open_states { + my ($new_status, $query) = @_; + + my @open_states = Bugzilla::Status::BUG_STATE_OPEN(); + my $cgi = Bugzilla::CGI->new($query); + my @query_states = $cgi->param('bug_status'); + + my ($removed, $added) = diff_arrays(\@query_states, \@open_states); + + if (scalar @$added == 1 && $added->[0] eq $new_status) { + push(@query_states, $new_status); + $cgi->param('bug_status', @query_states); + return $cgi->canonicalise_query(); + } + + return ''; +} + +sub validate_status { + my ($status) = @_; + my $dbh = Bugzilla->dbh; + my $exists = $dbh->selectrow_array("SELECT 1 FROM bug_status + WHERE value = ?", + undef, $status); + return $exists ? 1 : 0; +} + +############################################################################# +# MAIN CODE +############################################################################# +# This is a pure command line script. +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +if (scalar @ARGV < 1) { + usage(); + exit(1); +} + +my ($new_status) = @ARGV; + +$new_status = uc($new_status); + +if (!validate_status($new_status)) { + print "Invalid status: $new_status\n\n"; + usage(); + exit(1); +} + +print "Adding new status '$new_status'.\n\n"; + +do_namedqueries($new_status); +do_series($new_status); + +# It's complex to determine which items now need to be flushed from memcached. +# As this is expected to be a rare event, we just flush the entire cache. +Bugzilla->memcached->clear_all(); + +exit(0); diff --git a/scripts/fixgroupqueries.pl b/scripts/fixgroupqueries.pl new file mode 100755 index 000000000..13dd0cb3e --- /dev/null +++ b/scripts/fixgroupqueries.pl @@ -0,0 +1,123 @@ +#!/usr/bin/perl -w +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Netscape Communications +# Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Gervase Markham <gerv@gerv.net> + +use strict; + +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Util; + +sub usage() { + print <<USAGE; +Usage: fixgroupqueries.pl <oldvalue> <newvalue> + +E.g.: fixgroupqueries.pl w-security webtools-security +will change all occurrences of "w-security" to "webtools-security" in the +appropriate places in the namedqueries. + +Note that all parameters are case-sensitive. +USAGE +} + +sub do_namedqueries($$) { + my ($old, $new) = @_; + $old = url_quote($old); + $new = url_quote($new); + + my $dbh = Bugzilla->dbh; + + my $replace_count = 0; + my $query = $dbh->selectall_arrayref("SELECT id, query FROM namedqueries"); + if ($query) { + my $sth = $dbh->prepare("UPDATE namedqueries SET query = ? + WHERE id = ?"); + + foreach my $row (@$query) { + my ($id, $query) = @$row; + if (($query =~ /field\d+-\d+-\d+=bug_group/) && + ($query =~ /(?:^|&|;)value\d+-\d+-\d+=$old(?:;|&|$)/)) { + $query =~ s/((?:^|&|;)value\d+-\d+-\d+=)$old(;|&|$)/$1$new$2/; + $sth->execute($query, $id); + $replace_count++; + } + } + } + + print "namedqueries: $replace_count replacements made.\n"; +} + +# series +sub do_series($$) { + my ($old, $new) = @_; + $old = url_quote($old); + $new = url_quote($new); + + my $dbh = Bugzilla->dbh; + #$dbh->bz_start_transaction(); + + my $replace_count = 0; + my $query = $dbh->selectall_arrayref("SELECT series_id, query + FROM series"); + if ($query) { + my $sth = $dbh->prepare("UPDATE series SET query = ? + WHERE series_id = ?"); + foreach my $row (@$query) { + my ($series_id, $query) = @$row; + + if (($query =~ /field\d+-\d+-\d+=bug_group/) && + ($query =~ /(?:^|&|;)value\d+-\d+-\d+=$old(?:;|&|$)/)) { + $query =~ s/((?:^|&|;)value\d+-\d+-\d+=)$old(;|&|$)/$1$new$2/; + $sth->execute($query, $series_id); + $replace_count++; + } + } + } + + #$dbh->bz_commit_transaction(); + print "series: $replace_count replacements made.\n"; +} + +############################################################################# +# MAIN CODE +############################################################################# +# This is a pure command line script. +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +if (scalar @ARGV < 2) { + usage(); + exit(); +} + +my ($old, $new) = @ARGV; + +print "Changing all instances of '$old' to '$new'.\n\n"; + +#do_namedqueries($old, $new); +do_series($old, $new); + +# It's complex to determine which items now need to be flushed from memcached. +# As this is expected to be a rare event, we just flush the entire cache. +Bugzilla->memcached->clear_all(); + +exit(0); diff --git a/scripts/fixperms.pl b/scripts/fixperms.pl new file mode 100755 index 000000000..406c149cb --- /dev/null +++ b/scripts/fixperms.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl -w +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Everything Solved, Inc. +# Portions created by the Initial Developer are Copyright (C) 2010 the +# Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Max Kanat-Alexander <mkanat@bugzilla.org> + +use strict; +use warnings; +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Install::Filesystem qw(fix_all_file_permissions); +fix_all_file_permissions(1); diff --git a/scripts/fixqueries.pl b/scripts/fixqueries.pl new file mode 100755 index 000000000..1fe25f261 --- /dev/null +++ b/scripts/fixqueries.pl @@ -0,0 +1,136 @@ +#!/usr/bin/perl -w +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Netscape Communications +# Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Gervase Markham <gerv@gerv.net> + +use strict; + +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Util; + +sub usage() { + print <<USAGE; +Usage: fixqueries.pl <parameter> <oldvalue> <newvalue> + +E.g.: fixqueries.pl product FoodReplicator SeaMonkey +will change all occurrences of "FoodReplicator" to "Seamonkey" in the +appropriate places in the namedqueries, series and series_categories tables. + +Note that all parameters are case-sensitive. +USAGE +} + +sub do_namedqueries($$$) { + my ($field, $old, $new) = @_; + $old = url_quote($old); + $new = url_quote($new); + + my $dbh = Bugzilla->dbh; + #$dbh->bz_start_transaction(); + + my $replace_count = 0; + my $query = $dbh->selectall_arrayref("SELECT id, query FROM namedqueries"); + if ($query) { + my $sth = $dbh->prepare("UPDATE namedqueries SET query = ? + WHERE id = ?"); + + foreach my $row (@$query) { + my ($id, $query) = @$row; + if ($query =~ /(?:^|&|;)$field=$old(?:&|$|;)/) { + $query =~ s/((?:^|&|;)$field=)$old(;|&|$)/$1$new$2/; + $sth->execute($query, $id); + $replace_count++; + } + } + } + + #$dbh->bz_commit_transaction(); + print "namedqueries: $replace_count replacements made.\n"; +} + +# series +sub do_series($$$) { + my ($field, $old, $new) = @_; + $old = url_quote($old); + $new = url_quote($new); + + my $dbh = Bugzilla->dbh; + #$dbh->bz_start_transaction(); + + my $replace_count = 0; + my $query = $dbh->selectall_arrayref("SELECT series_id, query + FROM series"); + if ($query) { + my $sth = $dbh->prepare("UPDATE series SET query = ? + WHERE series_id = ?"); + foreach my $row (@$query) { + my ($series_id, $query) = @$row; + + if ($query =~ /(?:^|&|;)$field=$old(?:&|$|;)/) { + $query =~ s/((?:^|&|;)$field=)$old(;|&|$)/$1$new$2/; + $replace_count++; + } + + $sth->execute($query, $series_id); + } + } + + #$dbh->bz_commit_transaction(); + print "series: $replace_count replacements made.\n"; +} + +# series_categories +sub do_series_categories($$) { + my ($old, $new) = @_; + my $dbh = Bugzilla->dbh; + + $dbh->do("UPDATE series_categories SET name = ? WHERE name = ?", + undef, + ($new, $old)); +} + +############################################################################# +# MAIN CODE +############################################################################# +# This is a pure command line script. +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +if (scalar @ARGV < 3) { + usage(); + exit(); +} + +my ($field, $old, $new) = @ARGV; + +print "Changing all instances of '$old' to '$new'.\n\n"; + +do_namedqueries($field, $old, $new); +do_series($field, $old, $new); +do_series_categories($old, $new); + +# It's complex to determine which items now need to be flushed from memcached. +# As this is expected to be a rare event, we just flush the entire cache. +Bugzilla->memcached->clear_all(); + +exit(0); + diff --git a/scripts/issue-api-key.pl b/scripts/issue-api-key.pl new file mode 100755 index 000000000..c5bdd8ca4 --- /dev/null +++ b/scripts/issue-api-key.pl @@ -0,0 +1,33 @@ +#!/usr/bin/perl -w + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use strict; +use feature 'say'; + +use FindBin qw( $RealBin ); +use lib "$RealBin/.."; +use lib "$RealBin/../lib"; + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::User; +use Bugzilla::User::APIKey; + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +my $login = shift + or die "syntax: $0 bugzilla-login [description]\n"; +my $description = shift; + +my $user = Bugzilla::User->check({ name => $login }); +my $api_key = Bugzilla::User::APIKey->create({ + user_id => $user->id, + description => $description, +}); +say $api_key->api_key; diff --git a/scripts/merge-users.pl b/scripts/merge-users.pl new file mode 100755 index 000000000..ebe68a6a8 --- /dev/null +++ b/scripts/merge-users.pl @@ -0,0 +1,271 @@ +#!/usr/bin/perl -wT +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Netscape Communications +# Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Myk Melez <myk@mozilla.org> +# Frédéric Buclin <LpSolit@gmail.com> + +use strict; + +=head1 NAME + +merge-users.pl - Merge two user accounts. + +=head1 SYNOPSIS + + This script moves activity from one user account to another. + Specify the two accounts on the command line, e.g.: + + ./merge-users.pl old_account@foo.com new_account@bar.com + or: + ./merge-users.pl id:old_userid id:new_userid + or: + ./merge-users.pl id:old_userid new_account@bar.com + + Notes: - the new account must already exist. + - the id:old_userid syntax permits you to migrate + activity from a deleted account to an existing one. + +=cut + +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Util; +use Bugzilla::User; +use Bugzilla::Hook; + +use Getopt::Long; +use Pod::Usage; + +my $dbh = Bugzilla->dbh; + +# Display the help if called with --help or -?. +my $help = 0; +my $result = GetOptions("help|?" => \$help); +pod2usage(0) if $help; + + +# Make sure accounts were specified on the command line and exist. +my $old = $ARGV[0] || die "You must specify an old user account.\n"; +my $old_id; +if ($old =~ /^id:(\d+)$/) { + # As the old user account may be a deleted one, we don't + # check whether this user ID is valid or not. + # If it never existed, no damage will be done. + $old_id = $1; +} +else { + trick_taint($old); + $old_id = $dbh->selectrow_array('SELECT userid FROM profiles + WHERE login_name = ?', + undef, $old); +} +if ($old_id) { + print "OK, old user account $old found; user ID: $old_id.\n"; +} +else { + die "The old user account $old does not exist.\n"; +} + +my $new = $ARGV[1] || die "You must specify a new user account.\n"; +my $new_id; +if ($new =~ /^id:(\d+)$/) { + $new_id = $1; + # Make sure this user ID exists. + $new_id = $dbh->selectrow_array('SELECT userid FROM profiles + WHERE userid = ?', + undef, $new_id); +} +else { + trick_taint($new); + $new_id = $dbh->selectrow_array('SELECT userid FROM profiles + WHERE login_name = ?', + undef, $new); +} +if ($new_id) { + print "OK, new user account $new found; user ID: $new_id.\n"; +} +else { + die "The new user account $new does not exist.\n"; +} + +# Make sure the old and new accounts are different. +if ($old_id == $new_id) { + die "\nBoth accounts are identical. There is nothing to migrate.\n"; +} + + +# A list of tables and columns to be changed: +# - keys of the hash are table names to be locked/altered; +# - values of the hash contain column names to be updated +# as well as the columns they depend on: +# = each array is of the form: +# ['foo1 bar11 bar12 bar13', 'foo2 bar21 bar22', 'foo3 bar31 bar32'] +# where fooN is the column to update, and barN1, barN2, ... are +# the columns to take into account to avoid duplicated entries. +# Note that the barNM columns are optional. +# +# We set the tables that require custom stuff (multiple columns to check) +# here, but the simple stuff is all handled below by bz_get_related_fks. +my %changes = ( + cc => ['who bug_id'], + # Tables affecting global behavior / other users. + component_cc => ['user_id component_id'], + watch => ['watcher watched', 'watched watcher'], + # Tables affecting the user directly. + namedqueries => ['userid name'], + namedqueries_link_in_footer => ['user_id namedquery_id'], + user_group_map => ['user_id group_id isbless grant_type'], + email_setting => ['user_id relationship event'], + profile_setting => ['user_id setting_name'], + + # Only do it if mailto_type = 0, i.e is pointing to a user account! + # This requires to be done separately due to this condition. + whine_schedules => [], # ['mailto'], +); + +my $userid_fks = $dbh->bz_get_related_fks('profiles', 'userid'); +foreach my $item (@$userid_fks) { + my ($table, $column) = @$item; + $changes{$table} ||= []; + push(@{ $changes{$table} }, $column); +} + +# Delete all old records for these tables; no migration. +foreach my $table (qw(logincookies tokens profiles)) { + $changes{$table} = []; +} + +# Start the transaction +$dbh->bz_start_transaction(); + +# BMO - pre-work hook +Bugzilla::Hook::process('merge_users_before', { old_id => $old_id, new_id => $new_id }); + +# Delete old records from logincookies and tokens tables. +$dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $old_id); +$dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $old_id); + +# Special care needs to be done with bug_user_last_visit table as the +# source user and destination user may have visited the same bug id at one time. +# In this case we remove the one with the oldest timestamp. +my $dupe_ids = $dbh->selectcol_arrayref(" + SELECT earlier.id + FROM bug_user_last_visit as earlier + INNER JOIN bug_user_last_visit as later + ON (earlier.user_id != later.user_id + AND earlier.last_visit_ts < later.last_visit_ts + AND earlier.bug_id = later.bug_id) + WHERE (earlier.user_id = ? OR earlier.user_id = ?) + AND (later.user_id = ? OR later.user_id = ?)", + undef, $old_id, $new_id, $old_id, $new_id); + +if (@$dupe_ids) { + $dbh->do("DELETE FROM bug_user_last_visit WHERE " . + $dbh->sql_in('id', $dupe_ids)); +} + +# Migrate records from old user to new user. +foreach my $table (keys %changes) { + foreach my $column_list (@{ $changes{$table} }) { + # Get all columns to consider. There is always at least + # one column given: the one to update. + my @columns = split(/[\s]+/, $column_list); + my $cols_to_check = join(' AND ', map {"$_ = ?"} @columns); + # The first column of the list is the one to update. + my $col_to_update = shift @columns; + + # Will be used to migrate the old user account to the new one. + my $sth_update = $dbh->prepare("UPDATE $table + SET $col_to_update = ? + WHERE $cols_to_check"); + + # Do we have additional columns to take care of? + if (scalar(@columns)) { + my $cols_to_query = join(', ', @columns); + + # Get existing entries for the old user account. + my $old_entries = + $dbh->selectall_arrayref("SELECT $cols_to_query + FROM $table + WHERE $col_to_update = ?", + undef, $old_id); + + # Will be used to check whether the same entry exists + # for the new user account. + my $sth_select = $dbh->prepare("SELECT COUNT(*) + FROM $table + WHERE $cols_to_check"); + + # Will be used to delete duplicated entries. + my $sth_delete = $dbh->prepare("DELETE FROM $table + WHERE $cols_to_check"); + + foreach my $entry (@$old_entries) { + my $exists = $dbh->selectrow_array($sth_select, undef, + ($new_id, @$entry)); + + if ($exists) { + $sth_delete->execute($old_id, @$entry); + } + else { + $sth_update->execute($new_id, $old_id, @$entry); + } + } + } + # No check required. Update the column directly. + else { + $sth_update->execute($new_id, $old_id); + } + print "OK, records in the '$col_to_update' column of the '$table' table\n" . + "have been migrated to the new user account.\n"; + } +} + +# Only update 'whine_schedules' if mailto_type = 0. +# (i.e. is pointing to a user ID). +$dbh->do('UPDATE whine_schedules SET mailto = ? + WHERE mailto = ? AND mailto_type = ?', + undef, ($new_id, $old_id, 0)); +print "OK, records in the 'mailto' column of the 'whine_schedules' table\n" . + "have been migrated to the new user account.\n"; + +# Delete the old record from the profiles table. +$dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $old_id); + +# rederive regexp-based group memberships, because we merged all memberships +# from all of the accounts, and since the email address isn't the same on +# them, some of them may no longer match the regexps. +my $user = new Bugzilla::User($new_id); +$user->derive_regexp_groups(); + +# BMO - post-work hook +Bugzilla::Hook::process('merge_users_after', { old_id => $old_id, new_id => $new_id }); + +# Commit the transaction +$dbh->bz_commit_transaction(); + +# It's complex to determine which items now need to be flushed from memcached. +# As user merge is expected to be a rare event, we just flush the entire cache +# when users are merged. +Bugzilla->memcached->clear_all(); + +print "Done.\n"; diff --git a/scripts/moco-ldap-check.pl b/scripts/moco-ldap-check.pl new file mode 100755 index 000000000..7a3a6ca8c --- /dev/null +++ b/scripts/moco-ldap-check.pl @@ -0,0 +1,542 @@ +#!/usr/bin/perl + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use strict; +use warnings; + +use FindBin qw($Bin); +use lib "$Bin/.."; +use lib "$Bin/../lib"; + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Group; +use Bugzilla::Mailer; +use Data::Dumper; +use File::Slurp; +use Getopt::Long; +use Net::LDAP; +use Safe; + +# + +use constant BUGZILLA_IGNORE => <<'EOF'; + infra+bot@mozilla.com # Mozilla Infrastructure Bot + qa-auto@mozilla.com # QA Desktop Automation + qualys@mozilla.com # Qualys Security Scanner + recruiting@mozilla.com # Recruiting + release@mozilla.com # Mozilla RelEng Bot + sumo-dev@mozilla.com # SUMOdev [:sumodev] + airmozilla@mozilla.com # Air Mozilla + ux-review@mozilla.com + release-mgmt@mozilla.com + reps@mozilla.com + moz_bug_r_a4@mozilla.com # Security contractor + nightwatch@mozilla.com # Security distribution list for whines +EOF + +use constant LDAP_IGNORE => <<'EOF'; + airmozilla@mozilla.com # Air Mozilla +EOF + +# REPORT_SENDER has to be a valid @mozilla.com LDAP account +use constant REPORT_SENDER => 'bjones@mozilla.com'; + +use constant BMO_RECIPIENTS => qw( + glob@mozilla.com + dkl@mozilla.com +); + +use constant SUPPORT_RECIPIENTS => qw( + desktop@mozilla.com +); + +# + +my ($ldap_host, $ldap_user, $ldap_pass, $debug, $no_update); +GetOptions('h=s' => \$ldap_host, + 'u=s' => \$ldap_user, + 'p=s' => \$ldap_pass, + 'd' => \$debug, + 'n' => \$no_update); +die "syntax: -h ldap_host -u ldap_user -p ldap_pass\n" + unless $ldap_host && $ldap_user && $ldap_pass; + +my $data_dir = bz_locations()->{'datadir'} . '/moco-ldap-check'; +mkdir($data_dir) unless -d $data_dir; + +if ($ldap_user !~ /,/) { + $ldap_user = "mail=$ldap_user,o=com,dc=mozilla"; +} + +# +# group members +# + +my @bugzilla_ignore; +foreach my $line (split(/\n/, BUGZILLA_IGNORE)) { + $line =~ s/^([^#]+)#.*$/$1/; + $line =~ s/(^\s+|\s+$)//g; + push @bugzilla_ignore, clean_email($line); +} + +my @bugzilla_moco; +if ($no_update && -s "$data_dir/bugzilla_moco.last") { + $debug && print "Using cached user list from Bugzilla...\n"; + my $ra = deserialise("$data_dir/bugzilla_moco.last"); + @bugzilla_moco = @$ra; +} else { + $debug && print "Getting user list from Bugzilla...\n"; + + my $group = Bugzilla::Group->new({ name => 'mozilla-corporation' }) + or die "Failed to find group mozilla-corporation\n"; + + foreach my $user (@{ $group->members_non_inherited }) { + next unless $user->is_enabled; + my $mail = clean_email($user->login); + my $name = trim($user->name); + $name =~ s/\s+/ /g; + next if grep { $mail eq $_ } @bugzilla_ignore; + push @bugzilla_moco, { + mail => $user->login, + canon => $mail, + name => $name, + }; + } + + @bugzilla_moco = sort { $a->{mail} cmp $b->{mail} } @bugzilla_moco; + serialise("$data_dir/bugzilla_moco.last", \@bugzilla_moco); +} + +# +# build list of current mo-co bugmail accounts +# + +my @ldap_ignore; +foreach my $line (split(/\n/, LDAP_IGNORE)) { + $line =~ s/^([^#]+)#.*$/$1/; + $line =~ s/(^\s+|\s+$)//g; + push @ldap_ignore, canon_email($line); +} + +my %ldap; +if ($no_update && -s "$data_dir/ldap.last") { + $debug && print "Using cached user list from LDAP...\n"; + my $rh = deserialise("$data_dir/ldap.last"); + %ldap = %$rh; +} else { + $debug && print "Logging into LDAP as $ldap_user...\n"; + my $ldap = Net::LDAP->new($ldap_host, + scheme => 'ldaps', onerror => 'die') or die "$@"; + $ldap->bind($ldap_user, password => $ldap_pass); + foreach my $ldap_base ('o=com,dc=mozilla', 'o=org,dc=mozilla') { + $debug && print "Getting user list from LDAP $ldap_base...\n"; + my $result = $ldap->search( + base => $ldap_base, + scope => 'sub', + filter => '(mail=*)', + attrs => ['mail', 'bugzillaEmail', 'emailAlias', 'cn', 'employeeType'], + ); + foreach my $entry ($result->entries) { + my ($name, $bugMail, $mail, $type) = + map { $entry->get_value($_) || '' } + qw(cn bugzillaEmail mail employeeType); + next if $type eq 'DISABLED'; + $mail = lc $mail; + next if grep { $_ eq canon_email($mail) } @ldap_ignore; + $bugMail = '' if $bugMail !~ /\@/; + $bugMail =~ s/(^\s+|\s+$)//g; + if ($bugMail =~ / /) { + $bugMail = (grep { /\@/ } split / /, $bugMail)[0]; + } + $name =~ s/\s+/ /g; + $ldap{$mail}{name} = trim($name); + $ldap{$mail}{bugmail} = $bugMail; + $ldap{$mail}{bugmail_canon} = canon_email($bugMail); + $ldap{$mail}{aliases} = []; + foreach my $alias ( + @{$entry->get_value('emailAlias', asref => 1) || []} + ) { + push @{$ldap{$mail}{aliases}}, canon_email($alias); + } + } + $debug && printf "Found %s entries\n", scalar($result->entries); + } + serialise("$data_dir/ldap.last", \%ldap); +} + +# +# validate all bugmail entries from the phonebook +# + +my %bugzilla_login; +if ($no_update && -s "$data_dir/bugzilla_login.last") { + $debug && print "Using cached bugzilla checks...\n"; + my $rh = deserialise("$data_dir/bugzilla_login.last"); + %bugzilla_login = %$rh; +} else { + my %logins; + foreach my $mail (keys %ldap) { + $logins{$mail} = 1; + $logins{$ldap{$mail}{bugmail}} = 1 if $ldap{$mail}{bugmail}; + } + my @logins = sort keys %logins; + $debug && print "Checking " . scalar(@logins) . " bugmail accounts...\n"; + + foreach my $login (@logins) { + if (Bugzilla::User->new({ name => $login })) { + $bugzilla_login{$login} = 1; + } + } + serialise("$data_dir/bugzilla_login.last", \%bugzilla_login); +} + +# +# load previous ldap list +# + +my %ldap_old; +{ + my $rh = deserialise("$data_dir/ldap.data"); + %ldap_old = %$rh if $rh; +} + +# +# save current ldap list +# + +{ + serialise("$data_dir/ldap.data", \%ldap); +} + +# +# new ldap accounts +# + +my @new_ldap; +{ + foreach my $mail (sort keys %ldap) { + next if exists $ldap_old{$mail}; + push @new_ldap, { + mail => $mail, + name => $ldap{$mail}{name}, + bugmail => $ldap{$mail}{bugmail}, + }; + } +} + +# +# deleted ldap accounts +# + +my @gone_ldap_bmo; +my @gone_ldap_no_bmo; +{ + foreach my $mail (sort keys %ldap_old) { + next if exists $ldap{$mail}; + if ($ldap_old{$mail}{bugmail}) { + push @gone_ldap_bmo, { + mail => $mail, + name => $ldap_old{$mail}{name}, + bugmail => $ldap_old{$mail}{bugmail}, + } + } else { + push @gone_ldap_no_bmo, { + mail => $mail, + name => $ldap_old{$mail}{name}, + } + } + } +} + +# +# check bugmail entry for all users in bmo/moco group +# + +my @suspect_bugzilla; +my @invalid_bugzilla; +foreach my $rh (@bugzilla_moco) { + my @check = ($rh->{mail}, $rh->{canon}); + if ($rh->{mail} =~ /^([^\@]+)\@mozilla\.org$/) { + push @check, "$1\@mozilla.com"; + } + + my $exists; + foreach my $check (@check) { + $exists = 0; + + # don't complain about deleted accounts + if (grep { $_->{mail} eq $check } (@gone_ldap_bmo, @gone_ldap_no_bmo)) { + $exists = 1; + last; + } + + # check for matching bugmail entry + foreach my $mail (sort keys %ldap) { + next unless $ldap{$mail}{bugmail_canon} eq $check; + $exists = 1; + last; + } + last if $exists; + + # check for matching mail + $exists = 0; + foreach my $mail (sort keys %ldap) { + next unless $mail eq $check; + $exists = 1; + last; + } + last if $exists; + + # check for matching email alias + $exists = 0; + foreach my $mail (sort keys %ldap) { + next unless grep { $check eq $_ } @{$ldap{$mail}{aliases}}; + $exists = 1; + last; + } + last if $exists; + } + + if (!$exists) { + # flag the account + if ($rh->{mail} =~ /\@mozilla\.(com|org)$/i) { + push @invalid_bugzilla, { + mail => $rh->{mail}, + name => $rh->{name}, + }; + } else { + push @suspect_bugzilla, { + mail => $rh->{mail}, + name => $rh->{name}, + }; + } + } +} + +# +# check bugmail entry for ldap users +# + +my @ldap_unblessed; +my @invalid_ldap; +my @invalid_bugmail; +foreach my $mail (sort keys %ldap) { + # try to find the bmo account + my $found; + foreach my $address ($ldap{$mail}{bugmail}, $ldap{$mail}{bugmail_canon}, $mail, @{$ldap{$mail}{aliases}}) { + if (exists $bugzilla_login{$address}) { + $found = $address; + last; + } + } + + # not on bmo + if (!$found) { + # if they have specified a bugmail account, warn, otherwise ignore + if ($ldap{$mail}{bugmail}) { + if (grep { $_->{canon} eq $ldap{$mail}{bugmail_canon} } @bugzilla_moco) { + push @invalid_bugmail, { + mail => $mail, + name => $ldap{$mail}{name}, + bugmail => $ldap{$mail}{bugmail}, + }; + } else { + push @invalid_ldap, { + mail => $mail, + name => $ldap{$mail}{name}, + bugmail => $ldap{$mail}{bugmail}, + }; + } + } + next; + } + + # warn about mismatches + if ($ldap{$mail}{bugmail} && $found ne $ldap{$mail}{bugmail}) { + push @invalid_bugmail, { + mail => $mail, + name => $ldap{$mail}{name}, + bugmail => $ldap{$mail}{bugmail}, + }; + } + + # warn about unblessed accounts + if ($mail =~ /\@mozilla\.com$/) { + unless (grep { $_->{mail} eq $found || $_->{canon} eq canon_email($found) } @bugzilla_moco) { + push @ldap_unblessed, { + mail => $mail, + name => $ldap{$mail}{name}, + bugmail => $ldap{$mail}{bugmail} || $mail, + }; + } + } +} + +# +# reports +# + +my @bmo_report; +push @bmo_report, generate_report( + 'new ldap accounts', + 'no action required', + @new_ldap); + +push @bmo_report, generate_report( + 'deleted ldap accounts', + 'disable bmo account', + @gone_ldap_bmo); + +push @bmo_report, generate_report( + 'deleted ldap accounts', + 'no action required (no bmo account)', + @gone_ldap_no_bmo); + +push @bmo_report, generate_report( + 'suspect bugzilla accounts', + 'remove from mo-co if required', + @suspect_bugzilla); + +push @bmo_report, generate_report( + 'miss-configured bugzilla accounts', + 'ask owner to update phonebook, disable if not on phonebook', + @invalid_bugzilla); + +push @bmo_report, generate_report( + 'ldap accounts without mo-co group', + 'verify, and add mo-co group to bmo account', + @ldap_unblessed); + +push @bmo_report, generate_report( + 'missmatched bugmail entries on ldap accounts', + 'ask owner to update phonebook', + @invalid_bugmail); + +push @bmo_report, generate_report( + 'invalid bugmail entries on ldap accounts', + 'ask owner to update phonebook', + @invalid_ldap); + +if (!scalar @bmo_report) { + push @bmo_report, '**'; + push @bmo_report, '** nothing to report \o/'; + push @bmo_report, '**'; +} + +email_report(\@bmo_report, 'moco-ldap-check', BMO_RECIPIENTS); + +my @support_report; + +push @support_report, generate_report( + 'Missmatched "Bugzilla Email" entries on LDAP accounts', + 'Ask owner to update phonebook, or update directly', + @invalid_bugmail); + +push @support_report, generate_report( + 'Invalid "Bugzilla Email" entries on LDAP accounts', + 'Ask owner to update phonebook', + @invalid_ldap); + +if (scalar @support_report) { + email_report(\@support_report, 'Invalid "Bugzilla Email" entries in LDAP', SUPPORT_RECIPIENTS); +} + +# +# +# + +sub generate_report { + my ($title, $action, @lines) = @_; + + my $count = scalar @lines; + return unless $count; + + my @report; + push @report, ''; + push @report, '**'; + push @report, "** $title ($count)"; + push @report, "** [ $action ]"; + push @report, '**'; + push @report, ''; + + my $max_length = 0; + foreach my $rh (@lines) { + $max_length = length($rh->{mail}) if length($rh->{mail}) > $max_length; + } + + foreach my $rh (@lines) { + my $template = "%-${max_length}s %s"; + my @fields = ($rh->{mail}, $rh->{name}); + + if ($rh->{bugmail}) { + $template .= ' (%s)'; + push @fields, $rh->{bugmail}; + }; + + push @report, sprintf($template, @fields); + } + + return @report; +} + +sub email_report { + my ($report, $subject, @recipients) = @_; + unshift @$report, ( + "Subject: $subject", + 'X-Bugzilla-Type: moco-ldap-check', + 'From: ' . REPORT_SENDER, + 'To: ' . join(',', @recipients), + ); + if ($debug) { + print "\n", join("\n", @$report), "\n"; + } else { + MessageToMTA(join("\n", @$report)); + } +} + +sub clean_email { + my $email = shift; + $email = trim($email); + $email = $1 if $email =~ /^(\S+)/; + $email =~ s/@/@/; + $email = lc $email; + return $email; +} + +sub canon_email { + my $email = shift; + $email = clean_email($email); + $email =~ s/^([^\+]+)\+[^\@]+(\@.+)$/$1$2/; + return $email; +} + +sub trim { + my $value = shift; + $value =~ s/(^\s+|\s+$)//g; + return $value; +} + +sub serialise { + my ($filename, $ref) = @_; + local $Data::Dumper::Purity = 1; + local $Data::Dumper::Deepcopy = 1; + local $Data::Dumper::Sortkeys = 1; + write_file($filename, Dumper($ref)); +} + +sub deserialise { + my ($filename) = @_; + return unless -s $filename; + my $cpt = Safe->new(); + $cpt->reval('our ' . read_file($filename)) + || die "$!"; + return ${$cpt->varglob('VAR1')}; +} + diff --git a/scripts/move_flag_types.pl b/scripts/move_flag_types.pl new file mode 100755 index 000000000..1f4398be1 --- /dev/null +++ b/scripts/move_flag_types.pl @@ -0,0 +1,172 @@ +#!/usr/bin/perl +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Initial Developer of the Original Code is Mozilla Foundation +# Portions created by the Initial Developer are Copyright (C) 2011 the +# Initial Developer. All Rights Reserved. +# +#=============================================================================== +# +# FILE: move_flag_types.pl +# +# USAGE: ./move_flag_types.pl +# +# DESCRIPTION: Move current set flag from one type_id to another +# based on product and optionally component. +# +# OPTIONS: --- +# REQUIREMENTS: --- +# BUGS: --- +# NOTES: --- +# AUTHOR: David Lawrence (:dkl), dkl@mozilla.com +# COMPANY: Mozilla Foundation +# VERSION: 1.0 +# CREATED: 08/22/2011 05:18:06 PM +# REVISION: --- +#=============================================================================== + +=head1 NAME + +move_flag_types.pl - Move currently set flags from one type id to another based +on product and optionally component. + +=head1 SYNOPSIS + +This script will move bugs matching a specific product (and optionally a component) +from one flag type id to another if the bug has the flag set to either +, -, or ?. + +./move_flag_types.pl --old-id 4 --new-id 720 --product Firefox --component Installer + +=head1 OPTIONS + +=over + +=item B<--help|-h|?> + +Print a brief help message and exits. + +=item B<--oldid|-o> + +Old flag type id. Use editflagtypes.cgi to determine the type id from the URL. + +=item B<--newid|-n> + +New flag type id. Use editflagtypes.cgi to determine the type id from the URL. + +=item B<--product|-p> + +The product that the bugs most be assigned to. + +=item B<--component|-c> + +Optional: The component of the given product that the bugs must be assigned to. + +=item B<--doit|-d> + +Without this argument, changes are not actually committed to the database. + +=back + +=cut + +use strict; +use warnings; + +use lib '.'; + +use Bugzilla; +use Getopt::Long; +use Pod::Usage; + +my %params; +GetOptions(\%params, 'help|h|?', 'oldid|o=s', 'newid|n=s', + 'product|p=s', 'component|c:s', 'doit|d') or pod2usage(1); + +if ($params{'help'} || !$params{'oldid'} + || !$params{'newid'} || !$params{'product'}) { + pod2usage({ -message => "Missing required argument", + -exitval => 1 }); +} + +# Set defaults +$params{'doit'} ||= 0; +$params{'component'} ||= ''; + +my $dbh = Bugzilla->dbh; + +# Get the flag names +my $old_flag_name = $dbh->selectrow_array( + "SELECT name FROM flagtypes WHERE id = ?", + undef, $params{'oldid'}); +my $new_flag_name = $dbh->selectrow_array( + "SELECT name FROM flagtypes WHERE id = ?", + undef, $params{'newid'}); + +# Find the product id +my $product_id = $dbh->selectrow_array( + "SELECT id FROM products WHERE name = ?", + undef, $params{'product'}); + +# Find the component id if not __ANY__ +my $component_id; +if ($params{'component'}) { + $component_id = $dbh->selectrow_array( + "SELECT id FROM components WHERE name = ? AND product_id = ?", + undef, $params{'component'}, $product_id); +} + +my @query_args = ($params{'oldid'}); + +my $flag_query = "SELECT flags.id AS flag_id, flags.bug_id AS bug_id + FROM flags JOIN bugs ON flags.bug_id = bugs.bug_id + WHERE flags.type_id = ? "; + +if ($component_id) { + # No need to compare against product_id as component_id is already + # tied to a specific product + $flag_query .= "AND bugs.component_id = ?"; + push(@query_args, $component_id); +} +else { + # All bugs for a product regardless of component + $flag_query .= "AND bugs.product_id = ?"; + push(@query_args, $product_id); +} + +my $flags = $dbh->selectall_arrayref($flag_query, undef, @query_args); + +if (@$flags) { + print "Moving '" . scalar @$flags . "' flags " . + "from $old_flag_name (" . $params{'oldid'} . ") " . + "to $new_flag_name (" . $params{'newid'} . ")...\n"; + + if (!$params{'doit'}) { + print "Pass the argument --doit or -d to permanently make changes to the database.\n"; + } + else { + my $flag_update_sth = $dbh->prepare("UPDATE flags SET type_id = ? WHERE id = ?"); + + foreach my $flag (@$flags) { + my ($flag_id, $bug_id) = @$flag; + print "Bug: $bug_id Flag: $flag_id\n"; + $flag_update_sth->execute($params{'newid'}, $flag_id); + } + } + + # It's complex to determine which items now need to be flushed from memcached. + # As this is expected to be a rare event, we just flush the entire cache. + Bugzilla->memcached->clear_all(); +} +else { + print "No flags to move\n"; +} diff --git a/scripts/move_os.pl b/scripts/move_os.pl new file mode 100755 index 000000000..96b58d616 --- /dev/null +++ b/scripts/move_os.pl @@ -0,0 +1,81 @@ +#!/usr/bin/perl +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use 5.10.1; +use strict; +use warnings; + +use FindBin '$RealBin'; +use lib "$RealBin/../..", "$RealBin/../../lib"; + +use Bugzilla; +use Bugzilla::Field; +use Bugzilla::Constants; + +use Getopt::Long qw( :config gnu_getopt ); +use Pod::Usage; + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +my ($from_os, $to_os); +GetOptions('from=s' => \$from_os, 'to=s' => \$to_os); + +pod2usage(1) unless defined $from_os && defined $to_os; + + +my $check_from_os = Bugzilla::Field::Choice->type('op_sys')->match({ value => $from_os }); +my $check_to_os = Bugzilla::Field::Choice->type('op_sys')->match({ value => $to_os }); +die "Cannot move $from_os because it does not exist\n" + unless @$check_from_os == 1; +die "Cannot move $from_os because $to_os doesn't exist.\n" + unless @$check_to_os == 1; + +my $dbh = Bugzilla->dbh; +my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); +my $bug_ids = $dbh->selectcol_arrayref(q{SELECT bug_id FROM bugs WHERE bugs.op_sys = ?}, undef, $from_os); +my $field = Bugzilla::Field->check({ name => 'op_sys', cache => 1 }); +my $nobody = Bugzilla::User->check({ name => 'nobody@mozilla.org', cache => 1 }); + +my $bug_count = @$bug_ids; +if ($bug_count == 0) { + warn "There are no bugs to move.\n"; + exit 1; +} + +print STDERR <<EOF; +About to move $bug_count bugs from $from_os to $to_os. + +Press <Ctrl-C> to stop or <Enter> to continue... +EOF +getc(); + +$dbh->bz_start_transaction; +foreach my $bug_id (@$bug_ids) { + warn "Moving $bug_id...\n"; + + $dbh->do(q{INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) + VALUES (?, ?, ?, ?, ?, ?)}, + undef, $bug_id, $nobody->id, $timestamp, $field->id, $from_os, $to_os); + $dbh->do(q{UPDATE bugs SET op_sys = ?, delta_ts = ?, lastdiffed = ? WHERE bug_id = ?}, + undef, $to_os, $timestamp, $timestamp, $bug_id); +} +$dbh->bz_commit_transaction; + +# It's complex to determine which items now need to be flushed from memcached. +# As this is expected to be a rare event, we just flush the entire cache. +Bugzilla->memcached->clear_all(); + +__END__ + +=head1 NAME + +move_os.pl - move the os on all bugs with a particular os to a new os + +=head1 SYNOPSIS + + move_os.pl --from 'Windows 8 Metro' --to 'Windows 8.1' diff --git a/scripts/movebugs.pl b/scripts/movebugs.pl new file mode 100755 index 000000000..ebe41ceb4 --- /dev/null +++ b/scripts/movebugs.pl @@ -0,0 +1,190 @@ +#!/usr/bin/perl -w + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use strict; + +use Cwd 'abs_path'; +use File::Basename; +use FindBin; +use lib "$FindBin::Bin/../.."; +use lib "$FindBin::Bin/../../lib"; + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::FlagType; +use Bugzilla::Hook; +use Bugzilla::Util; + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +if (scalar @ARGV < 4) { + die <<USAGE; +Usage: movebugs.pl <old-product> <old-component> <new-product> <new-component> + +Eg. movebugs.pl mozilla.org bmo bugzilla.mozilla.org admin +Will move all bugs in the mozilla.org:bmo component to the +bugzilla.mozilla.org:admin component. + +The new product must have matching versions, milestones, and flags from the old +product (will be validated by this script). +USAGE +} + +my ($old_product, $old_component, $new_product, $new_component) = @ARGV; + +my $dbh = Bugzilla->dbh; + +my $old_product_id = $dbh->selectrow_array( + "SELECT id FROM products WHERE name=?", + undef, $old_product); +$old_product_id + or die "Can't find product ID for '$old_product'.\n"; + +my $old_component_id = $dbh->selectrow_array( + "SELECT id FROM components WHERE name=? AND product_id=?", + undef, $old_component, $old_product_id); +$old_component_id + or die "Can't find component ID for '$old_component'.\n"; + +my $new_product_id = $dbh->selectrow_array( + "SELECT id FROM products WHERE name=?", + undef, $new_product); +$new_product_id + or die "Can't find product ID for '$new_product'.\n"; + +my $new_component_id = $dbh->selectrow_array( + "SELECT id FROM components WHERE name=? AND product_id=?", + undef, $new_component, $new_product_id); +$new_component_id + or die "Can't find component ID for '$new_component'.\n"; + +my $product_field_id = $dbh->selectrow_array( + "SELECT id FROM fielddefs WHERE name = 'product'"); +$product_field_id + or die "Can't find field ID for 'product' field\n"; +my $component_field_id = $dbh->selectrow_array( + "SELECT id FROM fielddefs WHERE name = 'component'"); +$component_field_id + or die "Can't find field ID for 'component' field\n"; + +my $user_id = $dbh->selectrow_array( + "SELECT userid FROM profiles WHERE login_name='nobody\@mozilla.org'"); +$user_id + or die "Can't find user ID for 'nobody\@mozilla.org'\n"; + +$dbh->bz_start_transaction(); + +# build list of bugs +my $ra_ids = $dbh->selectcol_arrayref( + "SELECT bug_id FROM bugs WHERE product_id=? AND component_id=?", + undef, $old_product_id, $old_component_id); +my $bug_count = scalar @$ra_ids; +$bug_count + or die "No bugs were found in '$old_component'\n"; +my $where_sql = 'bug_id IN (' . join(',', @$ra_ids) . ')'; + +# check versions +my @missing_versions; +my $ra_versions = $dbh->selectcol_arrayref( + "SELECT DISTINCT version FROM bugs WHERE $where_sql"); +foreach my $version (@$ra_versions) { + my $has_version = $dbh->selectrow_array( + "SELECT 1 FROM versions WHERE product_id=? AND value=?", + undef, $new_product_id, $version); + push @missing_versions, $version unless $has_version; +} + +# check milestones +my @missing_milestones; +my $ra_milestones = $dbh->selectcol_arrayref( + "SELECT DISTINCT target_milestone FROM bugs WHERE $where_sql"); +foreach my $milestone (@$ra_milestones) { + my $has_milestone = $dbh->selectrow_array( + "SELECT 1 FROM milestones WHERE product_id=? AND value=?", + undef, $new_product_id, $milestone); + push @missing_milestones, $milestone unless $has_milestone; +} + +# check flags +my @missing_flags; +my $ra_old_types = $dbh->selectcol_arrayref( + "SELECT DISTINCT type_id + FROM flags + INNER JOIN flagtypes ON flagtypes.id = flags.type_id + WHERE $where_sql"); +my $ra_new_types = + Bugzilla::FlagType::match({ product_id => $new_product_id, + component_id => $new_component_id }); +foreach my $old_type (@$ra_old_types) { + unless (grep { $_->id == $old_type } @$ra_new_types) { + my $flagtype = Bugzilla::FlagType->new($old_type); + push @missing_flags, $flagtype->name . ' (' . $flagtype->target_type . ')'; + } +} + +# show missing +my $missing_error = ''; +if (@missing_versions) { + $missing_error .= "'$new_product' is missing the following version(s):\n " . + join("\n ", @missing_versions) . "\n"; +} +if (@missing_milestones) { + $missing_error .= "'$new_product' is missing the following milestone(s):\n " . + join("\n ", @missing_milestones) . "\n"; +} +if (@missing_flags) { + $missing_error .= "'$new_product'::'$new_component' is missing the following flag(s):\n " . + join("\n ", @missing_flags) . "\n"; +} +die $missing_error if $missing_error; + +# confirmation +print <<EOF; +About to move $bug_count bugs +From '$old_product' : '$old_component' +To '$new_product' : '$new_component' + +Press <Ctrl-C> to stop or <Enter> to continue... +EOF +getc(); + +print "Moving $bug_count bugs from $old_product:$old_component to $new_product:$new_component\n"; + +# update bugs +$dbh->do( + "UPDATE bugs SET product_id=?, component_id=? WHERE $where_sql", + undef, $new_product_id, $new_component_id); + +# touch bugs +$dbh->do("UPDATE bugs SET delta_ts=NOW() WHERE $where_sql"); +$dbh->do("UPDATE bugs SET lastdiffed=NOW() WHERE $where_sql"); + +# update bugs_activity +$dbh->do( + "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) + SELECT bug_id, ?, delta_ts, ?, ?, ? FROM bugs WHERE $where_sql", + undef, + $user_id, $product_field_id, $old_product, $new_product); +$dbh->do( + "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) + SELECT bug_id, ?, delta_ts, ?, ?, ? FROM bugs WHERE $where_sql", + undef, + $user_id, $component_field_id, $old_component, $new_component); + +Bugzilla::Hook::process('reorg_move_bugs', { bug_ids => $ra_ids } ); + +$dbh->bz_commit_transaction(); + +foreach my $bug_id (@$ra_ids) { + Bugzilla->memcached->clear({ table => 'bugs', id => $bug_id }); +} + +# It's complex to determine which items now need to be flushed from memcached. +# As this is expected to be a rare event, we just flush the entire cache. +Bugzilla->memcached->clear_all(); diff --git a/scripts/movecomponent.pl b/scripts/movecomponent.pl new file mode 100755 index 000000000..cb07b84fc --- /dev/null +++ b/scripts/movecomponent.pl @@ -0,0 +1,154 @@ +#!/usr/bin/perl -w +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use strict; + +use FindBin '$RealBin'; +use lib "$RealBin/../..", "$RealBin/../../lib"; + +use Bugzilla; +use Bugzilla::Component; +use Bugzilla::Constants; +use Bugzilla::Field; +use Bugzilla::Hook; +use Bugzilla::Product; +use Bugzilla::Util; + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +if (scalar @ARGV < 3) { + die <<USAGE; +Usage: movecomponent.pl <oldproduct> <newproduct> <component> + +E.g.: movecomponent.pl ReplicationEngine FoodReplicator SeaMonkey +will move the component "SeaMonkey" from the product "ReplicationEngine" +to the product "FoodReplicator". + +Important: You must make sure the milestones and versions of the bugs in the +component are available in the new product. See syncmsandversions.pl. + +USAGE +} + +my ($old_product_name, $new_product_name, $component_name) = @ARGV; +my $old_product = Bugzilla::Product->check({ name => $old_product_name }); +my $new_product = Bugzilla::Product->check({ name => $new_product_name }); +my $component = Bugzilla::Component->check({ product => $old_product, name => $component_name }); +my $field_id = get_field_id('product'); + +my $dbh = Bugzilla->dbh; + +# check versions +my @missing_versions; +my $ra_versions = $dbh->selectcol_arrayref( + "SELECT DISTINCT version FROM bugs WHERE component_id = ?", + undef, $component->id); +foreach my $version (@$ra_versions) { + my $has_version = $dbh->selectrow_array( + "SELECT 1 FROM versions WHERE product_id = ? AND value = ?", + undef, $new_product->id, $version); + push @missing_versions, $version unless $has_version; +} + +# check milestones +my @missing_milestones; +my $ra_milestones = $dbh->selectcol_arrayref( + "SELECT DISTINCT target_milestone FROM bugs WHERE component_id = ?", + undef, $component->id); +foreach my $milestone (@$ra_milestones) { + my $has_milestone = $dbh->selectrow_array( + "SELECT 1 FROM milestones WHERE product_id=? AND value=?", + undef, $new_product->id, $milestone); + push @missing_milestones, $milestone unless $has_milestone; +} + +my $missing_error = ''; +if (@missing_versions) { + $missing_error .= "'$new_product_name' is missing the following version(s):\n " . + join("\n ", @missing_versions) . "\n"; +} +if (@missing_milestones) { + $missing_error .= "'$new_product_name' is missing the following milestone(s):\n " . + join("\n ", @missing_milestones) . "\n"; +} +die $missing_error if $missing_error; + +# confirmation +print <<EOF; +About to move the component '$component_name' +From '$old_product_name' +To '$new_product_name' + +Press <Ctrl-C> to stop or <Enter> to continue... +EOF +getc(); + +print "Moving '$component_name' from '$old_product_name' to '$new_product_name'...\n\n"; +$dbh->bz_start_transaction(); + +my $ra_ids = $dbh->selectcol_arrayref( + "SELECT bug_id FROM bugs WHERE product_id=? AND component_id=?", + undef, $old_product->id, $component->id); + +# Bugs table +$dbh->do("UPDATE bugs SET product_id = ? WHERE component_id = ?", + undef, + ($new_product->id, $component->id)); + +# Flags tables +fix_flags('flaginclusions', $new_product, $component); +fix_flags('flagexclusions', $new_product, $component); + +# Components +$dbh->do("UPDATE components SET product_id = ? WHERE id = ?", + undef, + ($new_product->id, $component->id)); + +Bugzilla::Hook::process('reorg_move_component', { + old_product => $old_product, + new_product => $new_product, + component => $component, +} ); + +# Mark bugs as touched +$dbh->do("UPDATE bugs SET delta_ts = NOW() + WHERE component_id = ?", undef, $component->id); +$dbh->do("UPDATE bugs SET lastdiffed = NOW() + WHERE component_id = ?", undef, $component->id); + +# Update bugs_activity +my $userid = 1; # nobody@mozilla.org + +$dbh->do("INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, + added) + SELECT bug_id, ?, delta_ts, ?, ?, ? + FROM bugs WHERE component_id = ?", + undef, + ($userid, $field_id, $old_product_name, $new_product_name, $component->id)); + +Bugzilla::Hook::process('reorg_move_bugs', { bug_ids => $ra_ids } ); + +$dbh->bz_commit_transaction(); + +# It's complex to determine which items now need to be flushed from memcached. +# As this is expected to be a rare event, we just flush the entire cache. +Bugzilla->memcached->clear_all(); + +sub fix_flags { + my ($table, $new_product, $component) = @_; + my $dbh = Bugzilla->dbh; + + my $type_ids = $dbh->selectcol_arrayref("SELECT DISTINCT type_id FROM $table WHERE component_id = ?", + undef, + $component->id); + $dbh->do("DELETE FROM $table WHERE component_id = ?", undef, $component->id); + foreach my $type_id (@$type_ids) { + $dbh->do("INSERT INTO $table (type_id, product_id, component_id) VALUES (?, ?, ?)", + undef, ($type_id, $new_product->id, $component->id)); + } +} diff --git a/scripts/nagios_blocker_checker.pl b/scripts/nagios_blocker_checker.pl new file mode 100755 index 000000000..768053126 --- /dev/null +++ b/scripts/nagios_blocker_checker.pl @@ -0,0 +1,201 @@ +#!/usr/bin/perl + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use strict; +use warnings; + +use FindBin qw($Bin); +use lib "$Bin/.."; +use lib "$Bin/../lib"; + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Product; +use Bugzilla::User; +use Getopt::Long; + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +my $config = { + # filter by assignee, product or component + assignee => '', + product => '', + component => '', + unassigned => 'nobody@mozilla.org', + # severities + severity => 'major,critical,blocker', + # time in hours to wait before paging/warning + major_alarm => 24, + major_warn => 20, + critical_alarm => 8, + critical_warn => 5, + blocker_alarm => 0, + blocker_warn => 0, + any_alarm => 24, + any_warn => 20, +}; + +my $usage = <<EOF; +FILTERS + + the filter determines which bugs to check, either by assignee, product or the + product's component. For backward compatibility, if just an email address is + provided, it will be used as the assignee. + + --assignee <email> filter bugs by assignee + --product <name> filter bugs by product name + --component <name> filter bugs by product's component name + --unassigned <email> set the unassigned user (default: $config->{unassigned}) + +SEVERITIES + + by default alerts and warnings will be generated for 'major', 'critical', and + 'blocker' bugs. you can alter this list with the 'severity' switch. + + setting severity to 'any' will result in alerting on unassigned bugs + regardless of severity. + + --severity <major|critical|blocker>[,..] + --severity any + +TIMING + + time in hours to wait before paging or warning. + + --major_alarm <hours> (default: $config->{major_alarm}) + --major_warn <hours> (default: $config->{major_warn}) + --critical_alarm <hours> (default: $config->{critical_alarm}) + --critical_warn <hours> (default: $config->{critical_warn}) + --blocker_alarm <hours> (default: $config->{blocker_alarm}) + --blocker_warn <hours> (default: $config->{blocker_warn}) + + when severity checking is set to "any", use the any_* switches instead: + + --any_alarm <hours> (default: $config->{any_alarm}) + --any_warn <hours> (default: $config->{any_warn}) + +EXAMPLES + + nagios_blocker_checker.pl --assignee server-ops\@mozilla-org.bugs + nagios_blocker_checker.pl server-ops\@mozilla-org.bugs + nagios_blocker_checker.pl --product 'Release Engineering' \ + --component 'Loan Requests' \ + --severity any --any_warn 24 --any_alarm 24 +EOF + +die($usage) unless GetOptions( + 'assignee=s' => \$config->{assignee}, + 'product=s' => \$config->{product}, + 'component=s' => \$config->{component}, + 'severity=s' => \$config->{severity}, + 'major_alarm=i' => \$config->{major_alarm}, + 'major_warn=i' => \$config->{major_warn}, + 'critical_alarm=i' => \$config->{critical_alarm}, + 'critical_warn=i' => \$config->{critical_warn}, + 'blocker_alarm=i' => \$config->{blocker_alarm}, + 'blocker_warn=i' => \$config->{blocker_warn}, + 'any_alarm=i' => \$config->{any_alarm}, + 'any_warn=i' => \$config->{any_warn}, + 'help|?' => \$config->{help}, +); +$config->{assignee} = $ARGV[0] if !$config->{assignee} && @ARGV; +die $usage if + $config->{help} + || !($config->{assignee} || $config->{product}) + || ($config->{assignee} && $config->{product}) + || ($config->{component} && !$config->{product}) + || !$config->{severity}; + +# + +use constant NAGIOS_OK => 0; +use constant NAGIOS_WARNING => 1; +use constant NAGIOS_CRITICAL => 2; +use constant NAGIOS_NAMES => [qw( OK WARNING CRITICAL )]; + +my $dbh = Bugzilla->switch_to_shadow_db; +my $any_severity = $config->{severity} eq 'any'; +my ($where, @values); + +if ($config->{assignee}) { + $where = 'bugs.assigned_to = ?'; + push @values, Bugzilla::User->check({ name => $config->{assignee} })->id; + +} elsif ($config->{component}) { + $where = 'bugs.product_id = ? AND bugs.component_id = ? AND bugs.assigned_to = ?'; + my $product = Bugzilla::Product->check({ name => $config->{product} }); + push @values, $product->id; + push @values, Bugzilla::Component->check({ product => $product, name => $config->{component} })->id; + push @values, Bugzilla::User->check({ name => $config->{unassigned} })->id; + +} else { + $where = 'bugs.product_id = ? AND bugs.assigned_to = ?'; + push @values, Bugzilla::Product->check({ name => $config->{product} })->id; + push @values, Bugzilla::User->check({ name => $config->{unassigned} })->id; +} + +if (!$any_severity) { + $where .= ' AND bug_severity IN (' . + join(',', map { $dbh->quote($_) } split(/,/, $config->{severity})) . ')'; +} + +my $sql = <<EOF; + SELECT bug_id, bug_severity, UNIX_TIMESTAMP(bugs.creation_ts) AS ts + FROM bugs + WHERE $where + AND COALESCE(resolution, '') = '' +EOF + +my $bugs = { + 'major' => [], + 'critical' => [], + 'blocker' => [], + 'any' => [], +}; +my $current_state = NAGIOS_OK; +my $current_time = time; + +foreach my $bug (@{ $dbh->selectall_arrayref($sql, { Slice => {} }, @values) }) { + my $severity = $any_severity ? 'any' : $bug->{bug_severity}; + my $age = ($current_time - $bug->{ts}) / 3600; + + if ($age > $config->{"${severity}_alarm"}) { + $current_state = NAGIOS_CRITICAL; + push @{$bugs->{$severity}}, $bug->{bug_id}; + + } elsif ($age > $config->{"${severity}_warn"}) { + if ($current_state < NAGIOS_WARNING) { + $current_state = NAGIOS_WARNING; + } + push @{$bugs->{$severity}}, $bug->{bug_id}; + + } +} + +print "bugs " . NAGIOS_NAMES->[$current_state] . ": "; +if ($current_state == NAGIOS_OK) { + if ($config->{severity} eq 'any') { + print "No unassigned bugs found."; + } else { + print "No $config->{severity} bugs found." + } +} +foreach my $severity (qw( blocker critical major any )) { + my $list = $bugs->{$severity}; + if (@$list) { + printf + "%s %s %s found https://bugzil.la/" . join(',', @$list) . " ", + scalar(@$list), + ($any_severity ? 'unassigned' : $severity), + (scalar(@$list) == 1 ? 'bug' : 'bugs'); + } +} +print "\n"; + +exit $current_state; diff --git a/scripts/nuke-bugs.pl b/scripts/nuke-bugs.pl new file mode 100755 index 000000000..0226c1726 --- /dev/null +++ b/scripts/nuke-bugs.pl @@ -0,0 +1,69 @@ +#!/usr/bin/perl -w +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use strict; + +use lib qw(.); + +use Bugzilla; +use Bugzilla::Constants; + +use Getopt::Long; + +# This SQL is designed to delete the bugs and other activity in a Bugzilla database +# so that one can use for development purposes or to start using it as a fresh installation. +# Other data will be retained such as products, versions, flags, profiles, etc. + +$| = 1; +my $trace = 0; + +GetOptions("trace" => \$trace) || exit; + +my $dbh = Bugzilla->dbh; + +$dbh->{TraceLevel} = 1 if $trace; + +print <<EOF; +WARNING - This will delete all bugs, hit <enter> to continue or <ctrl-c> to cancel - WARNING +EOF +getc(); + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +$dbh->bz_start_transaction(); + +print "Deleting all bug data...\n"; + +delete_from_table('bug_group_map'); +delete_from_table('bugs_activity'); +delete_from_table('cc'); +delete_from_table('dependencies'); +delete_from_table('duplicates'); +delete_from_table('flags'); +delete_from_table('keywords'); +delete_from_table('attach_data'); +delete_from_table('attachments'); +delete_from_table('bug_group_map'); +delete_from_table('bugs'); +delete_from_table('longdescs'); + +$dbh->do($dbh->_bz_real_schema->get_set_serial_sql('bugs', 'bug_id', 1)); + +$dbh->bz_commit_transaction(); + +# This has to happen outside of the transaction +$dbh->do("DELETE FROM bugs_fulltext"); + +print "All done!\n"; + +sub delete_from_table { + my $table = shift; + print "Deleting from $table..."; + $dbh->do("DELETE FROM $table"); + print "done.\n"; +} diff --git a/scripts/reassign_open_bugs.pl b/scripts/reassign_open_bugs.pl new file mode 100755 index 000000000..6496f9a95 --- /dev/null +++ b/scripts/reassign_open_bugs.pl @@ -0,0 +1,87 @@ +#!/usr/bin/perl +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use 5.10.1; +use strict; +use warnings; + +use FindBin '$RealBin'; +use lib "$RealBin/../..", "$RealBin/../../lib"; + +use Bugzilla; +use Bugzilla::User; +use Bugzilla::Constants; + +use Getopt::Long qw( :config gnu_getopt ); +use Pod::Usage; + +# Load extensions for monkeypatched $user->clear_last_statistics_ts() +BEGIN { Bugzilla->extensions(); } + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +my ($from, $to); +GetOptions( + "from|f=s" => \$from, + "to|t=s" => \$to, +); + +pod2usage(1) unless defined $from && defined $to; + +my $dbh = Bugzilla->dbh; + +my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); +my $field = Bugzilla::Field->check({ name => 'assigned_to', cache => 1 }); +my $from_user = Bugzilla::User->check({ name => $from, cache => 1 }); +my $to_user = Bugzilla::User->check({ name => $to, cache => 1 }); + +my $bugs = $dbh->selectcol_arrayref(q{SELECT bug_id + FROM bugs + LEFT JOIN bug_status + ON bug_status.value = bugs.bug_status + WHERE bug_status.is_open = 1 + AND bugs.assigned_to = ?}, undef, $from_user->id); +my $bug_count = @$bugs; +if ($bug_count == 0) { + warn "There are no bugs to move.\n"; + exit 1; +} + +print STDERR <<EOF; +About to move $bug_count bugs from $from to $to. + +Press <Ctrl-C> to stop or <Enter> to continue... +EOF +getc(); + +$dbh->bz_start_transaction; +foreach my $bug_id (@$bugs) { + warn "Updating bug $bug_id\n"; + $dbh->do(q{INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) + VALUES (?, ?, ?, ?, ?, ?)}, + undef, $bug_id, $to_user->id, $timestamp, $field->id, $from_user->login, $to_user->login); + $dbh->do(q{UPDATE bugs SET assigned_to = ?, delta_ts = ?, lastdiffed = ? WHERE bug_id = ?}, + undef, $to_user->id, $timestamp, $timestamp, $bug_id); +} +$from_user->clear_last_statistics_ts(); +$to_user->clear_last_statistics_ts(); +$dbh->bz_commit_transaction; + +# It's complex to determine which items now need to be flushed from memcached. +# As this is expected to be a rare event, we just flush the entire cache. +Bugzilla->memcached->clear_all(); + +__END__ + +=head1 NAME + +reassign-open-bugs.pl - reassign all open bugs from one user to another. + +=head1 SYNOPSIS + + reassign-open-bugs.pl --from general@js.bugs --to nobody@mozilla.org diff --git a/scripts/reset_default_user.pl b/scripts/reset_default_user.pl new file mode 100755 index 000000000..173d03849 --- /dev/null +++ b/scripts/reset_default_user.pl @@ -0,0 +1,145 @@ +#!/usr/bin/perl -wT +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +use strict; + +use lib '.'; + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::User; +use Bugzilla::Field; +use Bugzilla::Util qw(trick_taint); + +use Getopt::Long; + +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +my $dbh = Bugzilla->dbh; + +my $field_name = ""; +my $product = ""; +my $component = ""; +my $help = ""; +my %user_cache = (); + +my $result = GetOptions('field=s' => \$field_name, + 'product=s' => \$product, + 'component=s' => \$component, + 'help|h' => \$help); + +sub usage { + print <<USAGE; +Usage: reset_default_user.pl --field <fieldname> --product <product> [--component <component>] [--help] + +This script will load all bugs matching the product, and optionally component, +and reset the default user value back to the default value for the component. +Valid field names are assigned_to and qa_contact. +USAGE +} + +if (!$product || $help + || ($field_name ne 'assigned_to' && $field_name ne 'qa_contact')) +{ + usage(); + exit(1); +} + +# We will need these for entering into bugs_activity +my $who = Bugzilla::User->new({ name => 'nobody@mozilla.org' }); +my $field = Bugzilla::Field->new({ name => $field_name }); + +trick_taint($product); +my $product_id = $dbh->selectrow_array( + "SELECT id FROM products WHERE name = ?", + undef, $product); +$product_id or die "Can't find product ID for '$product'.\n"; + +my $component_id; +my $default_user_id; +if ($component) { + trick_taint($component); + my $colname = $field->name eq 'qa_contact' + ? 'initialqacontact' + : 'initialowner'; + ($component_id, $default_user_id) = $dbh->selectrow_array( + "SELECT id, $colname FROM components " . + "WHERE name = ? AND product_id = ?", + undef, $component, $product_id); + $component_id or die "Can't find component ID for '$component'.\n"; + $user_cache{$default_user_id} ||= Bugzilla::User->new($default_user_id); +} + +# build list of bugs +my $bugs_query = "SELECT bug_id, qa_contact, component_id " . + "FROM bugs WHERE product_id = ?"; +my @args = ($product_id); + +if ($component_id) { + $bugs_query .= " AND component_id = ? AND qa_contact != ?"; + push(@args, $component_id, $default_user_id); +} + +my $bugs = $dbh->selectall_arrayref($bugs_query, {Slice => {}}, @args); +my $bug_count = scalar @$bugs; +$bug_count + or die "No bugs were found.\n"; + +# confirmation +print <<EOF; +About to reset $field_name for $bug_count bugs. + +Press <Ctrl-C> to stop or <Enter> to continue... +EOF +getc(); + +$dbh->bz_start_transaction(); + +foreach my $bug (@$bugs) { + my $bug_id = $bug->{bug_id}; + my $old_user_id = $bug->{$field->name}; + my $old_comp_id = $bug->{component_id}; + + # If only changing one component, we already have the default user id + my $new_user_id; + if ($default_user_id) { + $new_user_id = $default_user_id; + } + else { + my $colname = $field->name eq 'qa_contact' + ? 'initialqacontact' + : 'initialowner'; + $new_user_id = $dbh->selectrow_array( + "SELECT $colname FROM components WHERE id = ?", + undef, $old_comp_id); + } + + if ($old_user_id != $new_user_id) { + print "Resetting " . $field->name . " for bug $bug_id ..."; + + # Use the cached version if already exists + my $old_user = $user_cache{$old_user_id} ||= Bugzilla::User->new($old_user_id); + my $new_user = $user_cache{$new_user_id} ||= Bugzilla::User->new($new_user_id); + + my $timestamp = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); + + $dbh->do("UPDATE bugs SET " . $field->name . " = ? WHERE bug_id = ?", + undef, $new_user_id, $bug_id); + $dbh->do("INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) " . + "VALUES (?, ?, ?, ?, ?, ?)", + undef, $bug_id, $who->id, $timestamp, $field->id, $old_user->login, $new_user->login); + $dbh->do("UPDATE bugs SET delta_ts = ?, lastdiffed = ? WHERE bug_id = ?", + undef, $timestamp, $timestamp, $bug_id); + + Bugzilla->memcached->clear({ table => 'bugs', id => $bug_id }); + + print "done.\n"; + } +} + +$dbh->bz_commit_transaction(); diff --git a/scripts/sanitizeme.pl b/scripts/sanitizeme.pl new file mode 100755 index 000000000..af0c167bf --- /dev/null +++ b/scripts/sanitizeme.pl @@ -0,0 +1,230 @@ +#!/usr/bin/perl -w +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is the Mozilla +# Corporation. Portions created by Mozilla are +# Copyright (C) 2006 Mozilla Foundation. All Rights Reserved. +# +# Contributor(s): Myk Melez <myk@mozilla.org> +# Alex Brugh <alex@cs.umn.edu> +# Dave Miller <justdave@mozilla.com> +# Byron Jones <glob@mozilla.com> + +use strict; + +use FindBin '$RealBin'; +use lib "$RealBin/..", "$RealBin/lib"; + +use Bugzilla; +use Bugzilla::Bug; +use Bugzilla::Constants; +use Bugzilla::Hook; +use Bugzilla::Util; +use Getopt::Long; +use List::MoreUtils qw(uniq); +$| = 1; + +my $dbh = Bugzilla->dbh; + +# This SQL is designed to sanitize a copy of a Bugzilla database so that it +# doesn't contain any information that can't be viewed from a web browser by +# a user who is not logged in. + +my ($dry_run, $from_cron, $keep_attachments, $keep_group_bugs, $keep_groups, $execute, + $keep_passwords, $keep_insider, $trace, $enable_email) = (0, 0, 0, '', 0, 0, 0, 0, 0, 0); +my $keep_group_bugs_sql = ''; + +my $syntax = <<EOF; +options: +--execute perform database sanitization +--keep-attachments disable removal of attachment content +--keep-passwords disable resetting of passwords +--keep-insider disable removal of insider comments and attachments +--keep-group-bugs disable removal of the specified groups and associated bugs +--keep-groups disable removal of group definitions +--enable-email do not disable email for all users +--dry-run do not update the database, just output what will be deleted +--from-cron quite mode - suppress non-warning/error output +--trace output sql statements +EOF +GetOptions( + "execute" => \$execute, + "dry-run" => \$dry_run, + "from-cron" => \$from_cron, + "keep-attachments" => \$keep_attachments, + "keep-passwords" => \$keep_passwords, + "keep-insider" => \$keep_insider, + "keep-group-bugs:s" => \$keep_group_bugs, + "keep-groups" => \$keep_groups, + "trace" => \$trace, + "enable-email" => \$enable_email, +) or die $syntax; +die "--execute switch required to perform database sanitization.\n\n$syntax" + unless $execute or $dry_run; + +if ($keep_group_bugs ne '') { + my @groups; + foreach my $group_id (split(/\s*,\s*/, $keep_group_bugs)) { + my $group; + if ($group_id =~ /\D/) { + $group = Bugzilla::Group->new({ name => $group_id }); + } else { + $group = Bugzilla::Group->new($group_id); + } + die "Invalid group '$group_id'\n" unless $group; + push @groups, $group->id; + } + $keep_group_bugs_sql = "NOT IN (" . join(",", @groups) . ")"; +} + +$dbh->{TraceLevel} = 1 if $trace; + +if ($dry_run) { + print "** dry run : no changes to the database will be made **\n"; + $dbh->bz_start_transaction(); +} +eval { + delete_non_public_products(); + delete_secure_bugs(); + delete_deleted_comments(); + delete_insider_comments() unless $keep_insider; + delete_security_groups() unless $keep_groups; + delete_sensitive_user_data(); + delete_attachment_data() unless $keep_attachments; + delete_bug_user_last_visit(); + Bugzilla::Hook::process('db_sanitize'); + disable_email_delivery() unless $enable_email; + print "All done!\n"; + $dbh->bz_rollback_transaction() if $dry_run; +}; +if ($@) { + $dbh->bz_rollback_transaction() if $dry_run; + die "$@" if $@; +} + +sub delete_non_public_products { + # Delete all non-public products, and all data associated with them + my @products = Bugzilla::Product->get_all(); + my $mandatory = CONTROLMAPMANDATORY; + foreach my $product (@products) { + # if there are any mandatory groups on the product, nuke it and + # everything associated with it (including the bugs) + Bugzilla->params->{'allowbugdeletion'} = 1; # override this in memory for now + my $mandatorygroups = $dbh->selectcol_arrayref("SELECT group_id FROM group_control_map WHERE product_id = ? AND (membercontrol = $mandatory)", undef, $product->id); + if (0 < scalar(@$mandatorygroups)) { + print "Deleting product '" . $product->name . "'...\n"; + $product->remove_from_db(); + } + } +} + +sub delete_secure_bugs { + # Delete all data for bugs in security groups. + my $buglist = $dbh->selectall_arrayref( + $keep_group_bugs + ? "SELECT DISTINCT bug_id FROM bug_group_map WHERE group_id $keep_group_bugs_sql" + : "SELECT DISTINCT bug_id FROM bug_group_map" + ); + my $numbugs = scalar(@$buglist); + my $bugnum = 0; + print "Deleting $numbugs bugs in " . ($keep_group_bugs ? 'non-' : '') . "security groups...\n"; + foreach my $row (@$buglist) { + my $bug_id = $row->[0]; + $bugnum++; + print "\r$bugnum/$numbugs" unless $from_cron; + my $bug = new Bugzilla::Bug($bug_id); + $bug->remove_from_db(); + } + print "\rDone \n" unless $from_cron; +} + +sub delete_deleted_comments { + # Delete all comments tagged as 'deleted' + my $comment_ids = $dbh->selectcol_arrayref("SELECT comment_id FROM longdescs_tags WHERE tag='deleted'"); + return unless @$comment_ids; + print "Deleting 'deleted' comments...\n"; + my @bug_ids = uniq @{ + $dbh->selectcol_arrayref("SELECT bug_id FROM longdescs WHERE comment_id IN (" . join(',', @$comment_ids) . ")") + }; + $dbh->do("DELETE FROM longdescs WHERE comment_id IN (" . join(',', @$comment_ids) . ")"); + foreach my $bug_id (@bug_ids) { + Bugzilla::Bug->new($bug_id)->_sync_fulltext(update_comments => 1); + } +} + +sub delete_insider_comments { + # Delete all 'insidergroup' comments and attachments + print "Deleting 'insidergroup' comments and attachments...\n"; + $dbh->do("DELETE FROM longdescs WHERE isprivate = 1"); + $dbh->do("DELETE attach_data FROM attachments JOIN attach_data ON attachments.attach_id = attach_data.id WHERE attachments.isprivate = 1"); + $dbh->do("DELETE FROM attachments WHERE isprivate = 1"); + $dbh->do("UPDATE bugs_fulltext SET comments = comments_noprivate"); +} + +sub delete_security_groups { + # Delete all security groups. + print "Deleting " . ($keep_group_bugs ? 'non-' : '') . "security groups...\n"; + $dbh->do("DELETE user_group_map FROM groups JOIN user_group_map ON groups.id = user_group_map.group_id WHERE groups.isbuggroup = 1"); + $dbh->do("DELETE group_group_map FROM groups JOIN group_group_map ON (groups.id = group_group_map.member_id OR groups.id = group_group_map.grantor_id) WHERE groups.isbuggroup = 1"); + $dbh->do("DELETE group_control_map FROM groups JOIN group_control_map ON groups.id = group_control_map.group_id WHERE groups.isbuggroup = 1"); + $dbh->do("UPDATE flagtypes LEFT JOIN groups ON flagtypes.grant_group_id = groups.id SET grant_group_id = NULL WHERE groups.isbuggroup = 1"); + $dbh->do("UPDATE flagtypes LEFT JOIN groups ON flagtypes.request_group_id = groups.id SET request_group_id = NULL WHERE groups.isbuggroup = 1"); + if ($keep_group_bugs) { + $dbh->do("DELETE FROM groups WHERE isbuggroup = 1 AND id $keep_group_bugs_sql"); + } else { + $dbh->do("DELETE FROM groups WHERE isbuggroup = 1"); + } +} + +sub delete_sensitive_user_data { + # Remove sensitive user account data. + print "Deleting sensitive user account data...\n"; + $dbh->do("UPDATE profiles SET cryptpassword = 'deleted'") unless $keep_passwords; + $dbh->do("DELETE FROM user_api_keys"); + $dbh->do("DELETE FROM profiles_activity"); + $dbh->do("DELETE FROM profile_search"); + $dbh->do("DELETE FROM namedqueries"); + $dbh->do("DELETE FROM tokens"); + $dbh->do("DELETE FROM logincookies"); + $dbh->do("DELETE FROM login_failure"); + $dbh->do("DELETE FROM audit_log"); + # queued bugmail + $dbh->do("DELETE FROM ts_error"); + $dbh->do("DELETE FROM ts_exitstatus"); + $dbh->do("DELETE FROM ts_funcmap"); + $dbh->do("DELETE FROM ts_job"); + $dbh->do("DELETE FROM ts_note"); +} + +sub delete_attachment_data { + # Delete unnecessary attachment data. + print "Removing attachment data to preserve disk space...\n"; + $dbh->do("UPDATE attach_data SET thedata = ''"); +} + +sub delete_bug_user_last_visit { + print "Removing all entries from bug_user_last_visit...\n"; + $dbh->do('TRUNCATE TABLE bug_user_last_visit'); +} + +sub disable_email_delivery { + # turn off email delivery for all users. + print "Turning off email delivery...\n"; + $dbh->do("UPDATE profiles SET disable_mail = 1"); + + # Also clear out the default flag cc as well since they do not + # have to be in the profiles table + $dbh->do("UPDATE flagtypes SET cc_list = NULL"); +} diff --git a/scripts/sendunsentbugmail.pl b/scripts/sendunsentbugmail.pl new file mode 100755 index 000000000..eeee41ced --- /dev/null +++ b/scripts/sendunsentbugmail.pl @@ -0,0 +1,60 @@ +#!/usr/bin/perl -wT +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Netscape Communications +# Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Dave Miller <justdave@bugzilla.org> +# Myk Melez <myk@mozilla.org> + +use 5.10.1; +use strict; +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::BugMail; + +my $dbh = Bugzilla->dbh; + +my $list = $dbh->selectcol_arrayref( + 'SELECT bug_id FROM bugs + WHERE lastdiffed IS NULL + OR lastdiffed < delta_ts + AND delta_ts < ' + . $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE') . + ' ORDER BY bug_id'); + +if (scalar(@$list) > 0) { + say "OK, now attempting to send unsent mail"; + say scalar(@$list) . " bugs found with possibly unsent mail.\n"; + foreach my $bugid (@$list) { + my $start_time = time; + say "Sending mail for bug $bugid..."; + my $outputref = Bugzilla::BugMail::Send($bugid); + if ($ARGV[0] && $ARGV[0] eq "--report") { + say "Mail sent to:"; + say $_ foreach (sort @{$outputref->{sent}}); + } + else { + my $sent = scalar @{$outputref->{sent}}; + say "$sent mails sent."; + say "Took " . (time - $start_time) . " seconds.\n"; + } + } + say "Unsent mail has been sent."; +} diff --git a/scripts/syncflags.pl b/scripts/syncflags.pl new file mode 100755 index 000000000..520a8305c --- /dev/null +++ b/scripts/syncflags.pl @@ -0,0 +1,88 @@ +#!/usr/bin/perl -w +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Netscape Communications +# Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Gervase Markham <gerv@gerv.net> + +# See also https://bugzilla.mozilla.org/show_bug.cgi?id=119569 + +use strict; + +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Constants; + +sub usage() { + print <<USAGE; +Usage: syncflags.pl <srcproduct> <tgtproduct> + +E.g.: syncflags.pl FoodReplicator SeaMonkey +will copy any flag inclusions (only) for the product "FoodReplicator" +so matching inclusions exist for the product "SeaMonkey". This script is +normally used prior to moving components from srcproduct to tgtproduct. +USAGE + + exit(1); +} + +############################################################################# +# MAIN CODE +############################################################################# + +# This is a pure command line script. +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +if (scalar @ARGV < 2) { + usage(); + exit(); +} + +my ($srcproduct, $tgtproduct) = @ARGV; + +my $dbh = Bugzilla->dbh; + +# Find product IDs +my $srcprodid = $dbh->selectrow_array("SELECT id FROM products WHERE name = ?", + undef, $srcproduct); +if (!$srcprodid) { + print "Can't find product ID for '$srcproduct'.\n"; + exit(1); +} + +my $tgtprodid = $dbh->selectrow_array("SELECT id FROM products WHERE name = ?", + undef, $tgtproduct); +if (!$tgtprodid) { + print "Can't find product ID for '$tgtproduct'.\n"; + exit(1); +} + +$dbh->do("INSERT INTO flaginclusions(component_id, type_id, product_id) + SELECT fi1.component_id, fi1.type_id, ? FROM flaginclusions fi1 + LEFT JOIN flaginclusions fi2 + ON fi1.type_id = fi2.type_id + AND fi2.product_id = ? + WHERE fi1.product_id = ? + AND fi2.type_id IS NULL", + undef, + $tgtprodid, $tgtprodid, $srcprodid); + +# It's complex to determine which items now need to be flushed from memcached. +# As this is expected to be a rare event, we just flush the entire cache. +Bugzilla->memcached->clear_all(); diff --git a/scripts/syncmsandversions.pl b/scripts/syncmsandversions.pl new file mode 100755 index 000000000..20e88252e --- /dev/null +++ b/scripts/syncmsandversions.pl @@ -0,0 +1,122 @@ +#!/usr/bin/perl -w +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Netscape Communications +# Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Gervase Markham <gerv@gerv.net> + +# See also https://bugzilla.mozilla.org/show_bug.cgi?id=119569 + +use strict; + +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Constants; + +sub usage() { + print <<USAGE; +Usage: syncmsandversions.pl <srcproduct> <tgtproduct> + +E.g.: syncmsandversions.pl FoodReplicator SeaMonkey +will copy any versions and milstones in the product "FoodReplicator" +which do not exist in product "SeaMonkey" into it. This script is normally +used prior to moving components from srcproduct to tgtproduct. +USAGE + + exit(1); +} + +############################################################################# +# MAIN CODE +############################################################################# + +# This is a pure command line script. +Bugzilla->usage_mode(USAGE_MODE_CMDLINE); + +if (scalar @ARGV < 2) { + usage(); + exit(); +} + +my ($srcproduct, $tgtproduct) = @ARGV; + +my $dbh = Bugzilla->dbh; + +# Find product IDs +my $srcprodid = $dbh->selectrow_array("SELECT id FROM products WHERE name = ?", + undef, $srcproduct); +if (!$srcprodid) { + print "Can't find product ID for '$srcproduct'.\n"; + exit(1); +} + +my $tgtprodid = $dbh->selectrow_array("SELECT id FROM products WHERE name = ?", + undef, $tgtproduct); +if (!$tgtprodid) { + print "Can't find product ID for '$tgtproduct'.\n"; + exit(1); +} + +$dbh->bz_start_transaction(); + +$dbh->do(" + INSERT INTO milestones(value, sortkey, isactive, product_id) + SELECT m1.value, m1.sortkey, m1.isactive, ? + FROM milestones m1 + LEFT JOIN milestones m2 ON m1.value = m2.value + AND m2.product_id = ? + WHERE m1.product_id = ? + AND m2.value IS NULL + ", + undef, + $tgtprodid, $tgtprodid, $srcprodid); + +$dbh->do(" + INSERT INTO versions(value, isactive, product_id) + SELECT v1.value, v1.isactive, ? + FROM versions v1 + LEFT JOIN versions v2 ON v1.value = v2.value + AND v2.product_id = ? + WHERE v1.product_id = ? + AND v2.value IS NULL + ", + undef, + $tgtprodid, $tgtprodid, $srcprodid); + +$dbh->do(" + INSERT INTO group_control_map (group_id, product_id, entry, membercontrol, + othercontrol, canedit, editcomponents, + editbugs, canconfirm) + SELECT g1.group_id, ?, g1.entry, g1.membercontrol, g1.othercontrol, + g1.canedit, g1.editcomponents, g1.editbugs, g1.canconfirm + FROM group_control_map g1 + LEFT JOIN group_control_map g2 ON g1.product_id = ? + AND g2.product_id = ? + AND g1.group_id = g2.group_id + WHERE g1.product_id = ? + AND g2.group_id IS NULL + ", + undef, + $tgtprodid, $srcprodid, $tgtprodid, $srcprodid); + +$dbh->bz_commit_transaction(); + +# It's complex to determine which items now need to be flushed from memcached. +# As this is expected to be a rare event, we just flush the entire cache. +Bugzilla->memcached->clear_all(); |