summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDylan William Hardison <dylan@hardison.net>2018-04-01 16:52:36 +0200
committerDylan William Hardison <dylan@hardison.net>2018-04-01 16:52:36 +0200
commitab229b9a828b77f8a3b9ce215f0dfed4c84d4ae5 (patch)
tree483da9c8b66f4444bb8a410e3d599c7484ad721e
parentdaa2d6b1c40354ecce0e48e6c5ee686efe642c4b (diff)
parent2f8b999750cc700faf03c6aee1c53d1fc4df767f (diff)
downloadbugzilla-ab229b9a828b77f8a3b9ce215f0dfed4c84d4ae5.tar.gz
bugzilla-ab229b9a828b77f8a3b9ce215f0dfed4c84d4ae5.tar.xz
Merge branch 'master' into unstable
-rw-r--r--.circleci/config.yml147
-rw-r--r--.htaccess10
-rw-r--r--.perlcriticrc23
-rw-r--r--Bugzilla.pm4
-rw-r--r--Bugzilla/BugMail.pm2
-rw-r--r--Bugzilla/CGI.pm14
-rw-r--r--Bugzilla/Constants.pm5
-rw-r--r--Bugzilla/DB.pm9
-rw-r--r--Bugzilla/DaemonControl.pm70
-rw-r--r--Bugzilla/Error.pm10
-rw-r--r--Bugzilla/Install/Filesystem.pm1
-rw-r--r--Bugzilla/Install/Localconfig.pm22
-rw-r--r--Bugzilla/JobQueue.pm31
-rw-r--r--Bugzilla/JobQueue/Runner.pm175
-rw-r--r--Bugzilla/JobQueue/Worker.pm30
-rw-r--r--Bugzilla/Mailer.pm17
-rw-r--r--Bugzilla/Memcached.pm39
-rw-r--r--Bugzilla/ModPerl.pm8
-rw-r--r--Bugzilla/ModPerl/BasicAuth.pm13
-rw-r--r--Bugzilla/ModPerl/Hostage.pm71
-rw-r--r--Bugzilla/Send/Sendmail.pm2
-rw-r--r--Bugzilla/Template.pm9
-rw-r--r--Bugzilla/Util.pm43
-rw-r--r--Bugzilla/WebService/Server/REST.pm3
-rwxr-xr-xMakefile.PL2
-rw-r--r--README.rst56
-rwxr-xr-xchecksetup.pl11
-rw-r--r--conf/httpd.conf5
-rw-r--r--docker-compose.yml14
-rw-r--r--extensions/BMO/template/en/default/bug/create/comment-ipc.txt.tmpl47
-rw-r--r--extensions/BMO/template/en/default/bug/create/create-ipc.html.tmpl259
-rw-r--r--extensions/BMO/template/en/default/pages/user_activity.html.tmpl1
-rw-r--r--extensions/BMO/web/images/ipc_form_buildscreen.pngbin102103 -> 0 bytes
-rw-r--r--extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl2
-rw-r--r--extensions/BugModal/template/en/default/bug_modal/navigate.html.tmpl4
-rw-r--r--extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl1
-rw-r--r--extensions/OpenGraph/template/en/default/hook/global/header-start.html.tmpl7
-rw-r--r--extensions/PhabBugz/Extension.pm1
-rw-r--r--extensions/PhabBugz/lib/Daemon.pm5
-rw-r--r--extensions/PhabBugz/lib/Feed.pm98
-rw-r--r--extensions/PhabBugz/lib/Logger.pm37
-rw-r--r--extensions/PhabBugz/lib/Revision.pm2
-rw-r--r--extensions/PhabBugz/lib/Util.pm12
-rw-r--r--extensions/Push/lib/Logger.pm52
-rw-r--r--extensions/Review/Extension.pm2
-rw-r--r--extensions/UserProfile/template/en/default/pages/user_profile.html.tmpl2
-rwxr-xr-xheartbeat.cgi4
-rw-r--r--helper.psgi35
-rw-r--r--jobqueue-worker.pl47
-rwxr-xr-xjobqueue.pl11
-rw-r--r--mod_perl.pl6
-rwxr-xr-xscripts/build-bmo-push-data.pl203
-rwxr-xr-xscripts/cereal.pl5
-rwxr-xr-xscripts/entrypoint.pl8
-rwxr-xr-xses/index.cgi141
-rw-r--r--template/en/default/account/auth/login.html.tmpl2
-rw-r--r--template/en/default/account/prefs/mfa.html.tmpl6
-rw-r--r--template/en/default/admin/admin.html.tmpl2
-rw-r--r--template/en/default/bug/activity/table.html.tmpl1
-rw-r--r--template/en/default/email/new-api-key.txt.tmpl8
-rw-r--r--template/en/default/setup/strings.txt.pl6
-rw-r--r--vagrant_support/bmo-generate-data.j21
-rw-r--r--vagrant_support/checksetup_answers.j21
63 files changed, 1171 insertions, 694 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3ed28133d..eca472ceb 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -11,8 +11,6 @@ main_filters: &main_filters
- /^(?:release|test)-20\d\d\d\d\d\d\.\d+/
- /\//
- production
- tags:
- only: /^(?:release|test)-20\d\d\d\d\d\d\.\d+/
defaults:
bmo_slim_image: &bmo_slim_image
@@ -63,12 +61,61 @@ defaults:
run:
name: default qa setup
command: |
+ [[ -f build_info/only_version_changed.txt ]] && exit 0
mv /opt/bmo/local /app/local
perl -MSys::Hostname -i -pE 's/bmo.test/hostname() . ":$ENV{PORT}"/ges' $BZ_QA_CONF_FILE
- /app/scripts/entrypoint.pl checksetup_gen_files --default-localconfig
+ /app/scripts/entrypoint.pl checksetup_gen_files --default-localconfig
mkdir artifacts
jobs:
+ build_info:
+ parallelism: 1
+ working_directory: /app
+ docker:
+ - <<: *bmo_slim_image
+ environment:
+ <<: *bmo_env
+ steps:
+ - checkout
+ - run:
+ name: build push data
+ command: |
+ mv /opt/bmo/local /app/local
+ perl checksetup.pl --no-database --no-templates --no-permissions
+ perl scripts/build-bmo-push-data.pl
+ - run:
+ name: only publish if tag exists
+ command: |
+ tag="$(cat build_info/tag.txt)"
+ git fetch --tags
+ if git tag | fgrep -q "$tag"; then
+ echo "tag $tag exists!"
+ else
+ echo "tag $tag does not exist"
+ echo yes > build_info/publish.txt
+ fi
+ - run:
+ name: check if only version changed
+ command: |
+ if git diff 'HEAD~..HEAD' --name-only | grep -qv '^Bugzilla.pm'; then
+ echo "more files than just Bugzilla.pm changed."
+ exit 0
+ fi
+ if git diff 'HEAD~..HEAD' |grep '^[+-][^+-]' | grep -qv '^[+-]our $VERSION'; then
+ echo "Something other than the version number changed."
+ exit 0
+ fi
+ if [[ "$CIRCLE_BRANCH" == "master" ]]; then
+ echo "Can't cut corners on the master branch"
+ exit 0
+ fi
+ echo yes > build_info/only_version_changed.txt
+ - persist_to_workspace:
+ root: /app/build_info
+ paths: ["*.txt"]
+ - store_artifacts:
+ path: /app/build_info
+
build:
working_directory: /app
docker:
@@ -85,39 +132,52 @@ jobs:
--build-arg CIRCLE_SHA1="$CIRCLE_SHA1" \
--build-arg CIRCLE_BUILD_URL="$CIRCLE_BUILD_URL" \
-t bmo .
+ - attach_workspace:
+ at: /app/build_info
+ - run: "docker run --name bmo --entrypoint true bmo"
+ - run: "docker cp bmo:/app/version.json build_info/version.json"
+ - store_artifacts:
+ path: /app/build_info
- deploy:
command: |
- if [[ -n "$DOCKERHUB_REPO" && -n "$DOCKER_USER" && -n "$DOCKER_PASS" ]]; then
- TAG=""
- if [[ -n "$CIRCLE_TAG" ]]; then
- TAG="$CIRCLE_TAG"
- elif [[ "$CIRCLE_BRANCH" == "master" ]]; then
- TAG=latest
- elif [[ "$CIRCLE_BRANCH" == "unstable" ]]; then
- TAG=unstable
- fi
- if [[ -n "$TAG" ]]; then
- docker tag bmo "$DOCKERHUB_REPO:$TAG"
- docker login -u "$DOCKER_USER" -p "$DOCKER_PASS"
- docker push "$DOCKERHUB_REPO:$TAG"
- fi
+ TAG="$(cat /app/build_info/tag.txt)"
+ [[ "$CIRCLE_BRANCH" == "master" ]] || exit 0
+ [[ -n "$DOCKERHUB_REPO" && -n "$DOCKER_USER" && -n "$DOCKER_PASS" ]] || exit 0
+ [[ -n "$GITHUB_PERSONAL_TOKEN" ]] || exit 0
+ docker login -u "$DOCKER_USER" -p "$DOCKER_PASS"
+ if [[ -n "$TAG" && -f build_info/publish.txt ]]; then
+ git config credential.helper "cache --timeout 120"
+ git config user.email "$GITHUB_EMAIL"
+ git config user.name "$GITHUB_NAME"
+ git tag $TAG
+ git push https://${GITHUB_PERSONAL_TOKEN}:x-oauth-basic@github.com/$GITHUB_REPO.git $TAG
+ docker tag bmo "$DOCKERHUB_REPO:$TAG"
+ docker push "$DOCKERHUB_REPO:$TAG"
fi
+ docker tag bmo "$DOCKERHUB_REPO:latest"
+ docker push "$DOCKERHUB_REPO:latest"
test_sanity:
- parallelism: 4
+ parallelism: 2
working_directory: /app
docker:
- <<: *bmo_slim_image
environment: *bmo_env
steps:
- checkout
+ - attach_workspace:
+ at: /app/build_info
- run: |
+ [[ -f build_info/only_version_changed.txt ]] && exit 0
mv /opt/bmo/local /app/local
mkdir artifacts
- - run: perl Makefile.PL
+ - run: |
+ [[ -f build_info/only_version_changed.txt ]] && exit 0
+ perl Makefile.PL
- run:
name: run sanity tests
command: |
+ [[ -f build_info/only_version_changed.txt ]] && exit 0
/app/scripts/entrypoint.pl prove -qf $(circleci tests glob 't/*.t' | circleci tests split) | tee artifacts/$CIRCLE_JOB.txt
- store_artifacts:
path: /app/artifacts
@@ -128,12 +188,15 @@ jobs:
docker: *docker_oldtests
steps:
- checkout
+ - attach_workspace:
+ at: /app/build_info
- *default_qa_setup
- run: |
+ [[ -f build_info/only_version_changed.txt ]] && exit 0
/app/scripts/entrypoint.pl load_test_data
- - run:
- command: |
- /app/scripts/entrypoint.pl test_webservices | tee artifacts/$CIRCLE_JOB.txt
+ - run: |
+ [[ -f build_info/only_version_changed.txt ]] && exit 0
+ /app/scripts/entrypoint.pl test_webservices | tee artifacts/$CIRCLE_JOB.txt
- store_artifacts:
path: /app/artifacts
@@ -143,12 +206,15 @@ jobs:
docker: *docker_oldtests
steps:
- checkout
+ - attach_workspace:
+ at: /app/build_info
- *default_qa_setup
- run: |
+ [[ -f build_info/only_version_changed.txt ]] && exit 0
/app/scripts/entrypoint.pl load_test_data --legacy
- - run:
- command: |
- /app/scripts/entrypoint.pl test_selenium | tee artifacts/$CIRCLE_JOB.txt
+ - run: |
+ [[ -f build_info/only_version_changed.txt ]] && exit 0
+ /app/scripts/entrypoint.pl test_selenium | tee artifacts/$CIRCLE_JOB.txt
- store_artifacts:
path: /app/artifacts
@@ -169,34 +235,45 @@ jobs:
- image: selenium/standalone-firefox:2.53.1
steps:
- checkout
+ - attach_workspace:
+ at: /app/build_info
- run: |
+ [[ -f build_info/only_version_changed.txt ]] && exit 0
mv /opt/bmo/local /app/local
/app/scripts/entrypoint.pl checksetup_gen_files
/app/scripts/entrypoint.pl load_test_data
mkdir artifacts
- - run: /app/scripts/entrypoint.pl test_bmo -q -f t/bmo/*.t
+ - run: |
+ [[ -f build_info/only_version_changed.txt ]] && exit 0
+ /app/scripts/entrypoint.pl test_bmo -q -f t/bmo/*.t
workflows:
version: 2
main:
jobs:
+ - build_info:
+ filters: *main_filters
+ - build:
+ filters: *main_filters
+ requires:
+ - build_info
+ - test_sanity
+ - test_bmo
+ - test_webservices
+ - test_selenium
- test_sanity:
filters: *main_filters
+ requires:
+ - build_info
- test_bmo:
filters: *main_filters
requires:
- - test_sanity
+ - build_info
- test_webservices:
filters: *main_filters
requires:
- - test_sanity
+ - build_info
- test_selenium:
filters: *main_filters
requires:
- - test_sanity
- - build:
- filters: *main_filters
- requires:
- - test_sanity
- - test_bmo
- - test_webservices
+ - build_info
diff --git a/.htaccess b/.htaccess
index e0a9a7dcc..fd14518bc 100644
--- a/.htaccess
+++ b/.htaccess
@@ -27,6 +27,8 @@ RewriteRule ^__version__$ version.json [L]
# heartbeat.cgi returns 200 if the DB and memcached are both working, and 500 otherwise.
RewriteRule ^__heartbeat__$ heartbeat.cgi [L]
+RewriteRule ^(\d+|quicksearch\.html|bugwritinghelp\.html)$ /helper/$1 [L]
+
RewriteRule ^static/v\d{4}\d{2}\d{2}\.\d+/(.+\.(?:js|css|woff2?|png|jpe?g|gif|ico|svg))$ $1 [NC,E=IMMUTABLE:1,L]
Header set Cache-Control "public, max-age=31536000" env=REDIRECT_IMMUTABLE
@@ -37,9 +39,9 @@ RewriteRule ^new[-_]bug$ new_bug.cgi [L,QSA]
RewriteRule ^template_cache/ - [F,L,NC]
RewriteRule ^template_cache.deleteme/ - [F,L,NC]
-RewriteRule ^review(.*) page.cgi?id=splinter.html$1 [QSA]
-RewriteRule ^user_?profile(.*) page.cgi?id=user_profile.html$1 [QSA]
-RewriteRule ^request_defer(.*) page.cgi?id=request_defer.html$1 [QSA]
+RewriteRule ^review$ page.cgi?id=splinter.html$1 [QSA]
+RewriteRule ^user_?profile$ page.cgi?id=user_profile.html$1 [QSA]
+RewriteRule ^request_defer$ page.cgi?id=request_defer.html$1 [QSA]
RewriteRule ^favicon\.ico$ extensions/BMO/web/images/favicon.ico
RewriteRule ^form[\.:]itrequest$ enter_bug.cgi?product=Infrastructure+\%26+Operations&format=itrequest [QSA]
RewriteRule ^form[\.:](mozlist|poweredby|presentation|trademark|recoverykey)$ enter_bug.cgi?product=mozilla.org&format=$1 [QSA]
@@ -76,7 +78,7 @@ RewriteRule ^form[\.:]third[\.\-:]party$ enter_bug.cgi?product=Marketing&format=
RewriteRule ^form[\.:]fsa[\.:]budget$ enter_bug.cgi?product=FSA&format=fsa-budget [QSA]
RewriteRule ^form[\.:]triage[\.\-]request$ page.cgi?id=triage_request.html [QSA]
RewriteRule ^form[\.:](crm|CRM)$ enter_bug.cgi?product=Marketing&format=crm [QSA]
-RewriteRule ^form[\.:](ipc|IPC)$ enter_bug.cgi?product=Marketing&format=ipc [QSA]
+RewriteRule ^form[\.:](ipc|IPC)$ https://airtable.com/shrcMqgbj1H9gXRlp [R,L]
RewriteRule ^form[\.:]nda$ enter_bug.cgi?product=Legal&format=nda [QSA]
RewriteRule ^form[\.:]name[\.:]clearance$ enter_bug.cgi?product=Legal&format=name-clearance [QSA]
RewriteRule ^form[\.:]shield[\.:]studies$ enter_bug.cgi?product=Shield&format=shield-studies [QSA]
diff --git a/.perlcriticrc b/.perlcriticrc
index 15ff6c82b..44254f64e 100644
--- a/.perlcriticrc
+++ b/.perlcriticrc
@@ -3,10 +3,28 @@ severity = 1
[-CodeLayout::RequireTidyCode]
#perltidyrc = .perltidyrc
+#severity = 2
+
[InputOutput::RequireCheckedSyscalls]
+severity = 2
functions = :builtins
exclude_functions = print say sleep binmode
+[ValuesAndExpressions::ProhibitInterpolationOfLiterals]
+severity = 2
+
+[Freenode::EmptyReturn]
+severity = 2
+
+[CodeLayout::RequireTrailingCommas]
+severity = 2
+
+[CodeLayout::ProhibitParensWithBuiltins]
+severity = 2
+
+[RegularExpressions::ProhibitUnusualDelimiters]
+severity = 2
+
[-BuiltinFunctions::ProhibitUselessTopic]
[-ControlStructures::ProhibitCascadingIfElse]
[-ControlStructures::ProhibitPostfixControls]
@@ -14,12 +32,13 @@ exclude_functions = print say sleep binmode
[-Documentation::RequirePodLinksIncludeText]
[-Documentation::RequirePodSections]
[-ErrorHandling::RequireCarping]
+[-InputOutput::RequireBracedFileHandleWithPrint]
[-Modules::RequireVersionVar]
[-References::ProhibitDoubleSigils]
[-RegularExpressions::ProhibitComplexRegexes]
[-RegularExpressions::RequireDotMatchAnything]
-[-RegularExpressions::RequireLineBoundaryMatching]
[-RegularExpressions::RequireExtendedFormatting]
+[-RegularExpressions::RequireLineBoundaryMatching]
[-Subroutines::ProhibitExcessComplexity]
[-ValuesAndExpressions::ProhibitConstantPragma]
[-ValuesAndExpressions::ProhibitEmptyQuotes]
@@ -46,7 +65,7 @@ exclude_functions = print say sleep binmode
# _build_* are allowed
[Subroutines::ProhibitUnusedPrivateSubroutines]
- private_name_regex = _(?!_|build_)\w+
+private_name_regex = _(?!_|build_)\w+
# I don't agree with this policy because
# a bare return can actually cause more problems.
diff --git a/Bugzilla.pm b/Bugzilla.pm
index afdfcefd2..e43110389 100644
--- a/Bugzilla.pm
+++ b/Bugzilla.pm
@@ -22,7 +22,7 @@ BEGIN {
}
}
-our $VERSION = '20180306.4';
+our $VERSION = '20180330.1';
use Bugzilla::Auth;
use Bugzilla::Auth::Persist::Cookie;
@@ -625,7 +625,7 @@ sub switch_to_shadow_db {
my $class = shift;
if (!$class->request_cache->{dbh_shadow}) {
- if ($class->params->{'shadowdb'}) {
+ if ($class->get_param_with_override('shadowdb')) {
$class->request_cache->{dbh_shadow} = Bugzilla::DB::connect_shadow();
} else {
$class->request_cache->{dbh_shadow} = $class->dbh_main;
diff --git a/Bugzilla/BugMail.pm b/Bugzilla/BugMail.pm
index defe7c84f..915405a0e 100644
--- a/Bugzilla/BugMail.pm
+++ b/Bugzilla/BugMail.pm
@@ -421,7 +421,7 @@ sub sendMail {
bugmailtype => $bugmailtype,
};
- if (Bugzilla->params->{'use_mailer_queue'}) {
+ if (Bugzilla->get_param_with_override('use_mailer_queue')) {
enqueue($vars);
} else {
MessageToMTA(_generate_bugmail($vars));
diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm
index ba82c83d0..b932116a2 100644
--- a/Bugzilla/CGI.pm
+++ b/Bugzilla/CGI.pm
@@ -11,6 +11,7 @@ use 5.10.1;
use strict;
use warnings;
+use Bugzilla::Logging;
use CGI;
use base qw(CGI);
@@ -597,7 +598,20 @@ sub header {
sub param {
my $self = shift;
+ # We don't let CGI.pm warn about list context, but we do it ourselves.
local $CGI::LIST_CONTEXT_WARN = 0;
+ if (0) {
+ state $has_warned = {};
+
+ ## no critic (Freenode::Wantarray)
+ if ( wantarray && @_ ) {
+ my ( $package, $filename, $line ) = caller;
+ if ( $package ne 'CGI' && ! $has_warned->{"$filename:$line"}++) {
+ WARN("Bugzilla::CGI::param called in list context from $package $filename:$line");
+ }
+ }
+ ## use critic
+ }
# When we are just requesting the value of a parameter...
if (scalar(@_) == 1) {
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm
index 2971c7a53..65b37dced 100644
--- a/Bugzilla/Constants.pm
+++ b/Bugzilla/Constants.pm
@@ -19,7 +19,6 @@ use Memoize;
@Bugzilla::Constants::EXPORT = qw(
BUGZILLA_VERSION
- REST_DOC
REMOTE_FILE
LOCAL_FILE
@@ -211,10 +210,6 @@ sub BUGZILLA_VERSION {
eval { Bugzilla->VERSION } || $bugzilla_version;
}
-# A base link to the current REST Documentation. We place it here
-# as it will need to be updated to whatever the current release is.
-use constant REST_DOC => "https://bugzilla.readthedocs.io/en/latest/api/";
-
# Location of the remote and local XML files to track new releases.
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
use constant LOCAL_FILE => 'bugzilla-update.xml'; # Relative to datadir.
diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm
index a2cff0bd4..15acfd0d9 100644
--- a/Bugzilla/DB.pm
+++ b/Bugzilla/DB.pm
@@ -109,8 +109,13 @@ sub connect_shadow {
my $connect_params = dclone(Bugzilla->localconfig);
$connect_params->{db_host} = Bugzilla->get_param_with_override('shadowdbhost');
$connect_params->{db_name} = Bugzilla->get_param_with_override('shadowdb');
- $connect_params->{db_port} = Bugzilla->get_param_with_override('shadowport');
- $connect_params->{db_sock} = Bugzilla->get_param_with_override('shadowsock');
+ $connect_params->{db_port} = Bugzilla->get_param_with_override('shadowdbport');
+ $connect_params->{db_sock} = Bugzilla->get_param_with_override('shadowdbsock');
+
+ if ( Bugzilla->localconfig->{'shadowdb_user'} && Bugzilla->localconfig->{'shadowdb_pass'} ) {
+ $connect_params->{db_user} = Bugzilla->localconfig->{'shadowdb_user'};
+ $connect_params->{db_pass} = Bugzilla->localconfig->{'shadowdb_pass'};
+ }
return _connect($connect_params);
}
diff --git a/Bugzilla/DaemonControl.pm b/Bugzilla/DaemonControl.pm
index b7f7bcbe9..6586cc01b 100644
--- a/Bugzilla/DaemonControl.pm
+++ b/Bugzilla/DaemonControl.pm
@@ -28,7 +28,8 @@ use POSIX qw(setsid WEXITSTATUS);
use base qw(Exporter);
our @EXPORT_OK = qw(
- run_httpd run_cereal run_cereal_and_httpd
+ run_httpd run_cereal run_jobqueue
+ run_cereal_and_httpd run_cereal_and_jobqueue
catch_signal on_finish on_exception
assert_httpd assert_database assert_selenium
);
@@ -39,10 +40,12 @@ our %EXPORT_TAGS = (
utils => [qw(catch_signal on_exception on_finish)],
);
-use constant CEREAL_BIN => realpath(catfile( bz_locations->{cgi_path}, 'scripts', 'cereal.pl'));
-
-use constant HTTPD_BIN => '/usr/sbin/httpd';
-use constant HTTPD_CONFIG => realpath(catfile( bz_locations->{confdir}, 'httpd.conf' ));
+use constant {
+ JOBQUEUE_BIN => realpath( catfile( bz_locations->{cgi_path}, 'jobqueue.pl' ) ),
+ CEREAL_BIN => realpath( catfile( bz_locations->{cgi_path}, 'scripts', 'cereal.pl' ) ),
+ HTTPD_BIN => '/usr/sbin/httpd',
+ HTTPD_CONFIG => realpath( catfile( bz_locations->{confdir}, 'httpd.conf' ) ),
+};
sub catch_signal {
my ($name, @done) = @_;
@@ -75,7 +78,7 @@ sub run_cereal {
my $cereal = IO::Async::Process->new(
command => [CEREAL_BIN],
on_finish => on_finish($exit_f),
- on_exception => on_exception( "cereal", $exit_f ),
+ on_exception => on_exception( 'cereal', $exit_f ),
);
$exit_f->on_cancel( sub { $cereal->kill('TERM') } );
$loop->add($cereal);
@@ -85,15 +88,18 @@ sub run_cereal {
sub run_httpd {
my (@args) = @_;
- my $loop = IO::Async::Loop->new;
+ my $loop = IO::Async::Loop->new;
my $exit_f = $loop->new_future;
my $httpd = IO::Async::Process->new(
code => sub {
+
# we have to setsid() to make a new process group
# or else apache will kill its parent.
setsid();
- exec HTTPD_BIN, '-DFOREGROUND', '-f' => HTTPD_CONFIG, @args;
+ my @command = ( HTTPD_BIN, '-DFOREGROUND', '-f' => HTTPD_CONFIG, @args );
+ exec @command
+ or die "failed to exec $command[0] $!";
},
on_finish => on_finish($exit_f),
on_exception => on_exception( 'httpd', $exit_f ),
@@ -104,21 +110,52 @@ sub run_httpd {
return $exit_f;
}
+sub run_jobqueue {
+ my (@args) = @_;
+
+ my $loop = IO::Async::Loop->new;
+ my $exit_f = $loop->new_future;
+ my $jobqueue = IO::Async::Process->new(
+ command => [ JOBQUEUE_BIN, 'start', '-f', '-d', @args ],
+ on_finish => on_finish($exit_f),
+ on_exception => on_exception( 'httpd', $exit_f ),
+ );
+ $exit_f->on_cancel( sub { $jobqueue->kill('TERM') } );
+ $loop->add($jobqueue);
+
+ return $exit_f;
+}
+
+sub run_cereal_and_jobqueue {
+ my (@jobqueue_args) = @_;
+
+ my $signal_f = catch_signal('TERM', 0);
+ my $cereal_exit_f = run_cereal();
+
+ return assert_cereal()->then(
+ sub {
+ my $jobqueue_exit_f = run_jobqueue(@jobqueue_args);
+ return Future->wait_any($cereal_exit_f, $jobqueue_exit_f, $signal_f);
+ }
+ );
+}
+
sub run_cereal_and_httpd {
my @httpd_args = @_;
- push @httpd_args, '-DNETCAT_LOGS';
- my $signal_f = catch_signal("TERM", 0);
+ my $signal_f = catch_signal('TERM', 0);
my $cereal_exit_f = run_cereal();
return assert_cereal()->then(
sub {
+ push @httpd_args, '-DNETCAT_LOGS';
+
my $lc = Bugzilla::Install::Localconfig::read_localconfig();
if ( ($lc->{inbound_proxies} // '') eq '*' && $lc->{urlbase} =~ /^https/) {
push @httpd_args, '-DHTTPS';
}
- elsif (not $lc->{urlbase} =~ /^https/) {
- WARN("HTTPS urlbase but inbound_proxies is not '*'");
+ elsif ($lc->{urlbase} =~ /^https/) {
+ WARN('HTTPS urlbase but inbound_proxies is not "*"');
}
my $httpd_exit_f = run_httpd(@httpd_args);
@@ -140,24 +177,23 @@ sub assert_httpd {
my $f = shift;
( $f->get =~ /^httpd OK/ );
};
- my $timeout = $loop->timeout_future(after => 20)->else_fail("assert_httpd timeout");
+ my $timeout = $loop->timeout_future(after => 20)->else_fail('assert_httpd timeout');
return Future->wait_any($repeat, $timeout);
}
-
sub assert_selenium {
my ($host, $port) = @_;
$host //= 'localhost';
$port //= 4444;
- return assert_connect($host, $port, "assert_selenium");
+ return assert_connect($host, $port, 'assert_selenium');
}
sub assert_cereal {
return assert_connect(
'localhost',
$ENV{LOGGING_PORT} // 5880,
- "assert_cereal"
+ 'assert_cereal'
);
}
@@ -199,7 +235,7 @@ sub assert_database {
);
} until => sub { defined shift->get };
- my $timeout = $loop->timeout_future( after => 20 )->else_fail("assert_database timeout");
+ my $timeout = $loop->timeout_future( after => 20 )->else_fail('assert_database timeout');
my $any_f = Future->wait_any( $repeat, $timeout );
return $any_f->transform(
done => sub { return },
diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm
index e7a99dba0..d67571848 100644
--- a/Bugzilla/Error.pm
+++ b/Bugzilla/Error.pm
@@ -38,15 +38,14 @@ sub _in_eval {
sub _throw_error {
my ($name, $error, $vars) = @_;
- my $dbh = Bugzilla->dbh;
$vars ||= {};
-
$vars->{error} = $error;
# Make sure any transaction is rolled back (if supported).
# If we are within an eval(), do not roll back transactions as we are
# eval'uating some test on purpose.
- $dbh->bz_rollback_transaction() if ($dbh->bz_in_transaction() && !_in_eval());
+ my $dbh = eval { Bugzilla->dbh };
+ $dbh->bz_rollback_transaction() if ($dbh && $dbh->bz_in_transaction() && !_in_eval());
my $datadir = bz_locations()->{'datadir'};
# If a writable $datadir/errorlog exists, log error details there.
@@ -191,10 +190,9 @@ sub ThrowCodeError {
sub ThrowTemplateError {
my ($template_err) = @_;
- my $dbh = Bugzilla->dbh;
-
+ my $dbh = eval { Bugzilla->dbh };
# Make sure the transaction is rolled back (if supported).
- $dbh->bz_rollback_transaction() if $dbh->bz_in_transaction();
+ $dbh->bz_rollback_transaction() if $dbh && $dbh->bz_in_transaction();
if (blessed($template_err) && $template_err->isa('Template::Exception')) {
my $type = $template_err->type;
diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm
index 08b824cad..5e51dd9cc 100644
--- a/Bugzilla/Install/Filesystem.pm
+++ b/Bugzilla/Install/Filesystem.pm
@@ -271,6 +271,7 @@ sub FILESYSTEM {
'metrics.pl' => { perms => WS_EXECUTE },
'Makefile.PL' => { perms => OWNER_EXECUTE },
'gen-cpanfile.pl' => { perms => OWNER_EXECUTE },
+ 'jobqueue-worker.pl' => { perms => OWNER_EXECUTE },
'clean-bug-user-last-visit.pl' => { perms => WS_EXECUTE },
'Bugzilla.pm' => { perms => CGI_READ },
diff --git a/Bugzilla/Install/Localconfig.pm b/Bugzilla/Install/Localconfig.pm
index ba8e8dc57..7a913358c 100644
--- a/Bugzilla/Install/Localconfig.pm
+++ b/Bugzilla/Install/Localconfig.pm
@@ -43,7 +43,7 @@ our @EXPORT_OK = qw(
# might want to change this for upstream
use constant ENV_PREFIX => 'BMO_';
-use constant PARAM_OVERRIDE => qw( shadowdb shadowdbhost shadowdbport shadowdbsock );
+use constant PARAM_OVERRIDE => qw( use_mailer_queue mail_delivery_method shadowdb shadowdbhost shadowdbport shadowdbsock );
sub _sensible_group {
return '' if ON_WINDOWS;
@@ -135,12 +135,12 @@ use constant LOCALCONFIG_VARS => (
{
name => 'param_override',
default => {
- memcached_servers => undef,
- memcached_namespace => undef,
- shadowdb => undef,
- shadowdbhost => undef,
- shadowdbport => undef,
- shadowdbsock => undef,
+ use_mailer_queue => undef,
+ mail_delivery_method => undef,
+ shadowdb => undef,
+ shadowdbhost => undef,
+ shadowdbport => undef,
+ shadowdbsock => undef,
},
},
{
@@ -175,6 +175,14 @@ use constant LOCALCONFIG_VARS => (
name => 'inbound_proxies',
default => _migrate_param( 'inbound_proxies', '' ),
},
+ {
+ name => 'shadowdb_user',
+ default => '',
+ },
+ {
+ name => 'shadowdb_pass',
+ default => '',
+ }
);
diff --git a/Bugzilla/JobQueue.pm b/Bugzilla/JobQueue.pm
index 55d40bfb8..53b088c6e 100644
--- a/Bugzilla/JobQueue.pm
+++ b/Bugzilla/JobQueue.pm
@@ -11,9 +11,14 @@ use 5.10.1;
use strict;
use warnings;
+use Bugzilla::Logging;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Install::Util qw(install_string);
+use Bugzilla::DaemonControl qw(catch_signal);
+use IO::Async::Timer::Periodic;
+use IO::Async::Loop;
+use Future;
use base qw(TheSchwartz);
# This maps job names for Bugzilla::JobQueue to the appropriate modules.
@@ -91,6 +96,32 @@ sub insert {
return $retval;
}
+sub debug {
+ my ($self, @args) = @_;
+ my $caller_pkg = caller;
+ local $Log::Log4perl::caller_depth = $Log::Log4perl::caller_depth + 1;
+ my $logger = Log::Log4perl->get_logger($caller_pkg);
+ $logger->info(@args);
+}
+
+sub work {
+ my ($self, $delay) = @_;
+ $delay ||= 1;
+ my $loop = IO::Async::Loop->new;
+ my $timer = IO::Async::Timer::Periodic->new(
+ first_interval => 0,
+ interval => $delay,
+ reschedule => 'drift',
+ on_tick => sub { $self->work_once }
+ );
+ DEBUG("working every $delay seconds");
+ $loop->add($timer);
+ $timer->start;
+ Future->wait_any(map { catch_signal($_) } qw( INT TERM HUP ))->get;
+ $timer->stop;
+ $loop->remove($timer);
+}
+
# Clear the request cache at the start of each run.
sub work_once {
my $self = shift;
diff --git a/Bugzilla/JobQueue/Runner.pm b/Bugzilla/JobQueue/Runner.pm
index 5b3164ef9..0177de40a 100644
--- a/Bugzilla/JobQueue/Runner.pm
+++ b/Bugzilla/JobQueue/Runner.pm
@@ -14,23 +14,34 @@ package Bugzilla::JobQueue::Runner;
use 5.10.1;
use strict;
use warnings;
+use autodie qw(open close unlink system);
+use Bugzilla::Logging;
+use Bugzilla::Constants;
+use Bugzilla::DaemonControl qw(:utils);
+use Bugzilla::JobQueue::Worker;
+use Bugzilla::JobQueue;
+use Bugzilla::Util qw(get_text);
use Cwd qw(abs_path);
+use English qw(-no_match_vars $PROGRAM_NAME $EXECUTABLE_NAME);
use File::Basename;
use File::Copy;
+use File::Spec::Functions qw(catfile tmpdir);
+use Future;
+use Future::Utils qw(fmap_void);
+use IO::Async::Loop;
+use IO::Async::Process;
+use IO::Async::Signal;
use Pod::Usage;
-use Bugzilla::Constants;
-use Bugzilla::JobQueue;
-use Bugzilla::Util qw(get_text);
-BEGIN { eval "use base qw(Daemon::Generic)"; }
+use parent qw(Daemon::Generic);
-our $VERSION = BUGZILLA_VERSION;
+our $VERSION = 2;
# Info we need to install/uninstall the daemon.
-our $chkconfig = "/sbin/chkconfig";
-our $initd = "/etc/init.d";
-our $initscript = "bugzilla-queue";
+our $chkconfig = '/sbin/chkconfig';
+our $initd = '/etc/init.d';
+our $initscript = 'bugzilla-queue';
# The Daemon::Generic docs say that it uses all sorts of
# things from gd_preconfig, but in fact it does not. The
@@ -40,11 +51,10 @@ sub gd_preconfig {
my $self = shift;
my $pidfile = $self->{gd_args}{pidfile};
- if (!$pidfile) {
- $pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname}
- . ".pid";
+ if ( !$pidfile ) {
+ $pidfile = catfile(tmpdir(), $self->{gd_progname} . '.pid');
}
- return (pidfile => $pidfile);
+ return ( pidfile => $pidfile );
}
# All config other than the pidfile has to be done in gd_getopt
@@ -54,24 +64,30 @@ sub gd_getopt {
$self->SUPER::gd_getopt();
- if ($self->{gd_args}{progname}) {
+ if ( $self->{gd_args}{progname} ) {
$self->{gd_progname} = $self->{gd_args}{progname};
}
else {
- $self->{gd_progname} = basename($0);
+ $self->{gd_progname} = basename($PROGRAM_NAME);
}
- # There are places that Daemon Generic's new() uses $0 instead of
+ # There are places that Daemon Generic's new() uses $PROGRAM_NAME instead of
# gd_progname, which it really shouldn't, but this hack fixes it.
- $self->{_original_zero} = $0;
- $0 = $self->{gd_progname};
+ $self->{_original_program_name} = $PROGRAM_NAME;
+
+ ## no critic (Variables::RequireLocalizedPunctuationVars)
+ $PROGRAM_NAME = $self->{gd_progname};
+ ## use critic
}
sub gd_postconfig {
my $self = shift;
+
# See the hack above in gd_getopt. This just reverses it
# in case anything else needs the accurate $0.
- $0 = delete $self->{_original_zero};
+ ## no critic (Variables::RequireLocalizedPunctuationVars)
+ $PROGRAM_NAME = delete $self->{_original_program_name};
+ ## use critic
}
sub gd_more_opt {
@@ -79,12 +95,13 @@ sub gd_more_opt {
return (
'pidfile=s' => \$self->{gd_args}{pidfile},
'n=s' => \$self->{gd_args}{progname},
+ 'jobs|j=i' => \$self->{gd_args}{jobs},
);
}
sub gd_usage {
- pod2usage({ -verbose => 0, -exitval => 'NOEXIT' });
- return 0
+ pod2usage( { -verbose => 0, -exitval => 'NOEXIT' } );
+ return 0;
}
sub gd_can_install {
@@ -95,66 +112,63 @@ sub gd_can_install {
my $sysconfig = '/etc/sysconfig';
my $config_file = "$sysconfig/$initscript";
- if (!-x $chkconfig or !-d $initd) {
+ if ( !-x $chkconfig || !-d $initd ) {
return $self->SUPER::gd_can_install(@_);
}
return sub {
- if (!-w $initd) {
+ if ( !-w $initd ) {
print "You must run the 'install' command as root.\n";
return;
}
- if (-e $dest_file) {
+ if ( -e $dest_file ) {
print "$initscript already in $initd.\n";
}
else {
- copy($source_file, $dest_file)
+ copy( $source_file, $dest_file )
or die "Could not copy $source_file to $dest_file: $!";
- chmod(0755, $dest_file)
+ chmod 0755, $dest_file
or die "Could not change permissions on $dest_file: $!";
}
- system($chkconfig, '--add', $initscript);
- print "$initscript installed.",
- " To start the daemon, do \"$dest_file start\" as root.\n";
+ system $chkconfig, '--add', $initscript;
+ print "$initscript installed.", " To start the daemon, do \"$dest_file start\" as root.\n";
- if (-d $sysconfig and -w $sysconfig) {
- if (-e $config_file) {
+ if ( -d $sysconfig and -w $sysconfig ) {
+ if ( -e $config_file ) {
print "$config_file already exists.\n";
return;
}
- open(my $config_fh, ">", $config_file)
- or die "Could not write to $config_file: $!";
- my $directory = abs_path(dirname($self->{_original_zero}));
- my $owner_id = (stat $self->{_original_zero})[4];
- my $owner = getpwuid($owner_id);
- print $config_fh <<END;
+ open my $config_fh, '>', $config_file;
+ my $directory = abs_path( dirname( $self->{_original_program_name} ) );
+ my $owner_id = ( stat $self->{_original_program_name} )[4];
+ my $owner = getpwuid $owner_id;
+ print $config_fh <<"END";
#!/bin/sh
BUGZILLA="$directory"
USER=$owner
END
- close($config_fh);
+ close $config_fh;
}
else {
print "Please edit $dest_file to configure the daemon.\n";
}
- }
+ }
}
sub gd_can_uninstall {
my $self = shift;
- if (-x $chkconfig and -d $initd) {
+ if ( -x $chkconfig and -d $initd ) {
return sub {
- if (!-e "$initd/$initscript") {
+ if ( !-e "$initd/$initscript" ) {
print "$initscript not installed.\n";
return;
}
- system($chkconfig, '--del', $initscript);
- print "$initscript disabled.",
- " To stop it, run: $initd/$initscript stop\n";
- }
+ system $chkconfig, '--del', $initscript;
+ print "$initscript disabled.", " To stop it, run: $initd/$initscript stop\n";
+ }
}
return $self->SUPER::gd_can_install(@_);
@@ -164,49 +178,76 @@ sub gd_check {
my $self = shift;
# Get a count of all the jobs currently in the queue.
- my $jq = Bugzilla->job_queue();
- my @dbs = $jq->bz_databases();
+ my $jq = Bugzilla->job_queue();
+ my @dbs = $jq->bz_databases();
my $count = 0;
foreach my $driver (@dbs) {
- $count += $driver->select_one('SELECT COUNT(*) FROM ts_job', []);
+ $count += $driver->select_one( 'SELECT COUNT(*) FROM ts_job', [] );
}
- print get_text('job_queue_depth', { count => $count }) . "\n";
+ print get_text( 'job_queue_depth', { count => $count } ) . "\n";
}
+# override this to use IO::Async.
sub gd_setup_signals {
- my $self = shift;
- $self->SUPER::gd_setup_signals();
- $SIG{TERM} = sub { $self->gd_quit_event(); }
+ my $self = shift;
+ my @signals = qw( INT HUP TERM );
+ $self->{_signal_future} = Future->wait_any( map { catch_signal( $_, $_ ) } @signals );
}
sub gd_other_cmd {
my ($self) = shift;
- if ($ARGV[0] eq "once") {
- $self->_do_work("work_once");
-
- exit(0);
+ if ( $ARGV[0] eq 'once' ) {
+ Bugzilla::JobQueue::Worker->run('work_once');
+ exit;
}
-
+
$self->SUPER::gd_other_cmd();
}
-sub gd_run {
- my $self = shift;
+sub gd_quit_event { FATAL('gd_quit_event() should never be called') }
+sub gd_reconfig_event { FATAL('gd_reconfig_event() should never be called') }
- $self->_do_work("work");
+sub gd_run {
+ my $self = shift;
+ my $jobs = $self->{gd_args}{jobs} // 1;
+ my $signal_f = $self->{_signal_future};
+ my $workers_f = fmap_void { $self->run_worker() }
+ concurrent => $jobs,
+ generate => sub { !$signal_f->is_ready };
+
+ # This is so the process shows up in (h)top in a useful way.
+ local $PROGRAM_NAME = "$self->{gd_progname} [supervisor]";
+ Future->wait_any($signal_f, $workers_f)->get;
+ unlink $self->{gd_pidfile};
+ exit 0;
}
-sub _do_work {
- my ($self, $fn) = @_;
+# This executes the script "jobqueue-worker.pl"
+# $EXECUTABLE_NAME is the name of the perl interpreter.
+sub run_worker {
+ my ( $self ) = @_;
- my $jq = Bugzilla->job_queue();
- $jq->set_verbose($self->{debug});
- foreach my $module (values %{ Bugzilla::JobQueue->job_map() }) {
- eval "use $module";
- $jq->can_do($module);
+ my $script = catfile( bz_locations->{cgi_path}, 'jobqueue-worker.pl' );
+ my @command = ( $EXECUTABLE_NAME, $script);
+ if ( $self->{gd_args}{progname} ) {
+ push @command, '--name' => "$self->{gd_args}{progname} [worker]";
}
- $jq->$fn;
+ my $loop = IO::Async::Loop->new;
+ my $exit_f = $loop->new_future;
+ my $worker = IO::Async::Process->new(
+ command => \@command,
+ on_finish => on_finish($exit_f),
+ on_exception => on_exception( 'jobqueue worker', $exit_f )
+ );
+ $exit_f->on_cancel(
+ sub {
+ DEBUG('terminate worker');
+ $worker->kill('TERM');
+ }
+ );
+ $loop->add($worker);
+ return $exit_f;
}
1;
diff --git a/Bugzilla/JobQueue/Worker.pm b/Bugzilla/JobQueue/Worker.pm
new file mode 100644
index 000000000..db8ebe35e
--- /dev/null
+++ b/Bugzilla/JobQueue/Worker.pm
@@ -0,0 +1,30 @@
+# 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.
+
+package Bugzilla::JobQueue::Worker;
+use 5.10.1;
+use strict;
+use warnings;
+
+use Bugzilla::Logging;
+use Module::Runtime qw(require_module);
+
+sub run {
+ my ( $class, $fn ) = @_;
+ DEBUG("Starting up for $fn");
+ my $jq = Bugzilla->job_queue();
+
+ DEBUG('Loading jobqueue modules');
+ foreach my $module ( values %{ Bugzilla::JobQueue->job_map() } ) {
+ DEBUG("JobQueue can do $module");
+ require_module($module);
+ $jq->can_do($module);
+ }
+ $jq->$fn;
+}
+
+1;
diff --git a/Bugzilla/Mailer.pm b/Bugzilla/Mailer.pm
index 6e46d1862..1dec3d4ff 100644
--- a/Bugzilla/Mailer.pm
+++ b/Bugzilla/Mailer.pm
@@ -37,10 +37,10 @@ use Bugzilla::Version qw(vers_cmp);
sub MessageToMTA {
my ($msg, $send_now) = (@_);
- my $method = Bugzilla->params->{'mail_delivery_method'};
+ my $method = Bugzilla->get_param_with_override('mail_delivery_method');
return if $method eq 'None';
- if (Bugzilla->params->{'use_mailer_queue'} and !$send_now) {
+ if (Bugzilla->get_param_with_override('use_mailer_queue') and !$send_now) {
Bugzilla->job_queue->insert('send_mail', { msg => $msg });
return;
}
@@ -66,7 +66,7 @@ sub MessageToMTA {
}
# Ensure that we are not sending emails too quickly to recipients.
- if (Bugzilla->params->{use_mailer_queue}
+ if (Bugzilla->get_param_with_override('use_mailer_queue')
&& (EMAIL_LIMIT_PER_MINUTE || EMAIL_LIMIT_PER_HOUR))
{
$dbh->do(
@@ -226,7 +226,7 @@ sub MessageToMTA {
}
# insert into email_rates
- if (Bugzilla->params->{use_mailer_queue}
+ if (Bugzilla->get_param_with_override('use_mailer_queue')
&& (EMAIL_LIMIT_PER_MINUTE || EMAIL_LIMIT_PER_HOUR))
{
$dbh->do(
@@ -252,15 +252,14 @@ sub build_thread_marker {
$sitespec = "-$2$sitespec"; # Put the port number back in, before the '@'
}
- my $threadingmarker;
+ my $threadingmarker = "References: <bug-$bug_id-$user_id$sitespec>";
if ($is_new) {
- $threadingmarker = "Message-ID: <bug-$bug_id-$user_id$sitespec>";
+ $threadingmarker .= "\nMessage-ID: <bug-$bug_id-$user_id$sitespec>";
}
else {
my $rand_bits = generate_random_password(10);
- $threadingmarker = "Message-ID: <bug-$bug_id-$user_id-$rand_bits$sitespec>" .
- "\nIn-Reply-To: <bug-$bug_id-$user_id$sitespec>" .
- "\nReferences: <bug-$bug_id-$user_id$sitespec>";
+ $threadingmarker .= "\nMessage-ID: <bug-$bug_id-$user_id-$rand_bits$sitespec>" .
+ "\nIn-Reply-To: <bug-$bug_id-$user_id$sitespec>";
}
return $threadingmarker;
diff --git a/Bugzilla/Memcached.pm b/Bugzilla/Memcached.pm
index 0ceed97c0..d34aaa595 100644
--- a/Bugzilla/Memcached.pm
+++ b/Bugzilla/Memcached.pm
@@ -16,7 +16,7 @@ use Log::Log4perl qw(:easy);
use Bugzilla::Error;
use Scalar::Util qw(blessed);
use List::Util qw(sum);
-use Bugzilla::Util qw(trick_taint);
+use Bugzilla::Util qw(trick_taint trim);
use URI::Escape;
use Encode;
use Sys::Syslog qw(:DEFAULT);
@@ -36,11 +36,25 @@ sub _new {
if (Bugzilla->feature('memcached') && $servers) {
$self->{namespace} = Bugzilla->localconfig->{memcached_namespace};
TRACE("connecting servers: $servers, namespace: $self->{namespace}");
- $self->{memcached} = Cache::Memcached::Fast->new({
- servers => [ split(/[, ]+/, $servers) ],
- namespace => $self->{namespace},
- max_size => 1024 * 1024 * 4,
- });
+ $self->{memcached} = Cache::Memcached::Fast->new(
+ {
+ servers => [ _parse_memcached_server_list($servers) ],
+ namespace => $self->{namespace},
+ max_size => 1024 * 1024 * 4,
+ max_failures => 1,
+ failure_timeout => 60,
+ io_timeout => 0.2,
+ connect_timeout => 0.2,
+ }
+ );
+ my $versions = $self->{memcached}->server_versions;
+ if (keys %$versions) {
+ # this is needed to ensure forked processes don't start out with a connected memcached socket.
+ $self->{memcached}->disconnect_all;
+ }
+ else {
+ WARN("No memcached servers");
+ }
}
else {
TRACE("memcached feature is not enabled");
@@ -48,6 +62,13 @@ sub _new {
return bless($self, $class);
}
+sub _parse_memcached_server_list {
+ my ($server_list) = @_;
+ my @servers = split(/[, ]+/, trim($server_list));
+
+ return map { /:[0-9]+$/s ? $_ : "$_:11211" } @servers;
+}
+
sub enabled {
return $_[0]->{memcached} ? 1 : 0;
}
@@ -206,6 +227,8 @@ sub should_rate_limit {
my $prefix = RATE_LIMIT_PREFIX . $name . ':';
my $memcached = $self->{memcached};
+ return 0 unless $memcached;
+
$tries //= 3;
for (0 .. $tries) {
@@ -272,7 +295,7 @@ sub _inc_prefix {
delete Bugzilla->request_cache->{"memcached_prefix_$name"};
# BMO - log that we've wiped the cache
- INFO("$name cache cleared");
+ TRACE("$name cache cleared");
}
sub _global_prefix {
@@ -315,7 +338,7 @@ sub _get {
my $enc_key = $self->_encode_key($key)
or return;
- my $val = $self->{memcached}->get($key);
+ my $val = $self->{memcached}->get($enc_key);
TRACE("get $enc_key: " . (defined $val ? "HIT" : "MISS"));
return $val;
}
diff --git a/Bugzilla/ModPerl.pm b/Bugzilla/ModPerl.pm
index a5c840897..120dd8210 100644
--- a/Bugzilla/ModPerl.pm
+++ b/Bugzilla/ModPerl.pm
@@ -20,6 +20,7 @@ use Carp ();
use Template ();
use Bugzilla::ModPerl::BlockIP;
+use Bugzilla::ModPerl::Hostage;
sub apache_config {
my ($class, $cgi_path) = @_;
@@ -74,6 +75,7 @@ __DATA__
# the built-in rand(), even though we never use it in Bugzilla itself,
# so we need to srand() both of them.)
PerlChildInitHandler "sub { Bugzilla::RNG::srand(); srand(); }"
+PerlInitHandler Bugzilla::ModPerl::Hostage
PerlAccessHandler Bugzilla::ModPerl::BlockIP
# It is important to specify ErrorDocuments outside of all directories.
@@ -84,6 +86,12 @@ ErrorDocument 403 /errors/403.html
ErrorDocument 404 /errors/404.html
ErrorDocument 500 /errors/500.html
+<Location /helper>
+ SetHandler perl-script
+ PerlResponseHandler Plack::Handler::Apache2
+ PerlSetVar psgi_app [% cgi_path %]/helper.psgi
+</Location>
+
<Directory "[% cgi_path %]">
AddHandler perl-script .cgi
# No need to PerlModule these because they're already defined in mod_perl.pl
diff --git a/Bugzilla/ModPerl/BasicAuth.pm b/Bugzilla/ModPerl/BasicAuth.pm
index e93680e9d..7248a19f3 100644
--- a/Bugzilla/ModPerl/BasicAuth.pm
+++ b/Bugzilla/ModPerl/BasicAuth.pm
@@ -25,18 +25,22 @@ use warnings;
# AUTH_VAR_NAME and AUTH_VAR_PASS are the names of variables defined in
# `localconfig` which hold the authentication credentials.
-use Apache2::Const -compile => qw(OK HTTP_UNAUTHORIZED);
+use Apache2::Const -compile => qw(OK HTTP_UNAUTHORIZED); ## no critic (Freenode::ModPerl)
+use Bugzilla::Logging;
use Bugzilla ();
sub handler {
my $r = shift;
my ($status, $password) = $r->get_basic_auth_pw;
- return $status if $status != Apache2::Const::OK;
+ if ($status != Apache2::Const::OK) {
+ WARN("Got non-OK status: $status when trying to get password");
+ return $status
+ }
my $auth_var_name = $ENV{AUTH_VAR_NAME};
my $auth_var_pass = $ENV{AUTH_VAR_PASS};
unless ($auth_var_name && $auth_var_pass) {
- warn "AUTH_VAR_NAME and AUTH_VAR_PASS environmental vars not set\n";
+ ERROR('AUTH_VAR_NAME and AUTH_VAR_PASS environmental vars not set');
$r->note_basic_auth_failure;
return Apache2::Const::HTTP_UNAUTHORIZED;
}
@@ -44,13 +48,14 @@ sub handler {
my $auth_user = Bugzilla->localconfig->{$auth_var_name};
my $auth_pass = Bugzilla->localconfig->{$auth_var_pass};
unless ($auth_user && $auth_pass) {
- warn "$auth_var_name and $auth_var_pass not configured\n";
+ ERROR("$auth_var_name and $auth_var_pass not configured");
$r->note_basic_auth_failure;
return Apache2::Const::HTTP_UNAUTHORIZED;
}
unless ($r->user eq $auth_user && $password eq $auth_pass) {
$r->note_basic_auth_failure;
+ WARN('username and password do not match');
return Apache2::Const::HTTP_UNAUTHORIZED;
}
diff --git a/Bugzilla/ModPerl/Hostage.pm b/Bugzilla/ModPerl/Hostage.pm
new file mode 100644
index 000000000..a3bdfac58
--- /dev/null
+++ b/Bugzilla/ModPerl/Hostage.pm
@@ -0,0 +1,71 @@
+package Bugzilla::ModPerl::Hostage;
+use 5.10.1;
+use strict;
+use warnings;
+
+use Apache2::Const qw(:common); ## no critic (Freenode::ModPerl)
+
+sub _attachment_root {
+ my ($base) = @_;
+ return undef unless $base;
+ return $base =~ m{^https?://(?:bug)?\%bugid\%\.([a-zA-Z\.-]+)}
+ ? $1
+ : undef;
+}
+
+sub _attachment_host_regex {
+ my ($base) = @_;
+ return undef unless $base;
+ my $val = $base;
+ $val =~ s{^https?://}{}s;
+ $val =~ s{/$}{}s;
+ my $regex = quotemeta $val;
+ $regex =~ s/\\\%bugid\\\%/\\d+/g;
+ return qr/^$regex$/s;
+}
+
+sub handler {
+ my $r = shift;
+ state $urlbase = Bugzilla->localconfig->{urlbase};
+ state $urlbase_uri = URI->new($urlbase);
+ state $urlbase_host = $urlbase_uri->host;
+ state $urlbase_host_regex = qr/^bug(\d+)\.\Q$urlbase_host\E$/;
+ state $attachment_base = Bugzilla->localconfig->{attachment_base};
+ state $attachment_root = _attachment_root($attachment_base);
+ state $attachment_host_regex = _attachment_host_regex($attachment_base);
+
+ my $hostname = $r->hostname;
+ return OK if $hostname eq $urlbase_host;
+
+ my $path = $r->uri;
+ return OK if $path eq '/__lbheartbeat__';
+
+ if ($attachment_base && $hostname eq $attachment_root) {
+ $r->headers_out->set(Location => $urlbase);
+ return REDIRECT;
+ }
+ elsif ($attachment_base && $hostname =~ $attachment_host_regex) {
+ if ($path =~ m{^/attachment\.cgi}s) {
+ return OK;
+ } else {
+ my $new_uri = URI->new($r->unparsed_uri);
+ $new_uri->scheme($urlbase_uri->scheme);
+ $new_uri->host($urlbase_host);
+ $r->headers_out->set(Location => $new_uri);
+ return REDIRECT;
+ }
+ }
+ elsif (my ($id) = $hostname =~ $urlbase_host_regex) {
+ my $new_uri = $urlbase_uri->clone;
+ $new_uri->path('/show_bug.cgi');
+ $new_uri->query_form(id => $id);
+ $r->headers_out->set(Location => $new_uri);
+ return REDIRECT;
+ }
+ else {
+ $r->headers_out->set(Location => $urlbase);
+ return REDIRECT;
+ }
+}
+
+1; \ No newline at end of file
diff --git a/Bugzilla/Send/Sendmail.pm b/Bugzilla/Send/Sendmail.pm
index 71c1f67ce..81c2190e5 100644
--- a/Bugzilla/Send/Sendmail.pm
+++ b/Bugzilla/Send/Sendmail.pm
@@ -37,7 +37,7 @@ sub send {
unless (close $pipe) {
return failure "error when closing pipe to $mailer: $!" if $!;
my ($error_message, $is_transient) = _map_exitcode($? >> 8);
- if (Bugzilla->params->{'use_mailer_queue'}) {
+ if (Bugzilla->get_param_with_override('use_mailer_queue')) {
# Return success for errors which are fatal so Bugzilla knows to
# remove them from the queue
if ($is_transient) {
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index 2ec813303..3ace60cf8 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -284,7 +284,8 @@ sub get_attachment_link {
$link_text =~ s/ \[details\]$//;
$link_text =~ s/ \[diff\]$//;
- my $linkval = "attachment.cgi?id=$attachid";
+ state $urlbase = Bugzilla->localconfig->{urlbase};
+ my $linkval = "${urlbase}attachment.cgi?id=$attachid";
# If the attachment is a patch and patch_viewer feature is
# enabled, add link to the diff.
@@ -572,7 +573,9 @@ sub create {
ABSOLUTE => 1,
RELATIVE => 1,
- COMPILE_DIR => bz_locations()->{'template_cache'},
+ # Only use an on-disk template cache if we're running as the web
+ # server. This ensures the permissions of the cache remain correct.
+ COMPILE_DIR => is_webserver_group() ? bz_locations()->{'template_cache'} : undef,
# Don't check for a template update until 1 hour has passed since the
# last check.
@@ -1071,6 +1074,8 @@ our %_templates_to_precompile;
sub precompile_templates {
my ($output) = @_;
+ return unless is_webserver_group();
+
# Remove the compiled templates.
my $cache_dir = bz_locations()->{'template_cache'};
my $datadir = bz_locations()->{'datadir'};
diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm
index 7d85a4dfd..a1316c7ef 100644
--- a/Bugzilla/Util.pm
+++ b/Bugzilla/Util.pm
@@ -17,7 +17,8 @@ use base qw(Exporter);
with_writable_database with_readonly_database
html_quote url_quote xml_quote
css_class_quote html_light_quote
- i_am_cgi i_am_webservice correct_urlbase remote_ip
+ i_am_cgi i_am_webservice is_webserver_group
+ correct_urlbase remote_ip
validate_ip do_ssl_redirect_if_required use_attachbase
diff_arrays on_main_db css_url_rewrite
trim wrap_hard wrap_comment find_wrap_point
@@ -32,19 +33,20 @@ use base qw(Exporter);
use Bugzilla::Constants;
use Bugzilla::RNG qw(irand);
-use Date::Parse;
use Date::Format;
-use DateTime;
+use Date::Parse;
use DateTime::TimeZone;
+use DateTime;
use Digest;
use Email::Address;
-use List::MoreUtils qw(none);
-use Scalar::Util qw(tainted blessed);
-use Text::Wrap;
use Encode qw(encode decode resolve_alias);
use Encode::Guess;
+use English qw(-no_match_vars $EGID);
+use List::MoreUtils qw(any none);
use POSIX qw(floor ceil);
+use Scalar::Util qw(tainted blessed);
use Taint::Util qw(untaint);
+use Text::Wrap;
use Try::Tiny;
sub with_writable_database(&) {
@@ -280,6 +282,30 @@ sub i_am_webservice {
|| $usage_mode == USAGE_MODE_REST;
}
+sub is_webserver_group {
+ my @effective_gids = split(/ /, $EGID);
+
+ state $web_server_gid;
+ if (!defined $web_server_gid) {
+ my $web_server_group = Bugzilla->localconfig->{webservergroup};
+
+ if ($web_server_group eq '' || ON_WINDOWS) {
+ $web_server_gid = $effective_gids[0];
+ }
+
+ elsif ($web_server_group =~ /^\d+$/) {
+ $web_server_gid = $web_server_group;
+ }
+
+ else {
+ $web_server_gid = eval { getgrnam($web_server_group) };
+ $web_server_gid //= 0;
+ }
+ }
+
+ return any { $web_server_gid == $_ } @effective_gids;
+}
+
# This exists as a separate function from Bugzilla::CGI::redirect_to_https
# because we don't want to create a CGI object during XML-RPC calls
# (doing so can mess up XML-RPC).
@@ -1071,6 +1097,11 @@ in a command-line script.
Tells you whether or not the current usage mode is WebServices related
such as JSONRPC or XMLRPC.
+=item C<is_webserver_group()>
+
+Tells you whether or not the current process's group matches that
+configured as webservergroup.
+
=item C<remote_ip()>
Returns the IP address of the remote client. If Bugzilla is behind
diff --git a/Bugzilla/WebService/Server/REST.pm b/Bugzilla/WebService/Server/REST.pm
index 6fb86fdd4..b8884b753 100644
--- a/Bugzilla/WebService/Server/REST.pm
+++ b/Bugzilla/WebService/Server/REST.pm
@@ -132,7 +132,8 @@ sub response {
if (exists $json_data->{error}) {
$result = $json_data->{error};
$result->{error} = $self->type('boolean', 1);
- $result->{documentation} = REST_DOC;
+
+ $result->{documentation} = Bugzilla->params->{docs_urlbase} . "api/";
delete $result->{'name'}; # Remove JSONRPCError
}
elsif (exists $json_data->{result}) {
diff --git a/Makefile.PL b/Makefile.PL
index 8e20d52b9..2c7b8fad7 100755
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -52,6 +52,7 @@ my %requires = (
'File::Slurp' => '9999.13',
'Future' => '0.34',
'HTML::Escape' => '1.10',
+ 'IPC::System::Simple' => 0,
'IO::Async' => '0.71',
'JSON::MaybeXS' => '1.003008',
'JSON::XS' => '2.01',
@@ -273,6 +274,7 @@ my %optional_features = (
requires => {
'mod_perl2' => '1.999022',
'Apache2::SizeLimit' => '0.96',
+ 'Plack::Handler::Apache2' => 0,
}
}
}
diff --git a/README.rst b/README.rst
index a2b23d069..64fae335b 100644
--- a/README.rst
+++ b/README.rst
@@ -19,6 +19,8 @@ BMO is Mozilla's highly customized version of Bugzilla.
3.1 Container Arguments
3.2 Environmental Variables
3.3 Persistent Data Volume
+ 4. Development Tips
+ 4.1 Testing Emails
If you want to contribute to BMO, you can fork this repo and get a local copy
of BMO running in a few minutes using Vagrant or Docker.
@@ -297,6 +299,15 @@ BMO_apache_size_limit
This is the max amount of unshared memory (in kb) that the apache process is
allowed to use before Apache::SizeLimit kills it.
+BMO_mail_delivery_method
+ Usually configured on the MTA section of admin interface, but may be set here for testing purposes.
+ Valid values are None, Test, Sendmail, or SMTP.
+ If set to Test, email will be appended to the /app/data/mailer.testfile.
+
+BMO_use_mailer_queue
+ Usually configured on the MTA section of the admin interface, you may change this here for testing purposes.
+ Should be 1 or 0. If 1, the job queue will be used. For testing, only set to 0 if the BMO_mail_delivery_method is None or Test.
+
HTTPD_StartServers
Sets the number of child server processes created on startup.
As the number of processes is dynamically controlled depending on the load,
@@ -356,3 +367,48 @@ Persistent Data Volume
This container expects /app/data to be a persistent, shared, writable directory
owned by uid 10001. This must be a shared (NFS/EFS/etc) volume between all
nodes.
+
+Development Tips
+================
+
+Testing Emails
+--------------
+
+With vagrant have two options to test emails sent by a local bugzilla instance. You can configure
+which setting you want to use by going to http://bmo-web.vm/editparams.cgi?section=mta and
+changing the mail_delivery_method to either 'Test' or 'Sendmail'. Afterwards restart bmo with
+``vagrant reload``. With docker, only the default 'Test' option is supported.
+
+'Test' option (Default for Docker)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+With this option, all mail will be appended to a ``mailer.testfile``.
+
+- Using docker, run ``docker-compose run bmo-web.vm cat /app/data/mailer.testfile``.
+- Using vagrant, run ``vagrant ssh web`` and then naviage to ``/vagrant/data/mailer.testfile``.
+
+'Sendmail' option (Default for Vagrant)
+~~~~~~~~~~~~~~~~~
+
+This option is useful if you want to preview email using a real mail client.
+An imap server is running on bmo-web.vm on port 143 and you can connect to it with
+the following settings:
+
+- host: bmo-web.vm
+- port: 143
+- encryption: No SSL, Plaintext password
+- username: vagrant
+- password: anything
+
+All email that bmo sends will go to the vagrant user, so there is no need to login with
+multiple imap accounts.
+
+`Thunderbird's`_ wizard to add a new "Existing Mail Account" doesn't work with bmo-web. It
+fails because it wants to create a mail account with both incoming mail (IMAP) and outgoing
+mail (SMTP, which bmo-web.vm doesn't provide). To work around this, using a regular email
+account to first setup, then modify the settings of that account: Right Click the account in
+the left side bar > Settings > Server Settings. Update the server settings to match those
+listed above. Afterwards, you may update the account name to be vagrant@bmo-web.vm. Thunderbird
+will now pull email from bmo. You can try it out by commenting on a bug.
+
+.. _`Thunderbird's`: https://www.mozilla.org/en-US/thunderbird/
diff --git a/checksetup.pl b/checksetup.pl
index 6cea8549d..d3f08e024 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -30,6 +30,7 @@ use Pod::Usage;
# Bug 1270550 - Tie::Hash::NamedCapture must be loaded before Safe.
use Tie::Hash::NamedCapture;
use Safe;
+use English qw(-no_match_vars $EUID $EGID);
use Bugzilla::Constants;
use Bugzilla::Install::Requirements;
@@ -155,6 +156,16 @@ unless ($ENV{LOCALCONFIG_ENV}) {
}
my $lc_hash = Bugzilla->localconfig;
+if ( $EUID == 0 && $lc_hash->{webservergroup} && !ON_WINDOWS ) {
+ # So checksetup was run as root, and we have a webserver group set.
+ # Let's assume the user wants us to make files that are writable
+ # by the webserver group.
+
+ $EGID = getgrnam $lc_hash->{webservergroup}; ## no critic (Variables::RequireLocalizedPunctuationVars)
+ umask 002
+ or die "failed to set umask 002: $!";
+}
+
unless ($switch{'no-database'}) {
die "urlbase is not set\n" unless $lc_hash->{urlbase};
die "urlbase must end with slash\n" unless $lc_hash->{urlbase} =~ m{/$}ms;
diff --git a/conf/httpd.conf b/conf/httpd.conf
index 20a21f5db..7fe859b59 100644
--- a/conf/httpd.conf
+++ b/conf/httpd.conf
@@ -4,7 +4,8 @@ ServerRoot "/etc/httpd"
ServerAdmin root@localhost
PidFile /tmp/httpd.pid
-Timeout 60
+Timeout 120
+LimitRequestLine 35000
KeepAlive Off
MaxKeepAliveRequests 100
KeepAliveTimeout 15
@@ -91,7 +92,7 @@ DocumentRoot "/app"
Alias "/bmo" "/app"
</IfDefine>
<IfDefine HTTPS>
- SetEnv HTTPS on
+ SetEnvIf X-Forwarded-Proto "https" HTTPS=on
</IfDefine>
<Directory "/app">
Options -Indexes -FollowSymLinks
diff --git a/docker-compose.yml b/docker-compose.yml
index e04e9c712..5dfb6fad4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -33,6 +33,20 @@ services:
- bmo-db.vm
- memcached
+ bmo-jobqueue.vm:
+ build: *bmo_build
+ command: jobqueue
+ volumes:
+ - bmo-data-dir:/app/data
+ tmpfs:
+ - /tmp
+ - /run
+ environment: *bmo_env
+ restart: always
+ depends_on:
+ - bmo-db.vm
+ - memcached
+
bmo-db.vm:
image: mozillabteam/bmo-mysql:5.6
volumes:
diff --git a/extensions/BMO/template/en/default/bug/create/comment-ipc.txt.tmpl b/extensions/BMO/template/en/default/bug/create/comment-ipc.txt.tmpl
deleted file mode 100644
index b644ee469..000000000
--- a/extensions/BMO/template/en/default/bug/create/comment-ipc.txt.tmpl
+++ /dev/null
@@ -1,47 +0,0 @@
-[%# 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 Bugzilla %]
-[% cgi = Bugzilla.cgi %]
-
->> What team are you are a part of?
-[%+ cgi.param("team_name") %]
-
->> Will your snippet need to appear in a language other than English?
-[%+ cgi.param("language").join(', ') %]
-
->> Does your snippet need to be targeted to a particular country?
-[%+ cgi.param("target_country") %]
-
->> Insert your tagged link here.
-[%+ cgi.param("tagged_link") %]
-
-[% IF cgi.param('start_run') %]
->> When would you like your snippet to start its run?
-[%+ cgi.param("start_run") %]
-
-[% END %]
-[% IF cgi.param('complete_run') %]
->> When would you like your snippet to complete its run?
-[%+ cgi.param("complete_run") %]
-
-[% END %]
->> Will you be able to provide the following assets for your snippet?
-[%+ cgi.param("snippet_assets").join(', ') %]
-
->> Your name?
-[%+ cgi.param("name") %]
-
->> E-mail?
-[%+ cgi.param("email") %]
-
->> Anything else you would like to add?
-[%+ cgi.param("else") %]
-
->> Who do you think is coolest?
-[%+ cgi.param("bonus") %]
diff --git a/extensions/BMO/template/en/default/bug/create/create-ipc.html.tmpl b/extensions/BMO/template/en/default/bug/create/create-ipc.html.tmpl
deleted file mode 100644
index e32ba62dc..000000000
--- a/extensions/BMO/template/en/default/bug/create/create-ipc.html.tmpl
+++ /dev/null
@@ -1,259 +0,0 @@
-[%# 1.0@bugzilla.org %]
-[%# 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 Mozilla Corporation.
- # Portions created by Mozilla are Copyright (C) 2008 Mozilla
- # Corporation. All Rights Reserved.
- #
- # Contributor(s): Sebastin Santy <sebastinssanty@gmail.com>
- #%]
-
-[% PROCESS global/variables.none.tmpl %]
-
-[% inline_style = BLOCK %]
-#heading {
- width: 98%;
- font-size: 2em;
- font-weight: bold;
- margin: 12px;
-}
-.required:before {
- content: "* ";
- color: red;
-}
-.required_star {
- color: red;
-}
-p {
- max-width: 40%;
-}
-.row {
- padding-bottom: 10px;
-}
-[% END %]
-
-[% inline_javascript = BLOCK %]
-$(document).ready(function() {
- $('.date_field').datetimepicker({
- format: 'Y-m-d',
- datepicker: true,
- timepicker: false,
- scrollInput: false,
- lazyInit: false,
- closeOnDateSelect: true
- });
- $('#langothers').on('focus', function(){
- document.getElementById("langothersradio").checked = true;
- });
- $('#assetothers').on('focus', function(){
- document.getElementById("assetothersradio").checked = true;
- });
- $('.date_field-img')
- .click(function(event) {
- var id = $(event.target).attr('id').replace(/-img$/, '');
- $('#' + id).datetimepicker('show');
- });
- $('#ipcForm').submit(function() {
- $('#short_desc').val($('#short_desc').val() + ': ' + $('#team_name').val());
- });
- $('#new_email_request').on('change', function() {
- if ($('#new_email_request').val() == 'Yes') {
- $('#email_cadence_row').show();
- $('input[name="email_cadence"]').each(function() {
- $(this).attr('required', true);
- });
- }
- else {
- $('#email_cadence_row').hide();
- $('input[name="email_cadence"]').each(function() {
- $(this).removeProp('required');
- });
- }
- });
-});
-[% END %]
-
-[% PROCESS global/header.html.tmpl
- title = "IPC Request"
- generate_api_token = 1
- style_urls = [ "skins/standard/attachment.css",
- "js/jquery/plugins/datetimepicker/datetimepicker.css" ]
- style = inline_style
- javascript = inline_javascript
- javascript_urls = [ "js/field.js", "js/util.js" ]
- jquery = [ "datetimepicker" ]
-%]
-
-<div id="heading">IPC Requests</div>
-<div>
- <p>You’ve decided to use the snippet channel to drive attention to one of your projects, awesome! The snippet channel drives hundreds of millions of impressions a month. It’s great tool to support your marketing efforts. Complete this form and we will follow up with you soon (maybe today, but definitely within 3 days).
- </p>
- <p>
- Tell us more:
- </p>
-
- <form method="post" action="post_bug.cgi" id="ipcForm">
- <input type="hidden" id="short_desc" name="short_desc" value="New IPC Request">
- <input type="hidden" name="product" value="Marketing">
- <input type="hidden" name="component" value="IPC">
- <input type="hidden" name="rep_platform" value="All">
- <input type="hidden" name="op_sys" value="Other">
- <input type="hidden" name="version" value="unspecified">
- <input type="hidden" name="priority" value="--">
- <input type="hidden" name="bug_severity" value="normal">
- <input type="hidden" name="format" value="ipc">
- <input type="hidden" name="token" value="[% token FILTER html %]">
- [% IF user.in_group('canconfirm') %]
- <input type="hidden" name="bug_status" value="NEW">
- [% END %]
-
- <div class="row">
- <div class="row_desc">
- <label class="required" for="team_name">
- <strong>What team are you a part of?</strong>
- </label>
- </div>
- <input required type="text" name="team_name" id="team_name" size="80">
- </div>
-
- <div class="row">
- <div class="row_desc">
- <label for="cf_user_story">
- <strong>What is the user story associated with your snippet?</strong>
- </label>
- </div>
- <textarea name="cf_user_story" id="cf_user_story" cols="80" rows="10"></textarea>
- </div>
-
- <div class="row">
- <div class="row_desc">
- <strong>Will your snippet need to appear in a language other than English? If so, please specify.</strong>
- </div>
- <input type="checkbox" name="language" id="spanish" value="Spanish">
- <label for="spanish">Spanish</label><br>
- <input type="checkbox" name="language" id="italian" value="Italian">
- <label for="italian">Italian</label><br>
- <input type="checkbox" name="language" id="german" value="German">
- <label for="german">German</label><br>
- <input type="checkbox" name="language" id="french" value="French">
- <label for="french">French</label><br>
- <input type="checkbox" name="language" id="russian" value="Russian">
- <label for="russian">Russian</label><br>
- <input type="checkbox" name="language" id="portuguese" value="Portuguese">
- <label for="portuguese">Portuguese</label><br>
- <input type="checkbox" name="language" id="langothersradio">
- <label for="langothers">Others: </label>
- <input type="text" name="language" id="langothers" size="30">
- </div>
-
- <div class="row">
- <div class="row_desc">
- <label for="target_country">
- <strong>Does your snippet need to be targeted to a particular country? If so, please specify.</strong>
- </label>
- </div>
- <input type="text" name="target_country" id="target_country" size="80">
- </div>
-
- <p>
- If you would like your snippet to include a link, please tag it by using the URL builder <a href="https://bit.ly/1JOgDr1">(https://bit.ly/1JOgDr1)</a> and following the steps below:
- </p>
- <img src="extensions/BMO/web/images/ipc_form_buildscreen.png" style = "width:50%"><br><br>
- <div class="row">
- <div class="row_desc">
- <label for="tagged_link">
- <strong>Insert your tagged link here.</strong>
- </label>
- </div>
- <input type="text" name="tagged_link" id="tagged_link" size="80">
- </div>
-
- <p>
- Please reference the Mozilla In-Product Messaging Calendar for available dates. <a href="https://bit.ly/2aZ3w9c"> https://bit.ly/2aZ3w9c</a>
- </p>
-
- <div class="row">
- <div class="row_desc">
- <label for="start_run">
- <strong>When would you like your snippet to start its run?</strong>
- </label>
- </div>
- <input class="date_field" name="start_run" id="start_run">
- <img class="date_field-img" id="start_run-img" src="extensions/BugModal/web/calendar.png" width="16" height="16">
- </div>
-
- <div class="row">
- <div class="row_desc">
- <label for="complete_run">
- <strong>When would you like your snippet to complete its run?</strong>
- </label>
- </div>
- <input class="date_field" name="complete_run" id="complete_run">
- <img class="date_field-img" id="complete_run-img" src="extensions/BugModal/web/calendar.png" width="16" height="16">
- </div>
-
- <div class="row">
- <div class="row_desc">
- <strong>Will you be able to provide the following assets for your snippet? Check all that apply.</strong><br>
- Feel free to look at our existing icon library:
- <a href="https://bit.ly/2efXaFo">https://bit.ly/2efXaFo</a>
- </div>
- <input type="checkbox" name="snippet_assets" id="copy" value="Copy">
- <label for="copy">Copy</label><br>
- <input type="checkbox" name="snippet_assets" id="icon" value="Icon">
- <label for="icon">Icon</label><br>
- <input type="checkbox" name="snippet_assets" id="special_format" value="Special Format">
- <label for="special_format">Special Format</label><br>
- <input type="checkbox" name="snippet_assets" id="assetothersradio">
- <label for="assetothers">Others: </label>
- <input type="text" name="snippet_assets" id="assetothers" size="30">
- </div>
-
- <input type="hidden" required name="name" id="name" value="[% user.name FILTER html %]" size="80">
-
- <input type="hidden" required name="email" id="email" value="[% user.email FILTER html %]" size="80">
-
- <div class="row">
- <div class="row_desc">
- <label for="else">
- <strong>Anything else you would like to add?</strong><br>
- </label>
- </div>
- <textarea name="else" id="else" cols="80" rows="10"></textarea>
- </div>
- <h3><strong>Bonus Question</strong></h3>
- <div class="row">
- <div class="row_desc">
- <strong>Who do you think is coolest?</strong>
- </div>
- <input type="radio" name="bonus" id="han_solo" value="Hans Solo">
- <label for="han_solo">Han Solo</label><br>
- <input type="radio" name="bonus" id="hermoine_granger" value="Hermoine Granger">
- <label for="hermoine_granger">Hermione Granger</label><br>
- <input type="radio" name="bonus" id="picard" value="Captain Jean-Luc Picard">
- <label for="picard">Captain Jean-Luc Picard</label><br>
- <input type="radio" name="bonus" id="everdeen" value="Katniss Everdeen">
- <label for="everdeen">Katniss Everdeen</label><br>
- </div>
-
- <p>Thanks for reaching out. Click submit and we will be back with you shortly.</p>
-
- <input type="submit" id="commit" value="Submit Request">
- <p>
- [ <span class="required_star">*</span> <span class="required_explanation">Required Field</span> ]
- </p>
- </form>
-</div>
-
-[% PROCESS global/footer.html.tmpl %] \ No newline at end of file
diff --git a/extensions/BMO/template/en/default/pages/user_activity.html.tmpl b/extensions/BMO/template/en/default/pages/user_activity.html.tmpl
index 075c8edf9..5603b943f 100644
--- a/extensions/BMO/template/en/default/pages/user_activity.html.tmpl
+++ b/extensions/BMO/template/en/default/pages/user_activity.html.tmpl
@@ -199,6 +199,7 @@
change.fieldname == 'reporter' ||
change.fieldname == 'qa_contact' ||
change.fieldname == 'cc' ||
+ change.fieldname == 'bug_mentor' ||
change.fieldname == 'flagtypes.name' %]
[% display_value(change.fieldname, change_type) FILTER email FILTER html %]
[% ELSE %]
diff --git a/extensions/BMO/web/images/ipc_form_buildscreen.png b/extensions/BMO/web/images/ipc_form_buildscreen.png
deleted file mode 100644
index d98207d29..000000000
--- a/extensions/BMO/web/images/ipc_form_buildscreen.png
+++ /dev/null
Binary files differ
diff --git a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl
index a8d55c137..51919ab27 100644
--- a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl
+++ b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl
@@ -365,7 +365,7 @@
", " UNLESS loop.last;
END;
- CASE [ 'assigned_to', 'reporter', 'qa_contact', 'cc', 'flagtypes.name' ];
+ CASE [ 'assigned_to', 'reporter', 'qa_contact', 'cc', 'bug_mentor', 'flagtypes.name' ];
value FILTER email;
CASE 'reporter_accessible';
diff --git a/extensions/BugModal/template/en/default/bug_modal/navigate.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/navigate.html.tmpl
index 05476fed4..06163a841 100644
--- a/extensions/BugModal/template/en/default/bug_modal/navigate.html.tmpl
+++ b/extensions/BugModal/template/en/default/bug_modal/navigate.html.tmpl
@@ -29,14 +29,14 @@
[% INCLUDE nav_link text="Next &#10097;" bug_id="" %]
[% END %]
[% INCLUDE nav_link text="Last &#10097;&#10097;" bug_id=last_bug_list.last %]
- <a id="search-nav-reget" href="buglist.cgi?regetlastlist=[% my_search.id FILTER uri %]">Last search results</a>
+ <a id="search-nav-reget" href="buglist.cgi?regetlastlist=[% search.id FILTER uri %]">Last search results</a>
</div>
[% BLOCK nav_link %]
[% IF bug_id == "" %]
<span class="search-nav-disabled">[% text FILTER none %]</span>
[% ELSE %]
- <a class="search-nav-link" href="show_bug.cgi?id=[% bug_id FILTER uri %]&amp;list_id=[% my_search.id FILTER uri %]">
+ <a class="search-nav-link" href="show_bug.cgi?id=[% bug_id FILTER uri %]&amp;list_id=[% search.id FILTER uri %]">
[%~ text FILTER none ~%]
</a>
[% END %]
diff --git a/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl b/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl
index d0a3abb5b..32e6499cb 100644
--- a/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl
+++ b/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl
@@ -155,6 +155,7 @@
change.fieldname == 'reporter' ||
change.fieldname == 'qa_contact' ||
change.fieldname == 'cc' ||
+ change.fieldname == 'bug_mentor' ||
change.fieldname == 'flagtypes.name' %]
[% value FILTER email FILTER js %]
[% ELSIF change.fieldtype == constants.FIELD_TYPE_DATETIME %]
diff --git a/extensions/OpenGraph/template/en/default/hook/global/header-start.html.tmpl b/extensions/OpenGraph/template/en/default/hook/global/header-start.html.tmpl
index 9b8e0e0cc..2d7c3379f 100644
--- a/extensions/OpenGraph/template/en/default/hook/global/header-start.html.tmpl
+++ b/extensions/OpenGraph/template/en/default/hook/global/header-start.html.tmpl
@@ -8,6 +8,11 @@
[% USE Bugzilla %]
<meta property="og:type" content="website">
-<meta property="og:image" content="[% urlbase FILTER none %]extensions/OpenGraph/web/moz-social-bw-rgb-1200x1200.png">
<meta property="og:title" content="[% title FILTER none %]">
<meta property="og:url" content="[% Bugzilla.cgi.self_url FILTER html %]">
+[% IF bug %]
+<meta property="og:description"
+ content="[% bug.bug_status FILTER html %] ([% bug.assigned_to.login FILTER email FILTER html %]) in [% bug.product FILTER html %] - [% bug.component FILTER html %]. Last updated [% bug.delta_ts FILTER time('%Y-%m-%d') %].">
+[% ELSE %]
+<meta property="og:image" content="[% urlbase FILTER none %]extensions/OpenGraph/web/moz-social-bw-rgb-1200x1200.png">
+[% END %]
diff --git a/extensions/PhabBugz/Extension.pm b/extensions/PhabBugz/Extension.pm
index b3ad44819..ee96901a2 100644
--- a/extensions/PhabBugz/Extension.pm
+++ b/extensions/PhabBugz/Extension.pm
@@ -15,7 +15,6 @@ use parent qw(Bugzilla::Extension);
use Bugzilla::Constants;
use Bugzilla::Extension::PhabBugz::Feed;
-use Bugzilla::Extension::PhabBugz::Logger;
our $VERSION = '0.01';
diff --git a/extensions/PhabBugz/lib/Daemon.pm b/extensions/PhabBugz/lib/Daemon.pm
index c8b4f73af..ef4a00534 100644
--- a/extensions/PhabBugz/lib/Daemon.pm
+++ b/extensions/PhabBugz/lib/Daemon.pm
@@ -13,7 +13,6 @@ use warnings;
use Bugzilla::Constants;
use Bugzilla::Extension::PhabBugz::Feed;
-use Bugzilla::Extension::PhabBugz::Logger;
use Carp qw(confess);
use Daemon::Generic;
@@ -89,11 +88,9 @@ sub gd_setup_signals {
sub gd_run {
my $self = shift;
- $::SIG{__DIE__} = \&Carp::confess if $self->{debug};
+ $SIG{__DIE__} = \&Carp::confess if $self->{debug};
my $phabbugz = Bugzilla::Extension::PhabBugz::Feed->new();
$phabbugz->is_daemon(1);
- $phabbugz->logger(
- Bugzilla::Extension::PhabBugz::Logger->new(debugging => $self->{debug}));
$phabbugz->start();
}
diff --git a/extensions/PhabBugz/lib/Feed.pm b/extensions/PhabBugz/lib/Feed.pm
index 9904d5090..074ecc0f9 100644
--- a/extensions/PhabBugz/lib/Feed.pm
+++ b/extensions/PhabBugz/lib/Feed.pm
@@ -13,6 +13,7 @@ use List::Util qw(first);
use List::MoreUtils qw(any);
use Moo;
+use Bugzilla::Logging;
use Bugzilla::Constants;
use Bugzilla::Search;
use Bugzilla::Util qw(diff_arrays with_writable_database with_readonly_database);
@@ -36,7 +37,6 @@ use Bugzilla::Extension::PhabBugz::Util qw(
);
has 'is_daemon' => ( is => 'rw', default => 0 );
-has 'logger' => ( is => 'rw' );
sub start {
my ($self) = @_;
@@ -48,7 +48,7 @@ sub start {
}
1;
};
- $self->logger->error( $@ // "unknown exception" ) unless $ok;
+ ERROR( $@ // "unknown exception" ) unless $ok;
sleep(PHAB_POLL_SECONDS);
}
}
@@ -59,19 +59,19 @@ sub feed_query {
# Ensure Phabricator syncing is enabled
if (!Bugzilla->params->{phabricator_enabled}) {
- $self->logger->info("PHABRICATOR SYNC DISABLED");
+ INFO("PHABRICATOR SYNC DISABLED");
return;
}
# PROCESS NEW FEED TRANSACTIONS
- $self->logger->info("FEED: Fetching new transactions");
+ INFO("FEED: Fetching new transactions");
my $story_last_id = $self->get_last_id('feed');
# Check for new transctions (stories)
my $new_stories = $self->new_stories($story_last_id);
- $self->logger->info("FEED: No new stories") unless @$new_stories;
+ INFO("FEED: No new stories") unless @$new_stories;
# Process each story
foreach my $story_data (@$new_stories) {
@@ -81,15 +81,15 @@ sub feed_query {
my $object_phid = $story_data->{objectPHID};
my $story_text = $story_data->{text};
- $self->logger->debug("STORY ID: $story_id");
- $self->logger->debug("STORY PHID: $story_phid");
- $self->logger->debug("AUTHOR PHID: $author_phid");
- $self->logger->debug("OBJECT PHID: $object_phid");
- $self->logger->info("STORY TEXT: $story_text");
+ DEBUG("STORY ID: $story_id");
+ DEBUG("STORY PHID: $story_phid");
+ DEBUG("AUTHOR PHID: $author_phid");
+ DEBUG("OBJECT PHID: $object_phid");
+ INFO("STORY TEXT: $story_text");
# Only interested in changes to revisions for now.
if ($object_phid !~ /^PHID-DREV/) {
- $self->logger->debug("SKIPPING: Not a revision change");
+ DEBUG("SKIPPING: Not a revision change");
$self->save_last_id($story_id, 'feed');
next;
}
@@ -99,7 +99,7 @@ sub feed_query {
if (@$phab_users) {
my $user = Bugzilla::User->new({ id => $phab_users->[0]->{id}, cache => 1 });
if ($user->login eq PHAB_AUTOMATION_USER) {
- $self->logger->debug("SKIPPING: Change made by phabricator user");
+ DEBUG("SKIPPING: Change made by phabricator user");
$self->save_last_id($story_id, 'feed');
next;
}
@@ -113,13 +113,13 @@ sub feed_query {
# PROCESS NEW USERS
- $self->logger->info("FEED: Fetching new users");
+ INFO("FEED: Fetching new users");
my $user_last_id = $self->get_last_id('user');
# Check for new users
my $new_users = $self->new_users($user_last_id);
- $self->logger->info("FEED: No new users") unless @$new_users;
+ INFO("FEED: No new users") unless @$new_users;
# Process each new user
foreach my $user_data (@$new_users) {
@@ -128,10 +128,10 @@ sub feed_query {
my $user_realname = $user_data->{fields}{realName};
my $object_phid = $user_data->{phid};
- $self->logger->debug("USER ID: $user_id");
- $self->logger->debug("USER LOGIN: $user_login");
- $self->logger->debug("USER REALNAME: $user_realname");
- $self->logger->debug("OBJECT PHID: $object_phid");
+ DEBUG("USER ID: $user_id");
+ DEBUG("USER LOGIN: $user_login");
+ DEBUG("USER REALNAME: $user_realname");
+ DEBUG("OBJECT PHID: $object_phid");
with_readonly_database {
$self->process_new_user($user_data);
@@ -151,15 +151,15 @@ sub process_revision_change {
if (!$revision->bug_id) {
if ($story_text =~ /\s+created\s+D\d+/) {
# If new revision and bug id was omitted, make revision public
- $self->logger->debug("No bug associated with new revision. Marking public.");
+ DEBUG("No bug associated with new revision. Marking public.");
$revision->set_policy('view', 'public');
$revision->set_policy('edit', 'users');
$revision->update();
- $self->logger->info("SUCCESS");
+ INFO("SUCCESS");
return;
}
else {
- $self->logger->debug("SKIPPING: No bug associated with revision change");
+ DEBUG("SKIPPING: No bug associated with revision change");
return;
}
}
@@ -170,7 +170,7 @@ sub process_revision_change {
$revision->title,
$revision->bug_id,
$story_text);
- $self->logger->info($log_message);
+ INFO($log_message);
# Pre setup before making changes
my $old_user = set_phab_user();
@@ -180,7 +180,7 @@ sub process_revision_change {
# If bug is public then remove privacy policy
if (!@{ $bug->groups_in }) {
- $self->logger->debug('Bug is public so setting view/edit public');
+ DEBUG('Bug is public so setting view/edit public');
$revision->set_policy('view', 'public');
$revision->set_policy('edit', 'users');
my $secure_project_phid = get_project_phid('secure-revision');
@@ -193,7 +193,7 @@ sub process_revision_change {
# If bug privacy groups do not have any matching synchronized groups,
# then leave revision private and it will have be dealt with manually.
if (!@set_groups) {
- $self->logger->debug('No matching groups. Adding comments to bug and revision');
+ DEBUG('No matching groups. Adding comments to bug and revision');
add_security_sync_comments([$revision], $bug);
}
# Otherwise, we create a new custom policy containing the project
@@ -205,23 +205,23 @@ sub process_revision_change {
# we leave the current policy alone.
my $current_policy;
if ($revision->view_policy =~ /^PHID-PLCY/) {
- $self->logger->debug("Loading current policy: " . $revision->view_policy);
+ DEBUG("Loading current policy: " . $revision->view_policy);
$current_policy
= Bugzilla::Extension::PhabBugz::Policy->new_from_query({ phids => [ $revision->view_policy ]});
my $current_projects = $current_policy->rule_projects;
- $self->logger->debug("Current policy projects: " . join(", ", @$current_projects));
+ DEBUG("Current policy projects: " . join(", ", @$current_projects));
my ($added, $removed) = diff_arrays($current_projects, \@set_projects);
if (@$added || @$removed) {
- $self->logger->debug('Project groups do not match. Need new custom policy');
+ DEBUG('Project groups do not match. Need new custom policy');
$current_policy= undef;
}
else {
- $self->logger->debug('Project groups match. Leaving current policy as-is');
+ DEBUG('Project groups match. Leaving current policy as-is');
}
}
if (!$current_policy) {
- $self->logger->debug("Creating new custom policy: " . join(", ", @set_projects));
+ DEBUG("Creating new custom policy: " . join(", ", @set_projects));
my $new_policy = Bugzilla::Extension::PhabBugz::Policy->create(\@set_projects);
$revision->set_policy('view', $new_policy->phid);
$revision->set_policy('edit', $new_policy->phid);
@@ -229,15 +229,17 @@ sub process_revision_change {
my $secure_project_phid = get_project_phid('secure-revision');
$revision->add_project($secure_project_phid);
-
- my $subscribers = get_bug_role_phids($bug);
- $revision->set_subscribers($subscribers);
}
+
+ # Subscriber list of the private revision should always match
+ # the bug roles such as assignee, qa contact, and cc members.
+ my $subscribers = get_bug_role_phids($bug);
+ $revision->set_subscribers($subscribers);
}
my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()");
- my $attachment = create_revision_attachment($bug, $revision->id, $revision->title, $timestamp);
+ my $attachment = create_revision_attachment($bug, $revision, $timestamp);
# ATTACHMENT OBSOLETES
@@ -250,11 +252,11 @@ sub process_revision_change {
next if $attach_revision_id != $revision->id;
my $make_obsolete = $revision->status eq 'abandoned' ? 1 : 0;
- $self->logger->debug('Updating obsolete status on attachmment ' . $attachment->id);
+ DEBUG('Updating obsolete status on attachmment ' . $attachment->id);
$attachment->set_is_obsolete($make_obsolete);
if ($revision->title ne $attachment->description) {
- $self->logger->debug('Updating description on attachment ' . $attachment->id);
+ DEBUG('Updating description on attachment ' . $attachment->id);
$attachment->set_description($revision->title);
}
@@ -270,7 +272,7 @@ sub process_revision_change {
});
foreach my $attachment (@$other_attachments) {
$other_bugs{$attachment->bug_id}++;
- $self->logger->debug('Updating obsolete status on attachment ' .
+ DEBUG('Updating obsolete status on attachment ' .
$attachment->id . " for bug " . $attachment->bug_id);
$attachment->set_is_obsolete(1);
$attachment->update($timestamp);
@@ -291,6 +293,8 @@ sub process_revision_change {
$phab_users = get_phab_bmo_ids({ phids => \@denied_phids });
@denied_user_ids = map { $_->{id} } @$phab_users;
+ my %reviewers_hash = map { $_->name => 1 } @{ $revision->reviewers };
+
foreach my $attachment (@attachments) {
my ($attach_revision_id) = ($attachment->filename =~ PHAB_ATTACHMENT_PATTERN);
next if $revision->id != $attach_revision_id;
@@ -331,7 +335,11 @@ sub process_revision_change {
$comment .= $flag_data->{setter}->name . " has requested changes to the revision.\n";
}
foreach my $flag_data (@removed_flags) {
- $comment .= $flag_data->{setter}->name . " has been removed from the revision.\n";
+ if ( exists $reviewers_hash{$flag_data->{setter}->name} ) {
+ $comment .= "Flag set by " . $flag_data->{setter}->name . " is no longer active.\n";
+ } else {
+ $comment .= $flag_data->{setter}->name . " has been removed from the revision.\n";
+ }
}
if ($comment) {
@@ -362,7 +370,7 @@ sub process_revision_change {
Bugzilla->set_user($old_user);
- $self->logger->info('SUCCESS: Revision D' . $revision->id . ' processed');
+ INFO('SUCCESS: Revision D' . $revision->id . ' processed');
}
sub process_new_user {
@@ -372,7 +380,7 @@ sub process_new_user {
my $phab_user = Bugzilla::Extension::PhabBugz::User->new($user_data);
if (!$phab_user->bugzilla_id) {
- $self->logger->debug("SKIPPING: No bugzilla id associated with user");
+ DEBUG("SKIPPING: No bugzilla id associated with user");
return;
}
@@ -425,7 +433,7 @@ sub process_new_user {
my @bug_ids = map { shift @$_ } @$data;
foreach my $bug_id (@bug_ids) {
- $self->logger->debug("Processing bug $bug_id");
+ DEBUG("Processing bug $bug_id");
my $bug = Bugzilla::Bug->new({ id => $bug_id, cache => 1 });
@@ -434,7 +442,7 @@ sub process_new_user {
foreach my $attachment (@attachments) {
my ($revision_id) = ($attachment->filename =~ PHAB_ATTACHMENT_PATTERN);
- $self->logger->debug("Processing revision D$revision_id");
+ DEBUG("Processing revision D$revision_id");
my $revision = Bugzilla::Extension::PhabBugz::Revision->new_from_query(
{ ids => [ int($revision_id) ] });
@@ -442,13 +450,13 @@ sub process_new_user {
$revision->add_subscriber($phab_user->phid);
$revision->update();
- $self->logger->debug("Revision $revision_id updated");
+ DEBUG("Revision $revision_id updated");
}
}
Bugzilla->set_user($old_user);
- $self->logger->info('SUCCESS: User ' . $phab_user->id . ' processed');
+ INFO('SUCCESS: User ' . $phab_user->id . ' processed');
}
##################
@@ -496,7 +504,7 @@ sub get_last_id {
my $last_id = Bugzilla->dbh->selectrow_array( "
SELECT value FROM phabbugz WHERE name = ?", undef, $type_full );
$last_id ||= 0;
- $self->logger->debug( "QUERY " . uc($type_full) . ": $last_id" );
+ DEBUG( "QUERY " . uc($type_full) . ": $last_id" );
return $last_id;
}
@@ -505,7 +513,7 @@ sub save_last_id {
# Store the largest last key so we can start from there in the next session
my $type_full = $type . "_last_id";
- $self->logger->debug( "UPDATING " . uc($type_full) . ": $last_id" );
+ DEBUG( "UPDATING " . uc($type_full) . ": $last_id" );
Bugzilla->dbh->do( "REPLACE INTO phabbugz (name, value) VALUES (?, ?)",
undef, $type_full, $last_id );
}
diff --git a/extensions/PhabBugz/lib/Logger.pm b/extensions/PhabBugz/lib/Logger.pm
deleted file mode 100644
index 3127b66db..000000000
--- a/extensions/PhabBugz/lib/Logger.pm
+++ /dev/null
@@ -1,37 +0,0 @@
-# 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.
-
-package Bugzilla::Extension::PhabBugz::Logger;
-
-use 5.10.1;
-
-use Moo;
-
-use Bugzilla::Extension::PhabBugz::Constants;
-
-has 'debugging' => ( is => 'ro' );
-
-sub info { shift->_log_it('INFO', @_) }
-sub error { shift->_log_it('ERROR', @_) }
-sub debug { shift->_log_it('DEBUG', @_) }
-
-sub _log_it {
- my ($self, $method, $message) = @_;
-
- return if $method eq 'DEBUG' && !$self->debugging;
- chomp $message;
- if ($ENV{MOD_PERL}) {
- require Apache2::Log;
- Apache2::ServerRec::warn("FEED $method: $message");
- } elsif ($ENV{SCRIPT_FILENAME}) {
- print STDERR "FEED $method: $message\n";
- } else {
- print STDERR '[' . localtime(time) ."] $method: $message\n";
- }
-}
-
-1;
diff --git a/extensions/PhabBugz/lib/Revision.pm b/extensions/PhabBugz/lib/Revision.pm
index c114de78c..98c3196c2 100644
--- a/extensions/PhabBugz/lib/Revision.pm
+++ b/extensions/PhabBugz/lib/Revision.pm
@@ -29,6 +29,7 @@ use Bugzilla::Extension::PhabBugz::Util qw(
has id => ( is => 'ro', isa => Int );
has phid => ( is => 'ro', isa => Str );
has title => ( is => 'ro', isa => Str );
+has summary => ( is => 'ro', isa => Str );
has status => ( is => 'ro', isa => Str );
has creation_ts => ( is => 'ro', isa => Str );
has modification_ts => ( is => 'ro', isa => Str );
@@ -93,6 +94,7 @@ sub BUILDARGS {
my ( $class, $params ) = @_;
$params->{title} = $params->{fields}->{title};
+ $params->{summary} = $params->{fields}->{summary};
$params->{status} = $params->{fields}->{status}->{value};
$params->{creation_ts} = $params->{fields}->{dateCreated};
$params->{modification_ts} = $params->{fields}->{dateModified};
diff --git a/extensions/PhabBugz/lib/Util.pm b/extensions/PhabBugz/lib/Util.pm
index 8085828f9..844d8c0b5 100644
--- a/extensions/PhabBugz/lib/Util.pm
+++ b/extensions/PhabBugz/lib/Util.pm
@@ -77,12 +77,12 @@ sub _get_revisions {
}
sub create_revision_attachment {
- my ( $bug, $revision_id, $revision_title, $timestamp ) = @_;
+ my ( $bug, $revision, $timestamp ) = @_;
my $phab_base_uri = Bugzilla->params->{phabricator_base_uri};
ThrowUserError('invalid_phabricator_uri') unless $phab_base_uri;
- my $revision_uri = $phab_base_uri . "D" . $revision_id;
+ my $revision_uri = $phab_base_uri . "D" . $revision->id;
# Check for previous attachment with same revision id.
# If one matches then return it instead. This is fine as
@@ -102,8 +102,8 @@ sub create_revision_attachment {
bug => $bug,
creation_ts => $timestamp,
data => $revision_uri,
- description => $revision_title,
- filename => 'phabricator-D' . $revision_id . '-url.txt',
+ description => $revision->title,
+ filename => 'phabricator-D' . $revision->id . '-url.txt',
ispatch => 0,
isprivate => 0,
mimetype => PHAB_CONTENT_TYPE,
@@ -111,8 +111,8 @@ sub create_revision_attachment {
);
# Insert a comment about the new attachment into the database.
- $bug->add_comment('', { type => CMT_ATTACHMENT_CREATED,
- extra_data => $attachment->id });
+ $bug->add_comment($revision->summary, { type => CMT_ATTACHMENT_CREATED,
+ extra_data => $attachment->id });
return $attachment;
}
diff --git a/extensions/Push/lib/Logger.pm b/extensions/Push/lib/Logger.pm
index 7ae96b58a..5d92010ee 100644
--- a/extensions/Push/lib/Logger.pm
+++ b/extensions/Push/lib/Logger.pm
@@ -8,53 +8,43 @@
package Bugzilla::Extension::Push::Logger;
use 5.10.1;
-use strict;
-use warnings;
+use Moo;
+use Bugzilla::Logging;
+use Log::Log4perl;
use Bugzilla::Extension::Push::Constants;
use Bugzilla::Extension::Push::LogEntry;
-sub new {
- my ($class) = @_;
- my $self = {};
- bless($self, $class);
- return $self;
-}
+# If Log4perl then finds that it's being called from a registered wrapper, it
+# will automatically step up to the next call frame.
+Log::Log4perl->wrapper_register(__PACKAGE__);
-sub info { shift->_log_it('INFO', @_) }
-sub error { shift->_log_it('ERROR', @_) }
-sub debug { shift->_log_it('DEBUG', @_) }
+sub info {
+ my ($this, $message) = @_;
+ INFO($message);
+}
-sub debugging {
- my ($self) = @_;
- return $self->{debug};
+sub error {
+ my ($this, $message) = @_;
+ ERROR($message);
}
-sub _log_it {
- my ($self, $method, $message) = @_;
- return if $method eq 'DEBUG' && !$self->debugging;
- chomp $message;
- if ($ENV{MOD_PERL}) {
- require Apache2::Log;
- Apache2::ServerRec::warn("Push $method: $message");
- } elsif ($ENV{SCRIPT_FILENAME}) {
- print STDERR "Push $method: $message\n";
- } else {
- print STDERR '[' . localtime(time) ."] $method: $message\n";
- }
+sub debug {
+ my ($this, $message) = @_;
+ DEBUG($message);
}
sub result {
my ($self, $connector, $message, $result, $data) = @_;
$data ||= '';
- $self->info(sprintf(
- "%s: Message #%s: %s %s",
+ my $log_msg = sprintf
+ '%s: Message #%s: %s %s',
$connector->name,
$message->message_id,
push_result_to_string($result),
- $data
- ));
+ $data;
+ $self->info($log_msg);
Bugzilla::Extension::Push::LogEntry->create({
message_id => $message->message_id,
@@ -68,4 +58,6 @@ sub result {
});
}
+sub _build_logger { Log::Log4perl->get_logger(__PACKAGE__); }
+
1;
diff --git a/extensions/Review/Extension.pm b/extensions/Review/Extension.pm
index db2b475a1..f05f2ba8b 100644
--- a/extensions/Review/Extension.pm
+++ b/extensions/Review/Extension.pm
@@ -93,6 +93,8 @@ sub _reviewers_objs {
sub _user_is_active {
my ($self) = @_;
+ # never consider .bugs or .tld addresses as inactive.
+ return 1 if $self->login =~ /bugs$/ || $self->login =~ /\.tld$/;
return 1 unless Bugzilla->params->{max_reviewer_last_seen};
return 0 if !defined($self->last_seen_date);
diff --git a/extensions/UserProfile/template/en/default/pages/user_profile.html.tmpl b/extensions/UserProfile/template/en/default/pages/user_profile.html.tmpl
index 27cb825ed..bea0d16d7 100644
--- a/extensions/UserProfile/template/en/default/pages/user_profile.html.tmpl
+++ b/extensions/UserProfile/template/en/default/pages/user_profile.html.tmpl
@@ -27,7 +27,7 @@
<td>&nbsp;</td>
<th>Search</th>
<td colspan="2">
- <form action="user_profile">
+ <form action="[% urlbase FILTER html %]user_profile">
[% INCLUDE global/userselect.html.tmpl
id => "login"
name => "login"
diff --git a/heartbeat.cgi b/heartbeat.cgi
index 917853d2b..08cf13fa7 100755
--- a/heartbeat.cgi
+++ b/heartbeat.cgi
@@ -13,6 +13,7 @@ use warnings;
use lib qw(. lib local/lib/perl5);
use Bugzilla;
+use Bugzilla::Logging;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Update;
@@ -29,10 +30,9 @@ my $ok = eval {
die "database not available" unless $database_ok;
die "memcached server(s) not available" unless $memcached_ok;
die "mod_perl/psgi not configured?" unless BZ_PERSISTENT;
- die "missing bmo feature dependencies" unless Bugzilla->has_feature('bmo');
1;
};
-warn "heartbeat error: $@" if !$ok && $@;
+FATAL("heartbeat error: $@") if !$ok && $@;
my $cgi = Bugzilla->cgi;
print $cgi->header(-type => 'text/plain', -status => $ok ? '200 OK' : '500 Internal Server Error');
diff --git a/helper.psgi b/helper.psgi
new file mode 100644
index 000000000..cc8c648a8
--- /dev/null
+++ b/helper.psgi
@@ -0,0 +1,35 @@
+#!/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 Plack::Request;
+use Plack::Response;
+
+my $app = sub {
+ my $env = shift;
+ my $req = Plack::Request->new($env);
+ my $res = Plack::Response->new(404);
+ my $urlbase = Bugzilla->localconfig->{urlbase};
+ my $path = $req->path;
+
+ if ( $path eq '/quicksearch.html' ) {
+ $res->redirect( $urlbase . 'page.cgi?id=quicksearch.html', 301 );
+ }
+ elsif ( $path eq '/bugwritinghelp.html') {
+ $res->redirect( $urlbase . 'page.cgi?id=bug-writing.html', 301 );
+ }
+ elsif ( $path =~ m{^/(\d+)$}s ) {
+ $res->redirect( $urlbase . "show_bug.cgi?id=$1", 301 );
+ }
+ else {
+ $res->body('not found');
+ }
+ return $res->finalize;
+}; \ No newline at end of file
diff --git a/jobqueue-worker.pl b/jobqueue-worker.pl
new file mode 100644
index 000000000..b26aacdba
--- /dev/null
+++ b/jobqueue-worker.pl
@@ -0,0 +1,47 @@
+#!/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 File::Basename qw(basename dirname);
+use File::Spec::Functions qw(catdir rel2abs);
+
+BEGIN {
+ require lib;
+ my $dir = rel2abs( dirname(__FILE__) );
+ lib->import( $dir, catdir( $dir, 'lib' ), catdir( $dir, qw(local lib perl5) ) );
+ chdir $dir or die "chdir $dir failed: $!";
+
+}
+
+use Bugzilla::JobQueue::Worker;
+use Bugzilla::JobQueue;
+use Bugzilla;
+use English qw(-no_match_vars $PROGRAM_NAME $OSNAME);
+use Getopt::Long qw(:config gnu_getopt);
+use if $OSNAME eq 'linux', 'Linux::Pdeathsig', 'set_pdeathsig';
+
+BEGIN { Bugzilla->extensions }
+my $name = basename(__FILE__);
+
+GetOptions( 'name=s' => \$name );
+
+if ($name) {
+ ## no critic (Variables::RequireLocalizedPunctuationVars)
+ $PROGRAM_NAME = $name;
+ ## use critic
+}
+
+if ($OSNAME eq 'linux') {
+ # get SIGTEMR (15) when parent exits.
+ set_pdeathsig(15);
+}
+
+Bugzilla::JobQueue::Worker->run('work');
diff --git a/jobqueue.pl b/jobqueue.pl
index f5541e0fb..7a884b811 100755
--- a/jobqueue.pl
+++ b/jobqueue.pl
@@ -10,12 +10,13 @@ use 5.10.1;
use strict;
use warnings;
-use File::Basename;
-use File::Spec;
+use File::Basename qw(dirname);
+use File::Spec::Functions qw(catdir rel2abs);
+
BEGIN {
require lib;
- my $dir = File::Spec->rel2abs(dirname(__FILE__));
- lib->import($dir, File::Spec->catdir($dir, 'lib'), File::Spec->catdir($dir, qw(local lib perl5)));
+ my $dir = rel2abs( dirname(__FILE__) );
+ lib->import( $dir, catdir( $dir, 'lib' ), catdir( $dir, qw(local lib perl5) ) );
chdir $dir or die "chdir $dir failed: $!";
}
@@ -39,6 +40,8 @@ jobqueue.pl - Runs jobs in the background for Bugzilla.
process id. Defaults to F<data/jobqueue.pl.pid>.
-n name What should this process call itself in the system log?
Defaults to the full path you used to invoke the script.
+ -j jobs How many child processes should be run?
+ Defaults to 1.
COMMANDS:
start Starts a new jobqueue daemon if there isn't one running already
diff --git a/mod_perl.pl b/mod_perl.pl
index 3221af19f..c64352600 100644
--- a/mod_perl.pl
+++ b/mod_perl.pl
@@ -58,6 +58,7 @@ use Apache2::SizeLimit;
use ModPerl::RegistryLoader ();
use File::Basename ();
use File::Find ();
+use English qw(-no_match_vars $OSNAME);
# This loads most of our modules.
use Bugzilla ();
@@ -81,8 +82,9 @@ Bugzilla::CGI->compile(qw(:cgi :push));
# is taking up more than $apache_size_limit of RAM all by itself, not counting RAM it is
# sharing with the other httpd processes.
my $limit = Bugzilla->localconfig->{apache_size_limit};
-if ($limit < 400_000) {
- $limit = 400_000;
+if ($OSNAME eq 'linux' && ! eval { require Linux::Smaps }) {
+ warn "SizeLimit requires Linux::Smaps on linux. size limit set to 800MB";
+ $limit = 800_000;
}
Apache2::SizeLimit->set_max_unshared_size($limit);
diff --git a/scripts/build-bmo-push-data.pl b/scripts/build-bmo-push-data.pl
new file mode 100755
index 000000000..e3cefe533
--- /dev/null
+++ b/scripts/build-bmo-push-data.pl
@@ -0,0 +1,203 @@
+#!/usr/bin/perl
+use 5.10.1;
+use strict;
+use warnings;
+
+use File::Basename qw(basename dirname);
+use File::Spec::Functions qw(catdir rel2abs);
+use Cwd qw(realpath);
+
+BEGIN {
+ require lib;
+ my $dir = realpath( catdir(dirname(__FILE__), '..') );
+ lib->import( $dir, catdir( $dir, 'lib' ), catdir( $dir, qw(local lib perl5) ) );
+ chdir $dir or die "chdir $dir failed: $!";
+}
+
+use autodie;
+use Bugzilla;
+use English qw(-no_match_vars $PROGRAM_NAME);
+use IPC::System::Simple qw(runx capture);
+use JSON::MaybeXS qw(decode_json);
+use LWP::Simple qw(get);
+use LWP::UserAgent;
+use MIME::Base64 qw(decode_base64);
+use URI::QueryParam;
+use URI;
+
+my $github_repo = "https://github.com/mozilla-bteam/bmo";
+my $version_info = decode_json(get('https://bugzilla.mozilla.org/__version__'));
+my $tag = 'release-' . Bugzilla->VERSION;
+my $prod_tag = "release-$version_info->{version}";
+my $tag_url = "$github_repo/tree/$tag";
+
+my @log = capture(qw(git log --oneline), "$prod_tag..HEAD");
+die "nothing to commit\n" unless @log;
+chomp @log;
+
+my @revisions;
+foreach my $line (@log) {
+ say $line;
+ my ($revision, $message);
+ unless ( ( $revision, $message ) = $line =~ /^(\S+) (.+)$/ ) {
+ warn "skipping $line\n";
+ next;
+ }
+
+ my @bug_ids;
+ if ($message =~ /\bBug (\d+)/i) {
+ push @bug_ids, $1;
+ }
+
+ if (!@bug_ids) {
+ warn "skipping $line (no bug)\n";
+ next;
+ }
+
+ foreach my $bug_id (@bug_ids) {
+ my $duplicate = 0;
+ foreach my $revisions (@revisions) {
+ if ($revisions->{bug_id} == $bug_id) {
+ $duplicate = 1;
+ last;
+ }
+ }
+ next if $duplicate;
+
+ my $bug = fetch_bug($bug_id);
+ if ($bug->{status} eq 'RESOLVED' && $bug->{resolution} ne 'FIXED') {
+ next;
+ }
+ if ($bug->{summary} =~ /\bbackport\s+(?:upstream\s+)?bug\s+(\d+)/i) {
+ my $upstream = $1;
+ $bug->{summary} = fetch_bug($upstream)->{summary};
+ }
+ push @revisions, {
+ hash => $revision,
+ bug_id => $bug_id,
+ summary => $bug->{summary},
+ };
+ }
+}
+if (!@revisions) {
+ die "no new revisions. make sure you run this script before production is updated.\n";
+}
+else {
+ @revisions = reverse @revisions;
+}
+
+my $first_revision = $revisions[0]->{hash};
+my $last_revision = $revisions[-1]->{hash};
+
+mkdir 'build_info' unless -d 'build_info';
+chdir 'build_info';
+
+say "write tag.txt";
+open my $tag_fh, '>', 'tag.txt';
+say $tag_fh $tag;
+close $tag_fh;
+
+say 'write bug.push.txt';
+
+open my $bug_fh, '>', 'bug.push.txt';
+say $bug_fh 'https://bugzilla.mozilla.org/enter_bug.cgi?product=bugzilla.mozilla.org&component=Infrastructure&short_desc=push+updated+bugzilla.mozilla.org+live';
+say $bug_fh "revisions: $first_revision - $last_revision";
+foreach my $revision (@revisions) {
+ say $bug_fh "bug $revision->{bug_id} : $revision->{summary}";
+}
+close $bug_fh;
+
+say 'write blog.push.txt';
+
+open my $blog_fh, '>', 'blog.push.txt';
+say $blog_fh "[release tag]($tag_url)\n";
+say $blog_fh "the following changes have been pushed to bugzilla.mozilla.org:\n<ul>";
+foreach my $revision (@revisions) {
+ printf $blog_fh '<li>[<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=%s" target="_blank">%s</a>] %s</li>%s',
+ $revision->{bug_id}, $revision->{bug_id}, html_escape($revision->{summary}), "\n";
+}
+say $blog_fh '</ul>';
+say $blog_fh q{discuss these changes on <a href="https://lists.mozilla.org/listinfo/tools-bmo" target="_blank">mozilla.tools.bmo</a>.};
+close $blog_fh;
+
+say 'write email.push.txt';
+
+open my $email_fh, '>', 'email.push.txt';
+say $email_fh "the following changes have been pushed to bugzilla.mozilla.org:\n";
+say $email_fh "(tag: $tag_url)\n";
+foreach my $revision (@revisions) {
+ printf $email_fh "https://bugzil.la/%s : %s\n", $revision->{bug_id}, $revision->{summary};
+}
+close $email_fh;
+
+say 'write wiki.push.txt';
+
+open my $wiki_fh, '>', 'wiki.push.txt';
+say $wiki_fh 'https://wiki.mozilla.org/BMO/Recent_Changes';
+say $wiki_fh '== ' . DateTime->now->set_time_zone('UTC')->ymd('-') . " ==\n";
+say $wiki_fh "[$tag_url $tag]";
+foreach my $revision (@revisions) {
+ printf $wiki_fh "* {{bug|%s}} %s\n", $revision->{bug_id}, $revision->{summary};
+}
+close $wiki_fh;
+
+sub html_escape {
+ my ($s) = @_;
+ $s =~ s/&/&amp;/g;
+ $s =~ s/</&lt;/g;
+ $s =~ s/>/&gt;/g;
+ return $s;
+}
+
+use constant BUG_FIELDS => [qw(
+ id
+ product
+ version
+ target_milestone
+ summary
+ status
+ resolution
+ assigned_to
+)];
+
+sub fetch_bug {
+ my ($bug_id) = @_;
+ die 'missing id' unless $bug_id;
+
+ my $response = _get( 'bug/' . $bug_id, { include_fields => BUG_FIELDS, } );
+ return $response->{bugs}->[0];
+}
+
+sub _get {
+ my ($endpoint, $args) = @_;
+ my $ua = LWP::UserAgent->new( agent => $PROGRAM_NAME );
+ $args //= {};
+
+ if (exists $args->{include_fields} && ref($args->{include_fields})) {
+ $args->{include_fields} = join ',', @{ $args->{include_fields} };
+ }
+
+ my $uri = URI->new('https://bugzilla.mozilla.org/rest/' . $endpoint);
+ foreach my $name (sort keys %$args) {
+ $uri->query_param($name => $args->{$name});
+ }
+
+ my $request = HTTP::Request->new('GET', $uri->as_string);
+ $request->header( Content_Type => 'application/json' );
+ $request->header( Accept => 'application/json' );
+ if ( $ENV{BMO_API_KEY} ) {
+ $request->header( X_Bugzilla_API_Key => $ENV{BMO_API_KEY} );
+ }
+
+ my $response = $ua->request($request);
+ if ($response->code !~ /^2/) {
+ my $error = $response->message;
+ my $ok = eval {
+ $error = decode_json($response->decoded_content)->{message};
+ 1;
+ };
+ $error = $@ unless $ok;
+ die $error . "\n";
+ }
+ return decode_json($response->decoded_content);
+}
diff --git a/scripts/cereal.pl b/scripts/cereal.pl
index d5b556451..bc4d8abd4 100755
--- a/scripts/cereal.pl
+++ b/scripts/cereal.pl
@@ -22,9 +22,12 @@ use Bugzilla::DaemonControl qw(catch_signal);
use Future;
use IO::Async::Loop;
use IO::Async::Protocol::LineStream;
+use IO::Handle;
$ENV{LOGGING_PORT} //= 5880;
+STDOUT->autoflush(1);
+
my $loop = IO::Async::Loop->new;
my $on_stream = sub {
my ($stream) = @_;
@@ -46,4 +49,4 @@ $loop->listen(
on_stream => $on_stream,
)->get;
-exit Future->wait_any(map { catch_signal($_, 0) } @signals)->get; \ No newline at end of file
+exit Future->wait_any(map { catch_signal($_, 0) } @signals)->get;
diff --git a/scripts/entrypoint.pl b/scripts/entrypoint.pl
index d5612dd85..350dcac8e 100755
--- a/scripts/entrypoint.pl
+++ b/scripts/entrypoint.pl
@@ -10,6 +10,7 @@ use Bugzilla::Install::Util qw(install_string);
use Bugzilla::Test::Util qw(create_user);
use Bugzilla::DaemonControl qw(
run_cereal_and_httpd
+ run_cereal_and_jobqueue
assert_httpd assert_database assert_selenium
on_finish on_exception
);
@@ -89,6 +90,13 @@ sub cmd_httpd {
exit $httpd_exit_f->get();
}
+sub cmd_jobqueue {
+ my (@args) = @_;
+ check_data_dir();
+ wait_for_db();
+ exit run_cereal_and_jobqueue(@args)->get;
+}
+
sub cmd_dev_httpd {
my $have_params = -f "/app/data/params";
assert_database->get();
diff --git a/ses/index.cgi b/ses/index.cgi
index aa5b34704..9e1632586 100755
--- a/ses/index.cgi
+++ b/ses/index.cgi
@@ -13,49 +13,55 @@ use warnings;
use lib qw(.. ../lib ../local/lib/perl5);
use Bugzilla ();
+use Bugzilla::Logging;
use Bugzilla::Constants qw( ERROR_MODE_DIE );
use Bugzilla::Mailer qw( MessageToMTA );
use Bugzilla::User ();
use Bugzilla::Util qw( html_quote remote_ip );
-use JSON::XS qw( decode_json encode_json );
+use JSON::MaybeXS qw( decode_json encode_json );
use LWP::UserAgent ();
use Try::Tiny qw( try catch );
Bugzilla->error_mode(ERROR_MODE_DIE);
try {
main();
-} catch {
- warn "SES: Fatal error: $_\n";
- respond(500 => 'Internal Server Error');
+}
+catch {
+ FATAL("Fatal error: $_");
+ respond( 500 => 'Internal Server Error' );
};
sub main {
- my $message = decode_json_wrapper(Bugzilla->cgi->param('POSTDATA')) // return;
+ my $message = decode_json_wrapper( Bugzilla->cgi->param('POSTDATA') ) // return;
my $message_type = $ENV{HTTP_X_AMZ_SNS_MESSAGE_TYPE} // '(missing)';
- if ($message_type eq 'SubscriptionConfirmation') {
+ if ( $message_type eq 'SubscriptionConfirmation' ) {
confirm_subscription($message);
}
- elsif ($message_type eq 'Notification') {
- my $notification = decode_json_wrapper($message->{Message}) // return;
+ elsif ( $message_type eq 'Notification' ) {
+ my $notification = decode_json_wrapper( $message->{Message} ) // return;
my $notification_type = $notification->{notificationType} // '';
- if ($notification_type eq 'Bounce') {
+ if ( $notification_type eq '' ) {
+ my $keys = join ', ', keys %$notification;
+ WARN("No notificationType in notification (keys: $keys)");
+ }
+ if ( $notification_type eq 'Bounce' ) {
process_bounce($notification);
}
- elsif ($notification_type eq 'Complaint') {
+ elsif ( $notification_type eq 'Complaint' ) {
process_complaint($notification);
}
else {
- warn "SES: Unsupported notification-type: $notification_type\n";
- respond(200 => 'OK');
+ WARN("Unsupported notification-type: $notification_type");
+ respond( 200 => 'OK' );
}
}
else {
- warn "SES: Unsupported message-type: $message_type\n";
- respond(200 => 'OK');
+ WARN("Unsupported message-type: $message_type");
+ respond( 200 => 'OK' );
}
}
@@ -63,112 +69,117 @@ sub confirm_subscription {
my ($message) = @_;
my $subscribe_url = $message->{SubscribeURL};
- if (!$subscribe_url) {
- warn "SES: Bad SubscriptionConfirmation request: missing SubscribeURL\n";
- respond(400 => 'Bad Request');
+ if ( !$subscribe_url ) {
+ WARN('Bad SubscriptionConfirmation request: missing SubscribeURL');
+ respond( 400 => 'Bad Request' );
return;
}
- my $ua = ua();
- my $res = $ua->get($message->{SubscribeURL});
- if (!$res->is_success) {
- warn "SES: Bad response from SubscribeURL: " . $res->status_line . "\n";
- respond(400 => 'Bad Request');
+ my $ua = ua();
+ my $res = $ua->get( $message->{SubscribeURL} );
+ if ( !$res->is_success ) {
+ WARN( 'Bad response from SubscribeURL: ' . $res->status_line );
+ respond( 400 => 'Bad Request' );
return;
}
- respond(200 => 'OK');
+ respond( 200 => 'OK' );
}
sub process_bounce {
my ($notification) = @_;
my $type = $notification->{bounce}->{bounceType};
- # these should be infrequent and hopefully small
- warn("SES: notification: " . encode_json($notification));
+ if ( $type eq 'Transient' ) {
- if ($type eq 'Transient') {
# just log transient bounces
- foreach my $recipient (@{ $notification->{bounce}->{bouncedRecipients} }) {
+ foreach my $recipient ( @{ $notification->{bounce}->{bouncedRecipients} } ) {
my $address = $recipient->{emailAddress};
- Bugzilla->audit("SES: transient bounce for <$address>");
+ Bugzilla->audit("transient bounce for <$address>");
}
}
- elsif ($type eq 'Permanent') {
+ elsif ( $type eq 'Permanent' ) {
+
# disable each account that is permanently bouncing
- foreach my $recipient (@{ $notification->{bounce}->{bouncedRecipients} }) {
+ foreach my $recipient ( @{ $notification->{bounce}->{bouncedRecipients} } ) {
my $address = $recipient->{emailAddress};
- my $reason = sprintf('(%s) %s', $recipient->{action} // 'error',
- $recipient->{diagnosticCode} // 'unknown');
+ my $reason
+ = sprintf( '(%s) %s', $recipient->{action} // 'error', $recipient->{diagnosticCode} // 'unknown' );
- my $user = Bugzilla::User->new({ name => $address, cache => 1 });
+ my $user = Bugzilla::User->new( { name => $address, cache => 1 } );
if ($user) {
+
# never auto-disable admin accounts
- if ($user->in_group('admin')) {
- Bugzilla->audit("SES: ignoring permanent bounce for admin <$address>: $reason");
+ if ( $user->in_group('admin') ) {
+ Bugzilla->audit("ignoring permanent bounce for admin <$address>: $reason");
}
else {
my $template = Bugzilla->template_inner();
- my $vars = {
- mta => $notification->{bounce}->{reportingMTA} // 'unknown',
+ my $vars = {
+ mta => $notification->{bounce}->{reportingMTA} // 'unknown',
reason => $reason,
};
my $disable_text;
- $template->process('admin/users/bounce-disabled.txt.tmpl', $vars, \$disable_text)
+ $template->process( 'admin/users/bounce-disabled.txt.tmpl', $vars, \$disable_text )
|| die $template->error();
- $user->set_disabledtext($disable_text);
- $user->set_disable_mail(1);
- $user->update();
- Bugzilla->audit("SES: permanent bounce for <$address> disabled userid-" . $user->id . ": $reason");
+ $user->set_disabledtext($disable_text);
+ $user->set_disable_mail(1);
+ $user->update();
+ Bugzilla->audit(
+ "permanent bounce for <$address> disabled userid-" . $user->id . ": $reason" );
}
}
else {
- Bugzilla->audit("SES: permanent bounce for <$address> has no user: $reason");
+ Bugzilla->audit("permanent bounce for <$address> has no user: $reason");
}
}
}
else {
- warn "SES: Unsupported bounce type: $type\n";
+ WARN("Unsupported bounce type: $type\n");
}
- respond(200 => 'OK');
+ respond( 200 => 'OK' );
}
sub process_complaint {
+
# email notification to bugzilla admin
my ($notification) = @_;
- my $template = Bugzilla->template_inner();
- my $json = JSON::XS->new->pretty->utf8->canonical;
+ my $template = Bugzilla->template_inner();
+ my $json = JSON::MaybeXS->new(
+ pretty => 1,
+ utf8 => 1,
+ canonical => 1,
+ );
- foreach my $recipient (@{ $notification->{complaint}->{complainedRecipients} }) {
- my $reason = $notification->{complaint}->{complaintFeedbackType} // 'unknown';
+ foreach my $recipient ( @{ $notification->{complaint}->{complainedRecipients} } ) {
+ my $reason = $notification->{complaint}->{complaintFeedbackType} // 'unknown';
my $address = $recipient->{emailAddress};
- Bugzilla->audit("SES: complaint for <$address> for '$reason'");
+ Bugzilla->audit("complaint for <$address> for '$reason'");
my $vars = {
email => $address,
- user => Bugzilla::User->new({ name => $address, cache => 1 }),
+ user => Bugzilla::User->new( { name => $address, cache => 1 } ),
reason => $reason,
notification => $json->encode($notification),
};
my $message;
- $template->process('email/ses-complaint.txt.tmpl', $vars, \$message)
+ $template->process( 'email/ses-complaint.txt.tmpl', $vars, \$message )
|| die $template->error();
MessageToMTA($message);
}
- respond(200 => 'OK');
+ respond( 200 => 'OK' );
}
sub respond {
- my ($code, $message) = @_;
- print Bugzilla->cgi->header(
- -status => "$code $message",
- );
+ my ( $code, $message ) = @_;
+ print Bugzilla->cgi->header( -status => "$code $message" );
+
# apache will generate non-200 response pages for us
say html_quote($message) if $code == 200;
}
@@ -176,17 +187,17 @@ sub respond {
sub decode_json_wrapper {
my ($json) = @_;
my $result;
- if (!defined $json) {
- warn 'SES: Missing JSON from ' . remote_ip() . "\n";
- respond(400 => 'Bad Request');
+ if ( !defined $json ) {
+ WARN( 'Missing JSON from ' . remote_ip() );
+ respond( 400 => 'Bad Request' );
return undef;
}
my $ok = try {
$result = decode_json($json);
}
catch {
- warn 'SES: Malformed JSON from ' . remote_ip() . "\n";
- respond(400 => 'Bad Request');
+ WARN( 'Malformed JSON from ' . remote_ip() );
+ respond( 400 => 'Bad Request' );
return undef;
};
return $ok ? $result : undef;
@@ -195,9 +206,9 @@ sub decode_json_wrapper {
sub ua {
my $ua = LWP::UserAgent->new();
$ua->timeout(10);
- $ua->protocols_allowed(['http', 'https']);
- if (my $proxy_url = Bugzilla->params->{'proxy_url'}) {
- $ua->proxy(['http', 'https'], $proxy_url);
+ $ua->protocols_allowed( [ 'http', 'https' ] );
+ if ( my $proxy_url = Bugzilla->params->{'proxy_url'} ) {
+ $ua->proxy( [ 'http', 'https' ], $proxy_url );
}
else {
$ua->env_proxy;
diff --git a/template/en/default/account/auth/login.html.tmpl b/template/en/default/account/auth/login.html.tmpl
index 160fad43b..8cf5e85ef 100644
--- a/template/en/default/account/auth/login.html.tmpl
+++ b/template/en/default/account/auth/login.html.tmpl
@@ -42,7 +42,7 @@
</p>
<div id="login" class="login-form">
- <form name="login" action="[% target FILTER html %]" method="POST"
+ <form name="login" action="[% urlbase FILTER html %][% target FILTER uri FILTER html %]" method="POST"
[%- IF Bugzilla.cgi.param("data") %] enctype="multipart/form-data"[% END %]>
<div class="field-login">
<label for="Bugzilla_login">Email Address:</label>
diff --git a/template/en/default/account/prefs/mfa.html.tmpl b/template/en/default/account/prefs/mfa.html.tmpl
index f75320892..973d38432 100644
--- a/template/en/default/account/prefs/mfa.html.tmpl
+++ b/template/en/default/account/prefs/mfa.html.tmpl
@@ -91,7 +91,7 @@
[% END %]
</p>
- [% IF user.mfa && user.in_group("mozilla-employee-confidential") %]
+ [% IF user.mfa %]
<p class="mfa-disable-blurb">
You will need to disable your two-factor authentication in order to change to a different method.
There will be a small amount of time when your account will not be as secure, so you may use the
@@ -178,7 +178,7 @@
or <a href="https://freeotp.github.io/" target="_blank" rel="noopener noreferrer">Red Hat FreeOTP</a>),
</blockquote>
- [% IF Param("duo_host") && user.in_group("mozilla-employee-confidential") %]
+ [% IF Param("duo_host") %]
<button type="button" id="mfa-select-duo">Duo Security</button><br>
<blockquote>
Requires a <a href="https://mana.mozilla.org/wiki/display/SD/DuoSecurity" target="_blank" rel="noopener noreferrer">Duo Security</a>
@@ -245,7 +245,7 @@
</div>
- [% IF Param("duo_host") && user.in_group("mozilla-employee-confidential") %]
+ [% IF Param("duo_host") %]
[%# enable - duo %]
<div id="mfa-enable-duo" style="display:none">
diff --git a/template/en/default/admin/admin.html.tmpl b/template/en/default/admin/admin.html.tmpl
index 86bd8b973..62a246ceb 100644
--- a/template/en/default/admin/admin.html.tmpl
+++ b/template/en/default/admin/admin.html.tmpl
@@ -127,7 +127,7 @@
and time, and get the result of these queries directly per email. This is a
good way to create reminders and to keep track of the activity in your installation.</dd>
- [% IF Param('use_mailer_queue') %]
+ [% IF Bugzilla.localconfig.param_override.use_mailer_queue OR Param('use_mailer_queue') %]
[% class = user.in_group('admin') ? "" : "forbidden" %]
<dt id="view_job_queue" class="[% class %]"><a href="view_job_queue.cgi">Job Queue</a></dt>
<dd class="[% class %]">View the queue of undelivered/deferred jobs/emails.</dd>
diff --git a/template/en/default/bug/activity/table.html.tmpl b/template/en/default/bug/activity/table.html.tmpl
index 50193f894..101e43546 100644
--- a/template/en/default/bug/activity/table.html.tmpl
+++ b/template/en/default/bug/activity/table.html.tmpl
@@ -107,6 +107,7 @@
change.fieldname == 'reporter' ||
change.fieldname == 'qa_contact' ||
change.fieldname == 'cc' ||
+ change.fieldname == 'bug_mentor' ||
change.fieldname == 'flagtypes.name' %]
[% display_value(change.fieldname, change_type) FILTER email FILTER html %]
[% ELSE %]
diff --git a/template/en/default/email/new-api-key.txt.tmpl b/template/en/default/email/new-api-key.txt.tmpl
index 4a03fe800..aed904def 100644
--- a/template/en/default/email/new-api-key.txt.tmpl
+++ b/template/en/default/email/new-api-key.txt.tmpl
@@ -26,9 +26,17 @@ or update the key at the following URL:
[%+ urlbase %]userprefs.cgi?tab=apikey
+[% IF new_key.app_id == Param('mozreview_app_id') %]
+This API key was automatically created by MozReview. If you did not recently log in to
+MozReview, please disable the key at the above URL, and change your password immediately.
+[% ELSIF new_key.app_id == Param('phabricator_app_id') %]
+This API key was automatically created by Mozilla's Phabricator instance. If you did not recently
+log in to Phabricator, please disable the key at the above URL, and change your password immediately.
+[% ELSE %]
IMPORTANT: If you did not request a new key, your [% terms.Bugzilla %] account
may have been compromised. In this case, please disable the key at the above
URL, and change your password immediately.
+[% END %]
For security reasons, we have not included your new key in this e-mail.
diff --git a/template/en/default/setup/strings.txt.pl b/template/en/default/setup/strings.txt.pl
index 9de426972..5fc860519 100644
--- a/template/en/default/setup/strings.txt.pl
+++ b/template/en/default/setup/strings.txt.pl
@@ -269,6 +269,12 @@ END
This is the max amount of unshared memory the apache process is allowed to use
before Apache::SizeLimit kills it. This is only applicable when run under mod_perl.
EOT
+ localconfig_shadowdb_user => <<EOT,
+The username used to authenticate to the shadow db.
+EOT
+ localconfig_shadowdb_pass => <<EOT,
+The password used to authenticate to the shadow db.
+EOT
max_allowed_packet => <<EOT,
WARNING: You need to set the max_allowed_packet parameter in your MySQL
configuration to at least ##needed##. Currently it is set to ##current##.
diff --git a/vagrant_support/bmo-generate-data.j2 b/vagrant_support/bmo-generate-data.j2
index 79798ba4d..6ad9303d6 100644
--- a/vagrant_support/bmo-generate-data.j2
+++ b/vagrant_support/bmo-generate-data.j2
@@ -2,3 +2,4 @@
cd /vagrant
perl scripts/generate_bmo_data.pl 'vagrant@{{ WEB_HOSTNAME }}'
+perl scripts/update_params.pl 'mail_delivery_method' 'Sendmail'
diff --git a/vagrant_support/checksetup_answers.j2 b/vagrant_support/checksetup_answers.j2
index 683a28a6f..441a4d41d 100644
--- a/vagrant_support/checksetup_answers.j2
+++ b/vagrant_support/checksetup_answers.j2
@@ -23,6 +23,7 @@ $answer{'user_verify_class'} = 'GitHubAuth,DB';
$answer{'urlbase'} = "http://{{WEB_HOSTNAME}}/";
$answer{'memcached_namespace'} = 'bmo:';
$answer{'memcached_servers'} = '127.0.0.1:11211';
+$answer{'mail_delivery_method'} = 'Sendmail';
$answer{'use_mailer_queue'} = 1;
$answer{'useclassification'} = 1;
$answer{'usebugaliases'} = 1;