diff options
Diffstat (limited to 'extensions/Push/lib')
23 files changed, 1977 insertions, 1991 deletions
diff --git a/extensions/Push/lib/Admin.pm b/extensions/Push/lib/Admin.pm index 9df2bddcb..d86d30a62 100644 --- a/extensions/Push/lib/Admin.pm +++ b/extensions/Push/lib/Admin.pm @@ -19,110 +19,109 @@ use Bugzilla::Util qw(trim detaint_natural trick_taint); use base qw(Exporter); our @EXPORT = qw( - admin_config - admin_queues - admin_log + admin_config + admin_queues + admin_log ); sub admin_config { - my ($vars) = @_; - my $push = Bugzilla->push_ext; - my $input = Bugzilla->input_params; - - if ($input->{save}) { - my $token = $input->{token}; - check_hash_token($token, ['push_config']); - my $dbh = Bugzilla->dbh; - $dbh->bz_start_transaction(); - _update_config_from_form('global', $push->config); - foreach my $connector ($push->connectors->list) { - _update_config_from_form($connector->name, $connector->config); - } - $push->set_config_last_modified(); - $dbh->bz_commit_transaction(); - $vars->{message} = 'push_config_updated'; + my ($vars) = @_; + my $push = Bugzilla->push_ext; + my $input = Bugzilla->input_params; + + if ($input->{save}) { + my $token = $input->{token}; + check_hash_token($token, ['push_config']); + my $dbh = Bugzilla->dbh; + $dbh->bz_start_transaction(); + _update_config_from_form('global', $push->config); + foreach my $connector ($push->connectors->list) { + _update_config_from_form($connector->name, $connector->config); } + $push->set_config_last_modified(); + $dbh->bz_commit_transaction(); + $vars->{message} = 'push_config_updated'; + } - $vars->{push} = $push; - $vars->{connectors} = $push->connectors; + $vars->{push} = $push; + $vars->{connectors} = $push->connectors; } sub _update_config_from_form { - my ($name, $config) = @_; - my $input = Bugzilla->input_params; - - # read values from form - my $values = {}; - foreach my $option ($config->options) { - my $option_name = $option->{name}; - $values->{$option_name} = trim($input->{$name . ".$option_name"}); + my ($name, $config) = @_; + my $input = Bugzilla->input_params; + + # read values from form + my $values = {}; + foreach my $option ($config->options) { + my $option_name = $option->{name}; + $values->{$option_name} = trim($input->{$name . ".$option_name"}); + } + + # validate + if ($values->{enabled} eq 'Enabled') { + eval { $config->validate($values); }; + if ($@) { + ThrowUserError('push_error', {error_message => clean_error($@)}); } + } + + # update + foreach my $option ($config->options) { + my $option_name = $option->{name}; + trick_taint($values->{$option_name}); + $config->{$option_name} = $values->{$option_name}; + } + $config->update(); +} - # validate - if ($values->{enabled} eq 'Enabled') { - eval { - $config->validate($values); - }; - if ($@) { - ThrowUserError('push_error', { error_message => clean_error($@) }); - } - } +sub admin_queues { + my ($vars, $page) = @_; + my $push = Bugzilla->push_ext; + my $input = Bugzilla->input_params; - # update - foreach my $option ($config->options) { - my $option_name = $option->{name}; - trick_taint($values->{$option_name}); - $config->{$option_name} = $values->{$option_name}; + if ($page eq 'push_queues.html') { + $vars->{push} = $push; + + } + elsif ($page eq 'push_queues_view.html') { + my $queue; + if ($input->{connector}) { + my $connector = $push->connectors->by_name($input->{connector}) + || ThrowUserError('push_error', {error_message => 'Invalid connector'}); + $queue = $connector->backlog; } - $config->update(); -} + else { + $queue = $push->queue; + } + $vars->{queue} = $queue; -sub admin_queues { - my ($vars, $page) = @_; - my $push = Bugzilla->push_ext; - my $input = Bugzilla->input_params; - - if ($page eq 'push_queues.html') { - $vars->{push} = $push; - - } elsif ($page eq 'push_queues_view.html') { - my $queue; - if ($input->{connector}) { - my $connector = $push->connectors->by_name($input->{connector}) - || ThrowUserError('push_error', { error_message => 'Invalid connector' }); - $queue = $connector->backlog; - } else { - $queue = $push->queue; - } - $vars->{queue} = $queue; - - my $id = $input->{message} || 0; - detaint_natural($id) - || ThrowUserError('push_error', { error_message => 'Invalid message ID' }); - my $message = $queue->by_id($id) - || ThrowUserError('push_error', { error_message => 'Invalid message ID' }); - - if ($input->{delete}) { - my $token = $input->{token}; - check_hash_token($token, ['deleteMessage']); - $message->remove_from_db(); - $vars->{message} = 'push_message_deleted'; - - } else { - $vars->{message_obj} = $message; - eval { - $vars->{json} = to_json($message->payload_decoded, 1); - }; - } + my $id = $input->{message} || 0; + detaint_natural($id) + || ThrowUserError('push_error', {error_message => 'Invalid message ID'}); + my $message = $queue->by_id($id) + || ThrowUserError('push_error', {error_message => 'Invalid message ID'}); + + if ($input->{delete}) { + my $token = $input->{token}; + check_hash_token($token, ['deleteMessage']); + $message->remove_from_db(); + $vars->{message} = 'push_message_deleted'; + + } + else { + $vars->{message_obj} = $message; + eval { $vars->{json} = to_json($message->payload_decoded, 1); }; } + } } sub admin_log { - my ($vars) = @_; - my $push = Bugzilla->push_ext; - my $input = Bugzilla->input_params; + my ($vars) = @_; + my $push = Bugzilla->push_ext; + my $input = Bugzilla->input_params; - $vars->{push} = $push; + $vars->{push} = $push; } 1; diff --git a/extensions/Push/lib/BacklogMessage.pm b/extensions/Push/lib/BacklogMessage.pm index 28b17bae3..942eb77eb 100644 --- a/extensions/Push/lib/BacklogMessage.pm +++ b/extensions/Push/lib/BacklogMessage.pm @@ -28,31 +28,31 @@ use Encode; # initialisation # -use constant DB_TABLE => 'push_backlog'; +use constant DB_TABLE => 'push_backlog'; use constant DB_COLUMNS => qw( - id - message_id - push_ts - payload - change_set - routing_key - connector - attempt_ts - attempts - last_error + id + message_id + push_ts + payload + change_set + routing_key + connector + attempt_ts + attempts + last_error ); use constant UPDATE_COLUMNS => qw( - attempt_ts - attempts - last_error + attempt_ts + attempts + last_error ); use constant LIST_ORDER => 'push_ts'; use constant VALIDATORS => { - payload => \&_check_payload, - change_set => \&_check_change_set, - routing_key => \&_check_routing_key, - connector => \&_check_connector, - attempts => \&_check_attempts, + payload => \&_check_payload, + change_set => \&_check_change_set, + routing_key => \&_check_routing_key, + connector => \&_check_connector, + attempts => \&_check_attempts, }; # @@ -60,46 +60,46 @@ use constant VALIDATORS => { # sub create_from_message { - my ($class, $message, $connector) = @_; - my $self = $class->create({ - message_id => $message->id, - push_ts => $message->push_ts, - payload => $message->payload, - change_set => $message->change_set, - routing_key => $message->routing_key, - connector => $connector->name, - attempt_ts => undef, - attempts => 0, - last_error => undef, - }); - return $self; + my ($class, $message, $connector) = @_; + my $self = $class->create({ + message_id => $message->id, + push_ts => $message->push_ts, + payload => $message->payload, + change_set => $message->change_set, + routing_key => $message->routing_key, + connector => $connector->name, + attempt_ts => undef, + attempts => 0, + last_error => undef, + }); + return $self; } # # accessors # -sub message_id { return $_[0]->{'message_id'} } -sub push_ts { return $_[0]->{'push_ts'}; } -sub payload { return $_[0]->{'payload'}; } -sub change_set { return $_[0]->{'change_set'}; } +sub message_id { return $_[0]->{'message_id'} } +sub push_ts { return $_[0]->{'push_ts'}; } +sub payload { return $_[0]->{'payload'}; } +sub change_set { return $_[0]->{'change_set'}; } sub routing_key { return $_[0]->{'routing_key'}; } -sub connector { return $_[0]->{'connector'}; } -sub attempt_ts { return $_[0]->{'attempt_ts'}; } -sub attempts { return $_[0]->{'attempts'}; } -sub last_error { return $_[0]->{'last_error'}; } +sub connector { return $_[0]->{'connector'}; } +sub attempt_ts { return $_[0]->{'attempt_ts'}; } +sub attempts { return $_[0]->{'attempts'}; } +sub last_error { return $_[0]->{'last_error'}; } sub payload_decoded { - my ($self) = @_; - return from_json($self->{'payload'}); + my ($self) = @_; + return from_json($self->{'payload'}); } sub attempt_time { - my ($self) = @_; - if (!exists $self->{'attempt_time'}) { - $self->{'attempt_time'} = datetime_from($self->attempt_ts)->epoch; - } - return $self->{'attempt_time'}; + my ($self) = @_; + if (!exists $self->{'attempt_time'}) { + $self->{'attempt_time'} = datetime_from($self->attempt_ts)->epoch; + } + return $self->{'attempt_time'}; } # @@ -107,11 +107,11 @@ sub attempt_time { # sub inc_attempts { - my ($self, $error) = @_; - $self->{attempt_ts} = Bugzilla->dbh->selectrow_array('SELECT NOW()'); - $self->{attempts} = $self->{attempts} + 1; - $self->{last_error} = $error; - $self->update; + my ($self, $error) = @_; + $self->{attempt_ts} = Bugzilla->dbh->selectrow_array('SELECT NOW()'); + $self->{attempts} = $self->{attempts} + 1; + $self->{last_error} = $error; + $self->update; } # @@ -119,32 +119,35 @@ sub inc_attempts { # sub _check_payload { - my ($invocant, $value) = @_; - length($value) || ThrowCodeError('push_invalid_payload'); - return $value; + my ($invocant, $value) = @_; + length($value) || ThrowCodeError('push_invalid_payload'); + return $value; } sub _check_change_set { - my ($invocant, $value) = @_; - (defined($value) && length($value)) || ThrowCodeError('push_invalid_change_set'); - return $value; + my ($invocant, $value) = @_; + (defined($value) && length($value)) + || ThrowCodeError('push_invalid_change_set'); + return $value; } sub _check_routing_key { - my ($invocant, $value) = @_; - (defined($value) && length($value)) || ThrowCodeError('push_invalid_routing_key'); - return $value; + my ($invocant, $value) = @_; + (defined($value) && length($value)) + || ThrowCodeError('push_invalid_routing_key'); + return $value; } sub _check_connector { - my ($invocant, $value) = @_; - Bugzilla->push_ext->connectors->exists($value) || ThrowCodeError('push_invalid_connector'); - return $value; + my ($invocant, $value) = @_; + Bugzilla->push_ext->connectors->exists($value) + || ThrowCodeError('push_invalid_connector'); + return $value; } sub _check_attempts { - my ($invocant, $value) = @_; - return $value || 0; + my ($invocant, $value) = @_; + return $value || 0; } 1; diff --git a/extensions/Push/lib/BacklogQueue.pm b/extensions/Push/lib/BacklogQueue.pm index a7200c688..17d0a188f 100644 --- a/extensions/Push/lib/BacklogQueue.pm +++ b/extensions/Push/lib/BacklogQueue.pm @@ -15,74 +15,67 @@ use Bugzilla; use Bugzilla::Extension::Push::BacklogMessage; sub new { - my ($class, $connector) = @_; - my $self = {}; - bless($self, $class); - $self->{connector} = $connector; - return $self; + my ($class, $connector) = @_; + my $self = {}; + bless($self, $class); + $self->{connector} = $connector; + return $self; } sub count { - my ($self) = @_; - my $dbh = Bugzilla->dbh; - return $dbh->selectrow_array(" + my ($self) = @_; + my $dbh = Bugzilla->dbh; + return $dbh->selectrow_array(" SELECT COUNT(*) FROM push_backlog - WHERE connector = ?", - undef, - $self->{connector}); + WHERE connector = ?", undef, $self->{connector}); } sub oldest { - my ($self) = @_; - my @messages = $self->list( - limit => 1, - filter => 'AND ((next_attempt_ts IS NULL) OR (next_attempt_ts <= NOW()))', - ); - return scalar(@messages) ? $messages[0] : undef; + my ($self) = @_; + my @messages = $self->list( + limit => 1, + filter => 'AND ((next_attempt_ts IS NULL) OR (next_attempt_ts <= NOW()))', + ); + return scalar(@messages) ? $messages[0] : undef; } sub by_id { - my ($self, $id) = @_; - my @messages = $self->list( - limit => 1, - filter => "AND (log.id = $id)", - ); - return scalar(@messages) ? $messages[0] : undef; + my ($self, $id) = @_; + my @messages = $self->list(limit => 1, filter => "AND (log.id = $id)",); + return scalar(@messages) ? $messages[0] : undef; } sub list { - my ($self, %args) = @_; - $args{limit} ||= 10; - $args{filter} ||= ''; - my @result; - my $dbh = Bugzilla->dbh; + my ($self, %args) = @_; + $args{limit} ||= 10; + $args{filter} ||= ''; + my @result; + my $dbh = Bugzilla->dbh; - my $filter_sql = $args{filter} || ''; - my $sth = $dbh->prepare(" + my $filter_sql = $args{filter} || ''; + my $sth = $dbh->prepare(" SELECT log.id, message_id, push_ts, payload, change_set, routing_key, attempt_ts, log.attempts FROM push_backlog log LEFT JOIN push_backoff off ON off.connector = log.connector - WHERE log.connector = ? ". - $args{filter} . " - ORDER BY push_ts " . - $dbh->sql_limit($args{limit}) - ); - $sth->execute($self->{connector}); - while (my $row = $sth->fetchrow_hashref()) { - push @result, Bugzilla::Extension::Push::BacklogMessage->new({ - id => $row->{id}, - message_id => $row->{message_id}, - push_ts => $row->{push_ts}, - payload => $row->{payload}, - change_set => $row->{change_set}, - routing_key => $row->{routing_key}, - connector => $self->{connector}, - attempt_ts => $row->{attempt_ts}, - attempts => $row->{attempts}, - }); - } - return @result; + WHERE log.connector = ? " . $args{filter} . " + ORDER BY push_ts " . $dbh->sql_limit($args{limit})); + $sth->execute($self->{connector}); + while (my $row = $sth->fetchrow_hashref()) { + push @result, + Bugzilla::Extension::Push::BacklogMessage->new({ + id => $row->{id}, + message_id => $row->{message_id}, + push_ts => $row->{push_ts}, + payload => $row->{payload}, + change_set => $row->{change_set}, + routing_key => $row->{routing_key}, + connector => $self->{connector}, + attempt_ts => $row->{attempt_ts}, + attempts => $row->{attempts}, + }); + } + return @result; } # @@ -90,39 +83,40 @@ sub list { # sub backoff { - my ($self) = @_; - if (!$self->{backoff}) { - my $ra = Bugzilla::Extension::Push::Backoff->match({ - connector => $self->{connector} + my ($self) = @_; + if (!$self->{backoff}) { + my $ra + = Bugzilla::Extension::Push::Backoff->match({connector => $self->{connector} + }); + if (@$ra) { + $self->{backoff} = $ra->[0]; + } + else { + $self->{backoff} + = Bugzilla::Extension::Push::Backoff->create({connector => $self->{connector} }); - if (@$ra) { - $self->{backoff} = $ra->[0]; - } else { - $self->{backoff} = Bugzilla::Extension::Push::Backoff->create({ - connector => $self->{connector} - }); - } } - return $self->{backoff}; + } + return $self->{backoff}; } sub reset_backoff { - my ($self) = @_; - my $backoff = $self->backoff; - $backoff->reset(); - $backoff->update(); + my ($self) = @_; + my $backoff = $self->backoff; + $backoff->reset(); + $backoff->update(); } sub inc_backoff { - my ($self) = @_; - my $backoff = $self->backoff; - $backoff->inc(); - $backoff->update(); + my ($self) = @_; + my $backoff = $self->backoff; + $backoff->inc(); + $backoff->update(); } sub connector { - my ($self) = @_; - return $self->{connector}; + my ($self) = @_; + return $self->{connector}; } 1; diff --git a/extensions/Push/lib/Backoff.pm b/extensions/Push/lib/Backoff.pm index 0436cdf14..070adfc29 100644 --- a/extensions/Push/lib/Backoff.pm +++ b/extensions/Push/lib/Backoff.pm @@ -26,21 +26,21 @@ use Bugzilla::Util; # initialisation # -use constant DB_TABLE => 'push_backoff'; +use constant DB_TABLE => 'push_backoff'; use constant DB_COLUMNS => qw( - id - connector - next_attempt_ts - attempts + id + connector + next_attempt_ts + attempts ); use constant UPDATE_COLUMNS => qw( - next_attempt_ts - attempts + next_attempt_ts + attempts ); use constant VALIDATORS => { - connector => \&_check_connector, - next_attempt_ts => \&_check_next_attempt_ts, - attempts => \&_check_attempts, + connector => \&_check_connector, + next_attempt_ts => \&_check_next_attempt_ts, + attempts => \&_check_attempts, }; use constant LIST_ORDER => 'next_attempt_ts'; @@ -48,16 +48,16 @@ use constant LIST_ORDER => 'next_attempt_ts'; # accessors # -sub connector { return $_[0]->{'connector'}; } +sub connector { return $_[0]->{'connector'}; } sub next_attempt_ts { return $_[0]->{'next_attempt_ts'}; } -sub attempts { return $_[0]->{'attempts'}; } +sub attempts { return $_[0]->{'attempts'}; } sub next_attempt_time { - my ($self) = @_; - if (!exists $self->{'next_attempt_time'}) { - $self->{'next_attempt_time'} = datetime_from($self->next_attempt_ts)->epoch; - } - return $self->{'next_attempt_time'}; + my ($self) = @_; + if (!exists $self->{'next_attempt_time'}) { + $self->{'next_attempt_time'} = datetime_from($self->next_attempt_ts)->epoch; + } + return $self->{'next_attempt_time'}; } # @@ -65,25 +65,26 @@ sub next_attempt_time { # sub reset { - my ($self) = @_; - $self->{next_attempt_ts} = Bugzilla->dbh->selectrow_array('SELECT NOW()'); - $self->{attempts} = 0; - INFO( sprintf 'resetting backoff for %s', $self->connector ); + my ($self) = @_; + $self->{next_attempt_ts} = Bugzilla->dbh->selectrow_array('SELECT NOW()'); + $self->{attempts} = 0; + INFO(sprintf 'resetting backoff for %s', $self->connector); } sub inc { - my ($self) = @_; - my $dbh = Bugzilla->dbh; - - my $attempts = $self->attempts + 1; - my $seconds = $attempts <= 4 ? 5 ** $attempts : 15 * 60; - my ($date) = $dbh->selectrow_array("SELECT " . $dbh->sql_date_math('NOW()', '+', $seconds, 'SECOND')); - - $self->{next_attempt_ts} = $date; - $self->{attempts} = $attempts; - INFO( - sprintf 'setting next attempt for %s to %s (attempt %s)', $self->connector, $date, $attempts - ); + my ($self) = @_; + my $dbh = Bugzilla->dbh; + + my $attempts = $self->attempts + 1; + my $seconds = $attempts <= 4 ? 5**$attempts : 15 * 60; + my ($date) + = $dbh->selectrow_array( + "SELECT " . $dbh->sql_date_math('NOW()', '+', $seconds, 'SECOND')); + + $self->{next_attempt_ts} = $date; + $self->{attempts} = $attempts; + INFO(sprintf 'setting next attempt for %s to %s (attempt %s)', + $self->connector, $date, $attempts); } # @@ -91,19 +92,20 @@ sub inc { # sub _check_connector { - my ($invocant, $value) = @_; - Bugzilla->push_ext->connectors->exists($value) || ThrowCodeError('push_invalid_connector'); - return $value; + my ($invocant, $value) = @_; + Bugzilla->push_ext->connectors->exists($value) + || ThrowCodeError('push_invalid_connector'); + return $value; } sub _check_next_attempt_ts { - my ($invocant, $value) = @_; - return $value || Bugzilla->dbh->selectrow_array('SELECT NOW()'); + my ($invocant, $value) = @_; + return $value || Bugzilla->dbh->selectrow_array('SELECT NOW()'); } sub _check_attempts { - my ($invocant, $value) = @_; - return $value || 0; + my ($invocant, $value) = @_; + return $value || 0; } 1; diff --git a/extensions/Push/lib/Config.pm b/extensions/Push/lib/Config.pm index 2db95b972..bb0d523ad 100644 --- a/extensions/Push/lib/Config.pm +++ b/extensions/Push/lib/Config.pm @@ -17,199 +17,201 @@ use Bugzilla::Extension::Push::Option; use Crypt::CBC; sub new { - my ($class, $name, @options) = @_; - my $self = { - _name => $name - }; - bless($self, $class); - - $self->{_options} = [@options]; - unshift @{$self->{_options}}, { - name => 'enabled', - label => 'Status', - help => '', - type => 'select', - values => [ 'Enabled', 'Disabled' ], - default => 'Disabled', + my ($class, $name, @options) = @_; + my $self = {_name => $name}; + bless($self, $class); + + $self->{_options} = [@options]; + unshift @{$self->{_options}}, + { + name => 'enabled', + label => 'Status', + help => '', + type => 'select', + values => ['Enabled', 'Disabled'], + default => 'Disabled', }; - return $self; + return $self; } sub options { - my ($self) = @_; - return @{$self->{_options}}; + my ($self) = @_; + return @{$self->{_options}}; } sub option { - my ($self, $name) = @_; - foreach my $option ($self->options) { - return $option if $option->{name} eq $name; - } - return undef; + my ($self, $name) = @_; + foreach my $option ($self->options) { + return $option if $option->{name} eq $name; + } + return undef; } sub load { - my ($self) = @_; - my $config = {}; - - # prime $config with defaults - foreach my $rh ($self->options) { - $config->{$rh->{name}} = $rh->{default}; + my ($self) = @_; + my $config = {}; + + # prime $config with defaults + foreach my $rh ($self->options) { + $config->{$rh->{name}} = $rh->{default}; + } + + # override defaults with values from database + my $options + = Bugzilla::Extension::Push::Option->match({connector => $self->{_name},}); + foreach my $option (@$options) { + my $option_config = $self->option($option->name) || next; + if ($option_config->{type} eq 'password') { + $config->{$option->name} = $self->_decrypt($option->value); } - - # override defaults with values from database - my $options = Bugzilla::Extension::Push::Option->match({ - connector => $self->{_name}, - }); - foreach my $option (@$options) { - my $option_config = $self->option($option->name) - || next; - if ($option_config->{type} eq 'password') { - $config->{$option->name} = $self->_decrypt($option->value); - } else { - $config->{$option->name} = $option->value; - } + else { + $config->{$option->name} = $option->value; } + } - # validate when running from the daemon - if (Bugzilla->push_ext->is_daemon) { - $self->_validate_config($config); - } - - # done, update self - foreach my $name (keys %$config) { - my $value = $self->option($name)->{type} eq 'password' ? '********' : $config->{$name}; - TRACE( sprintf "%s: set %s=%s\n", $self->{_name}, $name, $value || '' ); - $self->{$name} = $config->{$name}; - } + # validate when running from the daemon + if (Bugzilla->push_ext->is_daemon) { + $self->_validate_config($config); + } + + # done, update self + foreach my $name (keys %$config) { + my $value + = $self->option($name)->{type} eq 'password' ? '********' : $config->{$name}; + TRACE(sprintf "%s: set %s=%s\n", $self->{_name}, $name, $value || ''); + $self->{$name} = $config->{$name}; + } } sub validate { - my ($self, $config) = @_; - $self->_validate_mandatory($config); - $self->_validate_config($config); + my ($self, $config) = @_; + $self->_validate_mandatory($config); + $self->_validate_config($config); } sub update { - my ($self) = @_; - - my @valid_options = map { $_->{name} } $self->options; - - my %options; - my $options_list = Bugzilla::Extension::Push::Option->match({ - connector => $self->{_name}, - }); - foreach my $option (@$options_list) { - $options{$option->name} = $option; - } - - # delete options which are no longer valid - foreach my $name (keys %options) { - if (!grep { $_ eq $name } @valid_options) { - $options{$name}->remove_from_db(); - delete $options{$name}; - } + my ($self) = @_; + + my @valid_options = map { $_->{name} } $self->options; + + my %options; + my $options_list + = Bugzilla::Extension::Push::Option->match({connector => $self->{_name},}); + foreach my $option (@$options_list) { + $options{$option->name} = $option; + } + + # delete options which are no longer valid + foreach my $name (keys %options) { + if (!grep { $_ eq $name } @valid_options) { + $options{$name}->remove_from_db(); + delete $options{$name}; } + } - # update options - foreach my $name (keys %options) { - my $option = $options{$name}; - if ($self->option($name)->{type} eq 'password') { - $option->set_value($self->_encrypt($self->{$name})); - } else { - $option->set_value($self->{$name}); - } - $option->update(); + # update options + foreach my $name (keys %options) { + my $option = $options{$name}; + if ($self->option($name)->{type} eq 'password') { + $option->set_value($self->_encrypt($self->{$name})); } - - # add missing options - foreach my $name (@valid_options) { - next if exists $options{$name}; - Bugzilla::Extension::Push::Option->create({ - connector => $self->{_name}, - option_name => $name, - option_value => $self->{$name}, - }); + else { + $option->set_value($self->{$name}); } + $option->update(); + } + + # add missing options + foreach my $name (@valid_options) { + next if exists $options{$name}; + Bugzilla::Extension::Push::Option->create({ + connector => $self->{_name}, + option_name => $name, + option_value => $self->{$name}, + }); + } } sub _remove_invalid_options { - my ($self, $config) = @_; - my @names; - foreach my $rh ($self->options) { - push @names, $rh->{name}; - } - foreach my $name (keys %$config) { - if ($name =~ /^_/ || !grep { $_ eq $name } @names) { - delete $config->{$name}; - } + my ($self, $config) = @_; + my @names; + foreach my $rh ($self->options) { + push @names, $rh->{name}; + } + foreach my $name (keys %$config) { + if ($name =~ /^_/ || !grep { $_ eq $name } @names) { + delete $config->{$name}; } + } } sub _validate_mandatory { - my ($self, $config) = @_; - $self->_remove_invalid_options($config); - - my @missing; - foreach my $option ($self->options) { - next unless $option->{required}; - my $name = $option->{name}; - if (!exists $config->{$name} || !defined($config->{$name}) || $config->{$name} eq '') { - push @missing, $option; - } + my ($self, $config) = @_; + $self->_remove_invalid_options($config); + + my @missing; + foreach my $option ($self->options) { + next unless $option->{required}; + my $name = $option->{name}; + if ( !exists $config->{$name} + || !defined($config->{$name}) + || $config->{$name} eq '') + { + push @missing, $option; + } + } + if (@missing) { + my $connector = $self->{_name}; + @missing = map { $_->{label} } @missing; + if (scalar @missing == 1) { + die "The option '$missing[0]' for the connector '$connector' is mandatory\n"; } - if (@missing) { - my $connector = $self->{_name}; - @missing = map { $_->{label} } @missing; - if (scalar @missing == 1) { - die "The option '$missing[0]' for the connector '$connector' is mandatory\n"; - } else { - die "The following options for the connector '$connector' are mandatory:\n " - . join("\n ", @missing) . "\n"; - } + else { + die "The following options for the connector '$connector' are mandatory:\n " + . join("\n ", @missing) . "\n"; } + } } sub _validate_config { - my ($self, $config) = @_; - $self->_remove_invalid_options($config); - - my @errors; - foreach my $option ($self->options) { - my $name = $option->{name}; - next unless exists $config->{$name} && exists $option->{validate}; - eval { - $option->{validate}->($config->{$name}, $config); - }; - push @errors, $@ if $@; - } - die join("\n", @errors) if @errors; - - if ($self->{_name} ne 'global') { - my $class = 'Bugzilla::Extension::Push::Connector::' . $self->{_name}; - $class->options_validate($config); - } + my ($self, $config) = @_; + $self->_remove_invalid_options($config); + + my @errors; + foreach my $option ($self->options) { + my $name = $option->{name}; + next unless exists $config->{$name} && exists $option->{validate}; + eval { $option->{validate}->($config->{$name}, $config); }; + push @errors, $@ if $@; + } + die join("\n", @errors) if @errors; + + if ($self->{_name} ne 'global') { + my $class = 'Bugzilla::Extension::Push::Connector::' . $self->{_name}; + $class->options_validate($config); + } } sub _cipher { - my ($self) = @_; - $self->{_cipher} ||= Crypt::CBC->new( - -key => Bugzilla->localconfig->{'site_wide_secret'}, - -cipher => 'DES_EDE3'); - return $self->{_cipher}; + my ($self) = @_; + $self->{_cipher} ||= Crypt::CBC->new( + -key => Bugzilla->localconfig->{'site_wide_secret'}, + -cipher => 'DES_EDE3' + ); + return $self->{_cipher}; } sub _decrypt { - my ($self, $value) = @_; - my $result; - eval { $result = $self->_cipher->decrypt_hex($value) }; - return $@ ? '' : $result; + my ($self, $value) = @_; + my $result; + eval { $result = $self->_cipher->decrypt_hex($value) }; + return $@ ? '' : $result; } sub _encrypt { - my ($self, $value) = @_; - return $self->_cipher->encrypt_hex($value); + my ($self, $value) = @_; + return $self->_cipher->encrypt_hex($value); } 1; diff --git a/extensions/Push/lib/Connector.disabled/AMQP.pm b/extensions/Push/lib/Connector.disabled/AMQP.pm index 1ba365e21..dda73dade 100644 --- a/extensions/Push/lib/Connector.disabled/AMQP.pm +++ b/extensions/Push/lib/Connector.disabled/AMQP.pm @@ -20,211 +20,197 @@ use Bugzilla::Util qw(generate_random_password); use DateTime; sub init { - my ($self) = @_; - $self->{mq} = 0; - $self->{channel} = 1; - - if ($self->config->{queue}) { - $self->{queue_name} = $self->config->{queue}; - } else { - my $queue_name = Bugzilla->localconfig->{'urlbase'}; - $queue_name =~ s#^https?://##; - $queue_name =~ s#/$#|#; - $queue_name .= generate_random_password(16); - $self->{queue_name} = $queue_name; - } + my ($self) = @_; + $self->{mq} = 0; + $self->{channel} = 1; + + if ($self->config->{queue}) { + $self->{queue_name} = $self->config->{queue}; + } + else { + my $queue_name = Bugzilla->localconfig->{'urlbase'}; + $queue_name =~ s#^https?://##; + $queue_name =~ s#/$#|#; + $queue_name .= generate_random_password(16); + $self->{queue_name} = $queue_name; + } } sub options { - return ( - { - name => 'host', - label => 'AMQP Hostname', - type => 'string', - default => 'localhost', - required => 1, - }, - { - name => 'port', - label => 'AMQP Port', - type => 'string', - default => '5672', - required => 1, - validate => sub { - $_[0] =~ /\D/ && die "Invalid port (must be numeric)\n"; - }, - }, - { - name => 'username', - label => 'Username', - type => 'string', - default => 'guest', - required => 1, - }, - { - name => 'password', - label => 'Password', - type => 'password', - default => 'guest', - required => 1, - }, - { - name => 'vhost', - label => 'Virtual Host', - type => 'string', - default => '/', - required => 1, - }, - { - name => 'exchange', - label => 'Exchange', - type => 'string', - default => '', - required => 1, - }, - { - name => 'queue', - label => 'Queue', - type => 'string', - }, - ); + return ( + { + name => 'host', + label => 'AMQP Hostname', + type => 'string', + default => 'localhost', + required => 1, + }, + { + name => 'port', + label => 'AMQP Port', + type => 'string', + default => '5672', + required => 1, + validate => sub { + $_[0] =~ /\D/ && die "Invalid port (must be numeric)\n"; + }, + }, + { + name => 'username', + label => 'Username', + type => 'string', + default => 'guest', + required => 1, + }, + { + name => 'password', + label => 'Password', + type => 'password', + default => 'guest', + required => 1, + }, + { + name => 'vhost', + label => 'Virtual Host', + type => 'string', + default => '/', + required => 1, + }, + { + name => 'exchange', + label => 'Exchange', + type => 'string', + default => '', + required => 1, + }, + {name => 'queue', label => 'Queue', type => 'string',}, + ); } sub stop { - my ($self) = @_; - if ($self->{mq}) { - Bugzilla->push_ext->logger->debug('AMQP: disconnecting'); - $self->{mq}->disconnect(); - $self->{mq} = 0; - } + my ($self) = @_; + if ($self->{mq}) { + Bugzilla->push_ext->logger->debug('AMQP: disconnecting'); + $self->{mq}->disconnect(); + $self->{mq} = 0; + } } sub _connect { - my ($self) = @_; - my $logger = Bugzilla->push_ext->logger; - my $config = $self->config; - - $self->stop(); - - $logger->debug('AMQP: Connecting to RabbitMQ ' . $config->{host} . ':' . $config->{port}); - require Net::RabbitMQ; - my $mq = Net::RabbitMQ->new(); - $mq->connect( - $config->{host}, - { - port => $config->{port}, - user => $config->{username}, - password => $config->{password}, - } - ); - $self->{mq} = $mq; - - $logger->debug('AMQP: Opening channel ' . $self->{channel}); - $self->{mq}->channel_open($self->{channel}); - - $logger->debug('AMQP: Declaring queue ' . $self->{queue_name}); - $self->{mq}->queue_declare( - $self->{channel}, - $self->{queue_name}, - { - passive => 0, - durable => 1, - exclusive => 0, - auto_delete => 0, - }, - ); + my ($self) = @_; + my $logger = Bugzilla->push_ext->logger; + my $config = $self->config; + + $self->stop(); + + $logger->debug( + 'AMQP: Connecting to RabbitMQ ' . $config->{host} . ':' . $config->{port}); + require Net::RabbitMQ; + my $mq = Net::RabbitMQ->new(); + $mq->connect( + $config->{host}, + { + port => $config->{port}, + user => $config->{username}, + password => $config->{password}, + } + ); + $self->{mq} = $mq; + + $logger->debug('AMQP: Opening channel ' . $self->{channel}); + $self->{mq}->channel_open($self->{channel}); + + $logger->debug('AMQP: Declaring queue ' . $self->{queue_name}); + $self->{mq}->queue_declare($self->{channel}, $self->{queue_name}, + {passive => 0, durable => 1, exclusive => 0, auto_delete => 0,}, + ); } sub _bind { - my ($self, $message) = @_; - my $logger = Bugzilla->push_ext->logger; - my $config = $self->config; - - # bind to queue (also acts to verify the connection is still valid) - if ($self->{mq}) { - eval { - $logger->debug('AMQP: binding queue(' . $self->{queue_name} . ') with exchange(' . $config->{exchange} . ')'); - $self->{mq}->queue_bind( - $self->{channel}, - $self->{queue_name}, - $config->{exchange}, - $message->routing_key, - ); - }; - if ($@) { - $logger->debug('AMQP: ' . clean_error($@)); - $self->{mq} = 0; - } + my ($self, $message) = @_; + my $logger = Bugzilla->push_ext->logger; + my $config = $self->config; + + # bind to queue (also acts to verify the connection is still valid) + if ($self->{mq}) { + eval { + $logger->debug('AMQP: binding queue(' + . $self->{queue_name} + . ') with exchange(' + . $config->{exchange} + . ')'); + $self->{mq}->queue_bind( + $self->{channel}, $self->{queue_name}, + $config->{exchange}, $message->routing_key, + ); + }; + if ($@) { + $logger->debug('AMQP: ' . clean_error($@)); + $self->{mq} = 0; } + } } sub should_send { - my ($self, $message) = @_; - my $logger = Bugzilla->push_ext->logger; - - my $payload = $message->payload_decoded(); - my $target = $payload->{event}->{target}; - my $is_private = $payload->{$target}->{is_private} ? 1 : 0; - if (!$is_private && exists $payload->{$target}->{bug}) { - $is_private = $payload->{$target}->{bug}->{is_private} ? 1 : 0; - } - - if ($is_private) { - # we only want to push the is_private message from the change_set, as - # this is guaranteed to contain public information only - if ($message->routing_key !~ /\.modify:is_private$/) { - $logger->debug('AMQP: Ignoring private message'); - return 0; - } - $logger->debug('AMQP: Sending change of message to is_private'); + my ($self, $message) = @_; + my $logger = Bugzilla->push_ext->logger; + + my $payload = $message->payload_decoded(); + my $target = $payload->{event}->{target}; + my $is_private = $payload->{$target}->{is_private} ? 1 : 0; + if (!$is_private && exists $payload->{$target}->{bug}) { + $is_private = $payload->{$target}->{bug}->{is_private} ? 1 : 0; + } + + if ($is_private) { + + # we only want to push the is_private message from the change_set, as + # this is guaranteed to contain public information only + if ($message->routing_key !~ /\.modify:is_private$/) { + $logger->debug('AMQP: Ignoring private message'); + return 0; } - return 1; + $logger->debug('AMQP: Sending change of message to is_private'); + } + return 1; } sub send { - my ($self, $message) = @_; - my $logger = Bugzilla->push_ext->logger; - my $config = $self->config; - - # don't push comments to pulse - if ($message->routing_key =~ /^comment\./) { - $logger->debug('AMQP: Ignoring comment'); - return PUSH_RESULT_IGNORED; - } + my ($self, $message) = @_; + my $logger = Bugzilla->push_ext->logger; + my $config = $self->config; - # don't push private data - $self->should_push($message) - || return PUSH_RESULT_IGNORED; + # don't push comments to pulse + if ($message->routing_key =~ /^comment\./) { + $logger->debug('AMQP: Ignoring comment'); + return PUSH_RESULT_IGNORED; + } - $self->_bind($message); + # don't push private data + $self->should_push($message) || return PUSH_RESULT_IGNORED; - eval { - # reconnect if required - if (!$self->{mq}) { - $self->_connect(); - } - - # send message - $logger->debug('AMQP: Publishing message'); - $self->{mq}->publish( - $self->{channel}, - $message->routing_key, - $message->payload, - { - exchange => $config->{exchange}, - }, - { - content_type => 'text/plain', - content_encoding => '8bit', - }, - ); - }; - if ($@) { - return (PUSH_RESULT_TRANSIENT, clean_error($@)); + $self->_bind($message); + + eval { + # reconnect if required + if (!$self->{mq}) { + $self->_connect(); } - return PUSH_RESULT_OK; + # send message + $logger->debug('AMQP: Publishing message'); + $self->{mq}->publish( + $self->{channel}, $message->routing_key, $message->payload, + {exchange => $config->{exchange},}, + {content_type => 'text/plain', content_encoding => '8bit',}, + ); + }; + if ($@) { + return (PUSH_RESULT_TRANSIENT, clean_error($@)); + } + + return PUSH_RESULT_OK; } 1; diff --git a/extensions/Push/lib/Connector.disabled/ServiceNow.pm b/extensions/Push/lib/Connector.disabled/ServiceNow.pm index d0ebdcf10..032e47dde 100644 --- a/extensions/Push/lib/Connector.disabled/ServiceNow.pm +++ b/extensions/Push/lib/Connector.disabled/ServiceNow.pm @@ -32,403 +32,411 @@ use MIME::Base64; use Net::LDAP; use constant SEND_COMPONENTS => ( - { - product => 'mozilla.org', - component => 'Server Operations: Desktop Issues', - }, + {product => 'mozilla.org', component => 'Server Operations: Desktop Issues',}, ); sub options { - return ( - { - name => 'bugzilla_user', - label => 'Bugzilla Service-Now User', - type => 'string', - default => 'service.now@bugzilla.tld', - required => 1, - validate => sub { - Bugzilla::User->new({ name => $_[0] }) - || die "Invalid Bugzilla user ($_[0])\n"; - }, - }, - { - name => 'ldap_scheme', - label => 'Mozilla LDAP Scheme', - type => 'select', - values => [ 'LDAP', 'LDAPS' ], - default => 'LDAPS', - required => 1, - }, - { - name => 'ldap_host', - label => 'Mozilla LDAP Host', - type => 'string', - default => '', - required => 1, - }, - { - name => 'ldap_user', - label => 'Mozilla LDAP Bind Username', - type => 'string', - default => '', - required => 1, - }, - { - name => 'ldap_pass', - label => 'Mozilla LDAP Password', - type => 'password', - default => '', - required => 1, - }, - { - name => 'ldap_poll', - label => 'Mozilla LDAP Poll Frequency', - type => 'string', - default => '3', - required => 1, - help => 'minutes', - validate => sub { - $_[0] =~ /\D/ - && die "LDAP Poll Frequency must be an integer\n"; - $_[0] == 0 - && die "LDAP Poll Frequency cannot be less than one minute\n"; - }, - }, - { - name => 'service_now_url', - label => 'Service Now JSON URL', - type => 'string', - default => 'https://mozilladev.service-now.com', - required => 1, - help => "Must start with https:// and end with ?JSON", - validate => sub { - $_[0] =~ m#^https://[^\.\/]+\.service-now\.com\/# - || die "Invalid Service Now JSON URL\n"; - $_[0] =~ m#\?JSON$# - || die "Invalid Service Now JSON URL (must end with ?JSON)\n"; - }, - }, - { - name => 'service_now_user', - label => 'Service Now JSON Username', - type => 'string', - default => '', - required => 1, - }, - { - name => 'service_now_pass', - label => 'Service Now JSON Password', - type => 'password', - default => '', - required => 1, - }, - ); + return ( + { + name => 'bugzilla_user', + label => 'Bugzilla Service-Now User', + type => 'string', + default => 'service.now@bugzilla.tld', + required => 1, + validate => sub { + Bugzilla::User->new({name => $_[0]}) || die "Invalid Bugzilla user ($_[0])\n"; + }, + }, + { + name => 'ldap_scheme', + label => 'Mozilla LDAP Scheme', + type => 'select', + values => ['LDAP', 'LDAPS'], + default => 'LDAPS', + required => 1, + }, + { + name => 'ldap_host', + label => 'Mozilla LDAP Host', + type => 'string', + default => '', + required => 1, + }, + { + name => 'ldap_user', + label => 'Mozilla LDAP Bind Username', + type => 'string', + default => '', + required => 1, + }, + { + name => 'ldap_pass', + label => 'Mozilla LDAP Password', + type => 'password', + default => '', + required => 1, + }, + { + name => 'ldap_poll', + label => 'Mozilla LDAP Poll Frequency', + type => 'string', + default => '3', + required => 1, + help => 'minutes', + validate => sub { + $_[0] =~ /\D/ && die "LDAP Poll Frequency must be an integer\n"; + $_[0] == 0 && die "LDAP Poll Frequency cannot be less than one minute\n"; + }, + }, + { + name => 'service_now_url', + label => 'Service Now JSON URL', + type => 'string', + default => 'https://mozilladev.service-now.com', + required => 1, + help => "Must start with https:// and end with ?JSON", + validate => sub { + $_[0] =~ m#^https://[^\.\/]+\.service-now\.com\/# + || die "Invalid Service Now JSON URL\n"; + $_[0] =~ m#\?JSON$# + || die "Invalid Service Now JSON URL (must end with ?JSON)\n"; + }, + }, + { + name => 'service_now_user', + label => 'Service Now JSON Username', + type => 'string', + default => '', + required => 1, + }, + { + name => 'service_now_pass', + label => 'Service Now JSON Password', + type => 'password', + default => '', + required => 1, + }, + ); } sub options_validate { - my ($self, $config) = @_; - my $host = $config->{ldap_host}; - trick_taint($host); - my $scheme = lc($config->{ldap_scheme}); - eval { - my $ldap = Net::LDAP->new($host, scheme => $scheme, onerror => 'die', timeout => 5) - or die $!; - $ldap->bind($config->{ldap_user}, password => $config->{ldap_pass}); - }; - if ($@) { - die sprintf("Failed to connect to %s://%s/: %s\n", $scheme, $host, $@); - } + my ($self, $config) = @_; + my $host = $config->{ldap_host}; + trick_taint($host); + my $scheme = lc($config->{ldap_scheme}); + eval { + my $ldap + = Net::LDAP->new($host, scheme => $scheme, onerror => 'die', timeout => 5) + or die $!; + $ldap->bind($config->{ldap_user}, password => $config->{ldap_pass}); + }; + if ($@) { + die sprintf("Failed to connect to %s://%s/: %s\n", $scheme, $host, $@); + } } my $_instance; sub init { - my ($self) = @_; - $_instance = $self; + my ($self) = @_; + $_instance = $self; } sub load_config { - my ($self) = @_; - $self->SUPER::load_config(@_); - $self->{bugzilla_user} ||= Bugzilla::User->new({ name => $self->config->{bugzilla_user} }); + my ($self) = @_; + $self->SUPER::load_config(@_); + $self->{bugzilla_user} + ||= Bugzilla::User->new({name => $self->config->{bugzilla_user}}); } sub should_send { - my ($self, $message) = @_; - - my $data = $message->payload_decoded; - my $bug_data = $self->_get_bug_data($data) - || return 0; - - # we don't want to send the initial comment in a separate message - # because we inject it into the inital message - if (exists $data->{comment} && $data->{comment}->{number} == 0) { - return 0; - } - - my $target = $data->{event}->{target}; - unless ($target eq 'bug' || $target eq 'comment' || $target eq 'attachment') { - return 0; - } - - # ensure the service-now user can see the bug - if (!$self->{bugzilla_user} || !$self->{bugzilla_user}->is_enabled) { - return 0; - } - $self->{bugzilla_user}->can_see_bug($bug_data->{id}) - || return 0; - - # don't push changes made by the service-now account - $data->{event}->{user}->{id} == $self->{bugzilla_user}->id - && return 0; - - # filter based on the component - my $bug = Bugzilla::Bug->new($bug_data->{id}); - my $send = 0; - foreach my $rh (SEND_COMPONENTS) { - if ($bug->product eq $rh->{product} && $bug->component eq $rh->{component}) { - $send = 1; - last; - } + my ($self, $message) = @_; + + my $data = $message->payload_decoded; + my $bug_data = $self->_get_bug_data($data) || return 0; + + # we don't want to send the initial comment in a separate message + # because we inject it into the inital message + if (exists $data->{comment} && $data->{comment}->{number} == 0) { + return 0; + } + + my $target = $data->{event}->{target}; + unless ($target eq 'bug' || $target eq 'comment' || $target eq 'attachment') { + return 0; + } + + # ensure the service-now user can see the bug + if (!$self->{bugzilla_user} || !$self->{bugzilla_user}->is_enabled) { + return 0; + } + $self->{bugzilla_user}->can_see_bug($bug_data->{id}) || return 0; + + # don't push changes made by the service-now account + $data->{event}->{user}->{id} == $self->{bugzilla_user}->id && return 0; + + # filter based on the component + my $bug = Bugzilla::Bug->new($bug_data->{id}); + my $send = 0; + foreach my $rh (SEND_COMPONENTS) { + if ($bug->product eq $rh->{product} && $bug->component eq $rh->{component}) { + $send = 1; + last; } - return $send; + } + return $send; } sub send { - my ($self, $message) = @_; - my $logger = Bugzilla->push_ext->logger; - my $config = $self->config; - - # should_send intiailises bugzilla_user; make sure we return a useful error message - if (!$self->{bugzilla_user}) { - return (PUSH_RESULT_TRANSIENT, "Invalid bugzilla-user (" . $self->config->{bugzilla_user} . ")"); - } - - # load the bug - my $data = $message->payload_decoded; - my $bug_data = $self->_get_bug_data($data); - my $bug = Bugzilla::Bug->new($bug_data->{id}); - - if ($message->routing_key eq 'bug.create') { - # inject the comment into the data for new bugs - my $comment = shift @{ $bug->comments }; - if ($comment->body ne '') { - $bug_data->{comment} = Bugzilla::Extension::Push::Serialise->instance->object_to_hash($comment, 1); - } - - } elsif ($message->routing_key eq 'attachment.create') { - # inject the attachment payload - my $attachment = Bugzilla::Attachment->new($data->{attachment}->{id}); - $data->{attachment}->{data} = encode_base64($attachment->data); - } - - # map bmo login to ldap login and insert into json payload - $self->_add_ldap_logins($data, {}); - - # flatten json data - $self->_flatten($data); - - # add sysparm_action - $data->{sysparm_action} = 'insert'; - - if ($logger->debugging) { - $logger->debug(to_json(ref($data) ? $data : from_json($data), 1)); - } - - # send to service-now - my $request = HTTP::Request->new(POST => $self->config->{service_now_url}); - $request->content_type('application/json'); - $request->content(to_json($data)); - $request->authorization_basic($self->config->{service_now_user}, $self->config->{service_now_pass}); - - $self->{lwp} ||= LWP::UserAgent->new(agent => Bugzilla->localconfig->{urlbase}); - my $result = $self->{lwp}->request($request); - - # http level errors - if (!$result->is_success) { - # treat these as transient - return (PUSH_RESULT_TRANSIENT, $result->status_line); - } - - # empty response - if (length($result->content) == 0) { - # malformed request, treat as transient to allow code to fix - # may also be misconfiguration on servicenow, also transient - return (PUSH_RESULT_TRANSIENT, "Empty response"); + my ($self, $message) = @_; + my $logger = Bugzilla->push_ext->logger; + my $config = $self->config; + +# should_send intiailises bugzilla_user; make sure we return a useful error message + if (!$self->{bugzilla_user}) { + return (PUSH_RESULT_TRANSIENT, + "Invalid bugzilla-user (" . $self->config->{bugzilla_user} . ")"); + } + + # load the bug + my $data = $message->payload_decoded; + my $bug_data = $self->_get_bug_data($data); + my $bug = Bugzilla::Bug->new($bug_data->{id}); + + if ($message->routing_key eq 'bug.create') { + + # inject the comment into the data for new bugs + my $comment = shift @{$bug->comments}; + if ($comment->body ne '') { + $bug_data->{comment} + = Bugzilla::Extension::Push::Serialise->instance->object_to_hash($comment, 1); } - # json errors - my $result_data; - eval { - $result_data = from_json($result->content); - }; - if ($@) { - return (PUSH_RESULT_TRANSIENT, clean_error($@)); - } - if ($logger->debugging) { - $logger->debug(to_json($result_data, 1)); - } - if (exists $result_data->{error}) { - return (PUSH_RESULT_ERROR, $result_data->{error}); - }; - - # malformed/unexpected json response - if (!exists $result_data->{records} - || ref($result_data->{records}) ne 'ARRAY' - || scalar(@{$result_data->{records}}) == 0 - ) { - return (PUSH_RESULT_ERROR, "Malformed JSON response from ServiceNow: missing or empty 'records' array"); - } - - my $record = $result_data->{records}->[0]; - if (ref($record) ne 'HASH') { - return (PUSH_RESULT_ERROR, "Malformed JSON response from ServiceNow: 'records' array does not contain an object"); - } + } + elsif ($message->routing_key eq 'attachment.create') { + + # inject the attachment payload + my $attachment = Bugzilla::Attachment->new($data->{attachment}->{id}); + $data->{attachment}->{data} = encode_base64($attachment->data); + } + + # map bmo login to ldap login and insert into json payload + $self->_add_ldap_logins($data, {}); + + # flatten json data + $self->_flatten($data); + + # add sysparm_action + $data->{sysparm_action} = 'insert'; + + if ($logger->debugging) { + $logger->debug(to_json(ref($data) ? $data : from_json($data), 1)); + } + + # send to service-now + my $request = HTTP::Request->new(POST => $self->config->{service_now_url}); + $request->content_type('application/json'); + $request->content(to_json($data)); + $request->authorization_basic($self->config->{service_now_user}, + $self->config->{service_now_pass}); + + $self->{lwp} ||= LWP::UserAgent->new(agent => Bugzilla->localconfig->{urlbase}); + my $result = $self->{lwp}->request($request); + + # http level errors + if (!$result->is_success) { + + # treat these as transient + return (PUSH_RESULT_TRANSIENT, $result->status_line); + } + + # empty response + if (length($result->content) == 0) { + + # malformed request, treat as transient to allow code to fix + # may also be misconfiguration on servicenow, also transient + return (PUSH_RESULT_TRANSIENT, "Empty response"); + } + + # json errors + my $result_data; + eval { $result_data = from_json($result->content); }; + if ($@) { + return (PUSH_RESULT_TRANSIENT, clean_error($@)); + } + if ($logger->debugging) { + $logger->debug(to_json($result_data, 1)); + } + if (exists $result_data->{error}) { + return (PUSH_RESULT_ERROR, $result_data->{error}); + } + + # malformed/unexpected json response + if (!exists $result_data->{records} + || ref($result_data->{records}) ne 'ARRAY' + || scalar(@{$result_data->{records}}) == 0) + { + return (PUSH_RESULT_ERROR, + "Malformed JSON response from ServiceNow: missing or empty 'records' array"); + } + + my $record = $result_data->{records}->[0]; + if (ref($record) ne 'HASH') { + return (PUSH_RESULT_ERROR, + "Malformed JSON response from ServiceNow: 'records' array does not contain an object" + ); + } - # sys_id is the unique identifier for this action - if (!exists $record->{sys_id} || $record->{sys_id} eq '') { - return (PUSH_RESULT_ERROR, "Malformed JSON response from ServiceNow: 'records object' does not contain a valid sys_id"); - } + # sys_id is the unique identifier for this action + if (!exists $record->{sys_id} || $record->{sys_id} eq '') { + return (PUSH_RESULT_ERROR, + "Malformed JSON response from ServiceNow: 'records object' does not contain a valid sys_id" + ); + } - # success - return (PUSH_RESULT_OK, "sys_id: " . $record->{sys_id}); + # success + return (PUSH_RESULT_OK, "sys_id: " . $record->{sys_id}); } sub _get_bug_data { - my ($self, $data) = @_; - my $target = $data->{event}->{target}; - if ($target eq 'bug') { - return $data->{bug}; - } elsif (exists $data->{$target}->{bug}) { - return $data->{$target}->{bug}; - } else { - return; - } + my ($self, $data) = @_; + my $target = $data->{event}->{target}; + if ($target eq 'bug') { + return $data->{bug}; + } + elsif (exists $data->{$target}->{bug}) { + return $data->{$target}->{bug}; + } + else { + return; + } } sub _flatten { - # service-now expects a flat json object - my ($self, $data) = @_; - my $target = $data->{event}->{target}; + # service-now expects a flat json object + my ($self, $data) = @_; - # delete unnecessary deep objects - if ($target eq 'comment' || $target eq 'attachment') { - $data->{$target}->{bug_id} = $data->{$target}->{bug}->{id}; - delete $data->{$target}->{bug}; - } - delete $data->{event}->{changes}; + my $target = $data->{event}->{target}; - $self->_flatten_hash($data, $data, 'u'); + # delete unnecessary deep objects + if ($target eq 'comment' || $target eq 'attachment') { + $data->{$target}->{bug_id} = $data->{$target}->{bug}->{id}; + delete $data->{$target}->{bug}; + } + delete $data->{event}->{changes}; + + $self->_flatten_hash($data, $data, 'u'); } sub _flatten_hash { - my ($self, $base_hash, $hash, $prefix) = @_; - foreach my $key (keys %$hash) { - if (ref($hash->{$key}) eq 'HASH') { - $self->_flatten_hash($base_hash, $hash->{$key}, $prefix . "_$key"); - } elsif (ref($hash->{$key}) ne 'ARRAY') { - $base_hash->{$prefix . "_$key"} = $hash->{$key}; - } - delete $hash->{$key}; + my ($self, $base_hash, $hash, $prefix) = @_; + foreach my $key (keys %$hash) { + if (ref($hash->{$key}) eq 'HASH') { + $self->_flatten_hash($base_hash, $hash->{$key}, $prefix . "_$key"); + } + elsif (ref($hash->{$key}) ne 'ARRAY') { + $base_hash->{$prefix . "_$key"} = $hash->{$key}; } + delete $hash->{$key}; + } } sub _add_ldap_logins { - my ($self, $rh, $cache) = @_; - if (exists $rh->{login}) { - my $login = $rh->{login}; - $cache->{$login} ||= $self->_bmo_to_ldap($login); - Bugzilla->push_ext->logger->debug("BMO($login) --> LDAP(" . $cache->{$login} . ")"); - $rh->{ldap} = $cache->{$login}; - } - foreach my $key (keys %$rh) { - next unless ref($rh->{$key}) eq 'HASH'; - $self->_add_ldap_logins($rh->{$key}, $cache); - } + my ($self, $rh, $cache) = @_; + if (exists $rh->{login}) { + my $login = $rh->{login}; + $cache->{$login} ||= $self->_bmo_to_ldap($login); + Bugzilla->push_ext->logger->debug( + "BMO($login) --> LDAP(" . $cache->{$login} . ")"); + $rh->{ldap} = $cache->{$login}; + } + foreach my $key (keys %$rh) { + next unless ref($rh->{$key}) eq 'HASH'; + $self->_add_ldap_logins($rh->{$key}, $cache); + } } sub _bmo_to_ldap { - my ($self, $login) = @_; - my $ldap = $self->_ldap_cache(); + my ($self, $login) = @_; + my $ldap = $self->_ldap_cache(); - return '' unless $login =~ /\@mozilla\.(?:com|org)$/; + return '' unless $login =~ /\@mozilla\.(?:com|org)$/; - foreach my $check ($login, canon_email($login)) { - # check for matching bugmail entry - foreach my $mail (keys %$ldap) { - next unless $ldap->{$mail}{bugmail_canon} eq $check; - return $mail; - } + foreach my $check ($login, canon_email($login)) { - # check for matching mail - if (exists $ldap->{$check}) { - return $check; - } + # check for matching bugmail entry + foreach my $mail (keys %$ldap) { + next unless $ldap->{$mail}{bugmail_canon} eq $check; + return $mail; + } - # check for matching email alias - foreach my $mail (sort keys %$ldap) { - next unless grep { $check eq $_ } @{$ldap->{$mail}{aliases}}; - return $mail; - } + # check for matching mail + if (exists $ldap->{$check}) { + return $check; + } + + # check for matching email alias + foreach my $mail (sort keys %$ldap) { + next unless grep { $check eq $_ } @{$ldap->{$mail}{aliases}}; + return $mail; } + } - return ''; + return ''; } sub _ldap_cache { - my ($self) = @_; - my $logger = Bugzilla->push_ext->logger; - my $config = $self->config; - - # cache of all ldap entries; updated infrequently - if (!$self->{ldap_cache_time} || (time) - $self->{ldap_cache_time} > $config->{ldap_poll} * 60) { - $logger->debug('refreshing LDAP cache'); - - my $cache = {}; - - my $host = $config->{ldap_host}; - trick_taint($host); - my $scheme = lc($config->{ldap_scheme}); - my $ldap = Net::LDAP->new($host, scheme => $scheme, onerror => 'die') - or die $!; - $ldap->bind($config->{ldap_user}, password => $config->{ldap_pass}); - foreach my $ldap_base ('o=com,dc=mozilla', 'o=org,dc=mozilla') { - my $result = $ldap->search( - base => $ldap_base, - scope => 'sub', - filter => '(mail=*)', - attrs => ['mail', 'bugzillaEmail', 'emailAlias', 'cn', 'employeeType'], - ); - foreach my $entry ($result->entries) { - my ($name, $bugMail, $mail, $type) = - map { $entry->get_value($_) || '' } - qw(cn bugzillaEmail mail employeeType); - next if $type eq 'DISABLED'; - $mail = lc $mail; - $bugMail = '' if $bugMail !~ /\@/; - $bugMail = trim($bugMail); - if ($bugMail =~ / /) { - $bugMail = (grep { /\@/ } split / /, $bugMail)[0]; - } - $name =~ s/\s+/ /g; - $cache->{$mail}{name} = trim($name); - $cache->{$mail}{bugmail} = $bugMail; - $cache->{$mail}{bugmail_canon} = canon_email($bugMail); - $cache->{$mail}{aliases} = []; - foreach my $alias ( - @{$entry->get_value('emailAlias', asref => 1) || []} - ) { - push @{$cache->{$mail}{aliases}}, canon_email($alias); - } - } - } + my ($self) = @_; + my $logger = Bugzilla->push_ext->logger; + my $config = $self->config; + + # cache of all ldap entries; updated infrequently + if (!$self->{ldap_cache_time} + || (time) - $self->{ldap_cache_time} > $config->{ldap_poll} * 60) + { + $logger->debug('refreshing LDAP cache'); - $self->{ldap_cache} = $cache; - $self->{ldap_cache_time} = (time); + my $cache = {}; + + my $host = $config->{ldap_host}; + trick_taint($host); + my $scheme = lc($config->{ldap_scheme}); + my $ldap = Net::LDAP->new($host, scheme => $scheme, onerror => 'die') or die $!; + $ldap->bind($config->{ldap_user}, password => $config->{ldap_pass}); + foreach my $ldap_base ('o=com,dc=mozilla', 'o=org,dc=mozilla') { + my $result = $ldap->search( + base => $ldap_base, + scope => 'sub', + filter => '(mail=*)', + attrs => ['mail', 'bugzillaEmail', 'emailAlias', 'cn', 'employeeType'], + ); + foreach my $entry ($result->entries) { + my ($name, $bugMail, $mail, $type) + = map { $entry->get_value($_) || '' } qw(cn bugzillaEmail mail employeeType); + next if $type eq 'DISABLED'; + $mail = lc $mail; + $bugMail = '' if $bugMail !~ /\@/; + $bugMail = trim($bugMail); + if ($bugMail =~ / /) { + $bugMail = (grep {/\@/} split / /, $bugMail)[0]; + } + $name =~ s/\s+/ /g; + $cache->{$mail}{name} = trim($name); + $cache->{$mail}{bugmail} = $bugMail; + $cache->{$mail}{bugmail_canon} = canon_email($bugMail); + $cache->{$mail}{aliases} = []; + foreach my $alias (@{$entry->get_value('emailAlias', asref => 1) || []}) { + push @{$cache->{$mail}{aliases}}, canon_email($alias); + } + } } - return $self->{ldap_cache}; + $self->{ldap_cache} = $cache; + $self->{ldap_cache_time} = (time); + } + + return $self->{ldap_cache}; } 1; diff --git a/extensions/Push/lib/Connector/Base.pm b/extensions/Push/lib/Connector/Base.pm index ee41bd160..bd46fe6b4 100644 --- a/extensions/Push/lib/Connector/Base.pm +++ b/extensions/Push/lib/Connector/Base.pm @@ -18,59 +18,65 @@ use Bugzilla::Extension::Push::BacklogQueue; use Bugzilla::Extension::Push::Backoff; sub new { - my ($class) = @_; - my $self = {}; - bless($self, $class); - ($self->{name}) = $class =~ /^.+:(.+)$/; - $self->init(); - return $self; + my ($class) = @_; + my $self = {}; + bless($self, $class); + ($self->{name}) = $class =~ /^.+:(.+)$/; + $self->init(); + return $self; } sub name { - my $self = shift; - return $self->{name}; + my $self = shift; + return $self->{name}; } sub init { - my ($self) = @_; - # abstract - # perform any initialisation here - # will be run when created by the web pages or by the daemon - # and also when the configuration needs to be reloaded + my ($self) = @_; + + # abstract + # perform any initialisation here + # will be run when created by the web pages or by the daemon + # and also when the configuration needs to be reloaded } sub stop { - my ($self) = @_; - # abstract - # run from the daemon only; disconnect from remote hosts, etc + my ($self) = @_; + + # abstract + # run from the daemon only; disconnect from remote hosts, etc } sub should_send { - my ($self, $message) = @_; - # abstract - # return boolean indicating if the connector will be sending the message. - # this will be called each message, and should be a very quick simple test. - # the connector can perform a more exhaustive test in the send() method. - return 0; + my ($self, $message) = @_; + + # abstract + # return boolean indicating if the connector will be sending the message. + # this will be called each message, and should be a very quick simple test. + # the connector can perform a more exhaustive test in the send() method. + return 0; } sub send { - my ($self, $message) = @_; - # abstract - # deliver the message, daemon only + my ($self, $message) = @_; + + # abstract + # deliver the message, daemon only } sub options { - my ($self) = @_; - # abstract - # return an array of configuration variables - return (); + my ($self) = @_; + + # abstract + # return an array of configuration variables + return (); } sub options_validate { - my ($class, $config) = @_; - # abstract, static - # die if a combination of options in $config is invalid + my ($class, $config) = @_; + + # abstract, static + # die if a combination of options in $config is invalid } # @@ -78,29 +84,30 @@ sub options_validate { # sub config { - my ($self) = @_; - if (!$self->{config}) { - $self->load_config(); - } - return $self->{config}; + my ($self) = @_; + if (!$self->{config}) { + $self->load_config(); + } + return $self->{config}; } sub load_config { - my ($self) = @_; - my $config = Bugzilla::Extension::Push::Config->new($self->name, $self->options); - $config->load(); - $self->{config} = $config; + my ($self) = @_; + my $config + = Bugzilla::Extension::Push::Config->new($self->name, $self->options); + $config->load(); + $self->{config} = $config; } sub enabled { - my ($self) = @_; - return $self->config->{enabled} eq 'Enabled'; + my ($self) = @_; + return $self->config->{enabled} eq 'Enabled'; } sub backlog { - my ($self) = @_; - $self->{backlog} ||= Bugzilla::Extension::Push::BacklogQueue->new($self->name); - return $self->{backlog}; + my ($self) = @_; + $self->{backlog} ||= Bugzilla::Extension::Push::BacklogQueue->new($self->name); + return $self->{backlog}; } 1; diff --git a/extensions/Push/lib/Connector/File.pm b/extensions/Push/lib/Connector/File.pm index ae249ffe5..7d86953f8 100644 --- a/extensions/Push/lib/Connector/File.pm +++ b/extensions/Push/lib/Connector/File.pm @@ -20,51 +20,46 @@ use Encode; use FileHandle; sub init { - my ($self) = @_; + my ($self) = @_; } sub options { - return ( - { - name => 'filename', - label => 'Filename', - type => 'string', - default => 'push.log', - required => 1, - validate => sub { - my $filename = shift; - $filename =~ m#^/# - && die "Absolute paths are not permitted\n"; - $filename =~ m#\.\.# - && die "Relative paths are not permitted\n"; - }, - }, - ); + return ( + { + name => 'filename', + label => 'Filename', + type => 'string', + default => 'push.log', + required => 1, + validate => sub { + my $filename = shift; + $filename =~ m#^/# && die "Absolute paths are not permitted\n"; + $filename =~ m#\.\.# && die "Relative paths are not permitted\n"; + }, + }, + ); } sub should_send { - my ($self, $message) = @_; - return 1; + my ($self, $message) = @_; + return 1; } sub send { - my ($self, $message) = @_; + my ($self, $message) = @_; - # pretty-format json payload - my $payload = $message->payload_decoded; - $payload = to_json($payload, 1); + # pretty-format json payload + my $payload = $message->payload_decoded; + $payload = to_json($payload, 1); - my $filename = bz_locations()->{'datadir'} . '/' . $self->config->{filename}; - Bugzilla->push_ext->logger->debug("File: Appending to $filename"); - my $fh = FileHandle->new(">>$filename"); - $fh->binmode(':utf8'); - $fh->print( - "[" . scalar(localtime) . "]\n" . - $payload . "\n\n" - ); - $fh->close; + my $filename = bz_locations()->{'datadir'} . '/' . $self->config->{filename}; + Bugzilla->push_ext->logger->debug("File: Appending to $filename"); + my $fh = FileHandle->new(">>$filename"); + $fh->binmode(':utf8'); + $fh->print("[" . scalar(localtime) . "]\n" . $payload . "\n\n"); + $fh->close; - return PUSH_RESULT_OK; + return PUSH_RESULT_OK; } 1; diff --git a/extensions/Push/lib/Connector/Phabricator.pm b/extensions/Push/lib/Connector/Phabricator.pm index 33e2bb6ad..61a39e32b 100644 --- a/extensions/Push/lib/Connector/Phabricator.pm +++ b/extensions/Push/lib/Connector/Phabricator.pm @@ -29,105 +29,96 @@ use Bugzilla::Extension::Push::Constants; use Bugzilla::Extension::Push::Util qw(is_public); sub options { - return ( - { - name => 'phabricator_url', - label => 'Phabricator URL', - type => 'string', - default => '', - required => 1, - } - ); + return ({ + name => 'phabricator_url', + label => 'Phabricator URL', + type => 'string', + default => '', + required => 1, + }); } sub should_send { - my ( $self, $message ) = @_; + my ($self, $message) = @_; - return 0 unless Bugzilla->params->{phabricator_enabled}; + return 0 unless Bugzilla->params->{phabricator_enabled}; - # We are only interested currently in bug group, assignee, qa-contact, or cc changes. - return 0 - unless $message->routing_key =~ - /^(?:attachment|bug)\.modify:.*\b(bug_group|assigned_to|qa_contact|cc)\b/; +# We are only interested currently in bug group, assignee, qa-contact, or cc changes. + return 0 + unless $message->routing_key + =~ /^(?:attachment|bug)\.modify:.*\b(bug_group|assigned_to|qa_contact|cc)\b/; - my $bug = $self->_get_bug_by_data( $message->payload_decoded ) || return 0; + my $bug = $self->_get_bug_by_data($message->payload_decoded) || return 0; - return $bug->has_attachment_with_mimetype(PHAB_CONTENT_TYPE); + return $bug->has_attachment_with_mimetype(PHAB_CONTENT_TYPE); } sub send { - my ( $self, $message ) = @_; - - my $logger = Bugzilla->push_ext->logger; - - my $data = $message->payload_decoded; - - my $bug = $self->_get_bug_by_data($data) || return PUSH_RESULT_OK; - - my $is_public = is_public($bug); - - my $revisions = get_attachment_revisions($bug); - - my $group_change = - ($message->routing_key =~ /^(?:attachment|bug)\.modify:.*\bbug_group\b/) - ? 1 - : 0; - - foreach my $revision (@$revisions) { - if ( $is_public && $group_change ) { - Bugzilla->audit(sprintf( - 'Making revision %s public for bug %s', - $revision->id, - $bug->id - )); - $revision->make_public(); - } - elsif ( !$is_public && $group_change ) { - Bugzilla->audit(sprintf( - 'Giving revision %s a custom policy for bug %s', - $revision->id, - $bug->id - )); - my $set_project_names = [ map { "bmo-" . $_->name } @{ $bug->groups_in } ]; - $revision->make_private($set_project_names); - } - - # Subscriber list of the private revision should always match - # the bug roles such as assignee, qa contact, and cc members. - if (!$is_public) { - Bugzilla->audit(sprintf( - 'Updating subscribers for %s for bug %s', - $revision->id, - $bug->id - )); - my $subscribers = get_bug_role_phids($bug); - $revision->set_subscribers($subscribers) if $subscribers; - } - - $revision->update(); + my ($self, $message) = @_; + + my $logger = Bugzilla->push_ext->logger; + + my $data = $message->payload_decoded; + + my $bug = $self->_get_bug_by_data($data) || return PUSH_RESULT_OK; + + my $is_public = is_public($bug); + + my $revisions = get_attachment_revisions($bug); + + my $group_change + = ($message->routing_key =~ /^(?:attachment|bug)\.modify:.*\bbug_group\b/) + ? 1 + : 0; + + foreach my $revision (@$revisions) { + if ($is_public && $group_change) { + Bugzilla->audit( + sprintf('Making revision %s public for bug %s', $revision->id, $bug->id)); + $revision->make_public(); } + elsif (!$is_public && $group_change) { + Bugzilla->audit(sprintf( + 'Giving revision %s a custom policy for bug %s', + $revision->id, $bug->id + )); + my $set_project_names = [map { "bmo-" . $_->name } @{$bug->groups_in}]; + $revision->make_private($set_project_names); + } + + # Subscriber list of the private revision should always match + # the bug roles such as assignee, qa contact, and cc members. + if (!$is_public) { + Bugzilla->audit( + sprintf('Updating subscribers for %s for bug %s', $revision->id, $bug->id)); + my $subscribers = get_bug_role_phids($bug); + $revision->set_subscribers($subscribers) if $subscribers; + } + + $revision->update(); + } - return PUSH_RESULT_OK; + return PUSH_RESULT_OK; } sub _get_bug_by_data { - my ( $self, $data ) = @_; - my $bug_data = $self->_get_bug_data($data) || return 0; - my $bug = Bugzilla::Bug->new( { id => $bug_data->{id} } ); + my ($self, $data) = @_; + my $bug_data = $self->_get_bug_data($data) || return 0; + my $bug = Bugzilla::Bug->new({id => $bug_data->{id}}); } sub _get_bug_data { - my ( $self, $data ) = @_; - my $target = $data->{event}->{target}; - if ( $target eq 'bug' ) { - return $data->{bug}; - } - elsif ( exists $data->{$target}->{bug} ) { - return $data->{$target}->{bug}; - } - else { - return; - } + my ($self, $data) = @_; + my $target = $data->{event}->{target}; + if ($target eq 'bug') { + return $data->{bug}; + } + elsif (exists $data->{$target}->{bug}) { + return $data->{$target}->{bug}; + } + else { + return; + } } 1; diff --git a/extensions/Push/lib/Connector/Spark.pm b/extensions/Push/lib/Connector/Spark.pm index e58ddfbe4..1eb6f22c6 100644 --- a/extensions/Push/lib/Connector/Spark.pm +++ b/extensions/Push/lib/Connector/Spark.pm @@ -25,150 +25,154 @@ use LWP::UserAgent; use List::MoreUtils qw(any); sub options { - return ( - { - name => 'spark_endpoint', - label => 'Spark API Endpoint', - type => 'string', - default => 'https://api.ciscospark.com/v1', - required => 1, - }, - { - name => 'spark_room_id', - label => 'Spark Room ID', - type => 'string', - default => 'bugzilla', - required => 1, - }, - { - name => 'spark_api_key', - label => 'Spark API Key', - type => 'string', - default => '', - required => 1, - }, - ); + return ( + { + name => 'spark_endpoint', + label => 'Spark API Endpoint', + type => 'string', + default => 'https://api.ciscospark.com/v1', + required => 1, + }, + { + name => 'spark_room_id', + label => 'Spark Room ID', + type => 'string', + default => 'bugzilla', + required => 1, + }, + { + name => 'spark_api_key', + label => 'Spark API Key', + type => 'string', + default => '', + required => 1, + }, + ); } sub stop { - my ($self) = @_; + my ($self) = @_; } sub should_send { - my ($self, $message) = @_; - - my $data = $message->payload_decoded; - my $bug_data = $self->_get_bug_data($data) - || return 0; - - # Send if bug has cisco-spark keyword - my $bug = Bugzilla::Bug->new({ id => $bug_data->{id}, cache => 1 }); - return 0 unless $bug->has_keyword('cisco-spark'); - - if ($message->routing_key eq 'bug.create') { - return 1; - } - else { - foreach my $change (@{ $data->{event}->{changes} }) { - # send status and resolution updates - return 1 if $change->{field} eq 'bug_status' || $change->{field} eq 'resolution'; - # also send if the right keyword has been added to this bug - if ($change->{field} eq 'keywords' && $change->{added}) { - my @added = split(/, /, $change->{added}); - return 1 if any { $_ eq 'cisco-spark' } @added; - } - } + my ($self, $message) = @_; + + my $data = $message->payload_decoded; + my $bug_data = $self->_get_bug_data($data) || return 0; + + # Send if bug has cisco-spark keyword + my $bug = Bugzilla::Bug->new({id => $bug_data->{id}, cache => 1}); + return 0 unless $bug->has_keyword('cisco-spark'); + + if ($message->routing_key eq 'bug.create') { + return 1; + } + else { + foreach my $change (@{$data->{event}->{changes}}) { + + # send status and resolution updates + return 1 + if $change->{field} eq 'bug_status' || $change->{field} eq 'resolution'; + + # also send if the right keyword has been added to this bug + if ($change->{field} eq 'keywords' && $change->{added}) { + my @added = split(/, /, $change->{added}); + return 1 if any { $_ eq 'cisco-spark' } @added; + } } + } - # and nothing else - return 0; + # and nothing else + return 0; } sub send { - my ($self, $message) = @_; + my ($self, $message) = @_; - eval { - my $data = $message->payload_decoded(); - my $bug_data = $self->_get_bug_data($data); - my $bug = Bugzilla::Bug->new({ id => $bug_data->{id}, cache => 1 }); + eval { + my $data = $message->payload_decoded(); + my $bug_data = $self->_get_bug_data($data); + my $bug = Bugzilla::Bug->new({id => $bug_data->{id}, cache => 1}); - my $text = "Bug " . $bug->id . " - " . $bug->short_desc . "\n"; - if ($message->routing_key eq 'bug.create') { - $text = "New " . $text; + my $text = "Bug " . $bug->id . " - " . $bug->short_desc . "\n"; + if ($message->routing_key eq 'bug.create') { + $text = "New " . $text; + } + else { + foreach my $change (@{$data->{event}->{changes}}) { + if ($change->{field} eq 'bug_status') { + $text + .= "Status changed: " . $change->{removed} . " -> " . $change->{added} . "\n"; } - else { - foreach my $change (@{ $data->{event}->{changes} }) { - if ($change->{field} eq 'bug_status') { - $text .= "Status changed: " . - $change->{removed} . " -> " . $change->{added} . "\n"; - } - if ($change->{field} eq 'resolution') { - $text .= "Resolution changed: " . - ($change->{removed} ? $change->{removed} . " -> " : "") . $change->{added} . "\n"; - } - } + if ($change->{field} eq 'resolution') { + $text + .= "Resolution changed: " + . ($change->{removed} ? $change->{removed} . " -> " : "") + . $change->{added} . "\n"; } - $text .= Bugzilla->localconfig->{urlbase} . "show_bug.cgi?id=" . $bug->id; + } + } + $text .= Bugzilla->localconfig->{urlbase} . "show_bug.cgi?id=" . $bug->id; - my $room_id = $self->config->{spark_room_id}; - my $message_uri = $self->_spark_uri('messages'); + my $room_id = $self->config->{spark_room_id}; + my $message_uri = $self->_spark_uri('messages'); - my $json_data = { roomId => $room_id, text => $text }; + my $json_data = {roomId => $room_id, text => $text}; - my $headers = HTTP::Headers->new( - Content_Type => 'application/json' - ); - my $request = HTTP::Request->new('POST', $message_uri, $headers, encode_json($json_data)); - my $resp = $self->_user_agent->request($request); + my $headers = HTTP::Headers->new(Content_Type => 'application/json'); + my $request + = HTTP::Request->new('POST', $message_uri, $headers, encode_json($json_data)); + my $resp = $self->_user_agent->request($request); - if ($resp->code != 200) { - die "Expected HTTP 200 response, got " . $resp->code; - } - }; - if ($@) { - return (PUSH_RESULT_TRANSIENT, clean_error($@)); + if ($resp->code != 200) { + die "Expected HTTP 200 response, got " . $resp->code; } + }; + if ($@) { + return (PUSH_RESULT_TRANSIENT, clean_error($@)); + } - return PUSH_RESULT_OK; + return PUSH_RESULT_OK; } # Private methods sub _get_bug_data { - my ($self, $data) = @_; - my $target = $data->{event}->{target}; - if ($target eq 'bug') { - return $data->{bug}; - } elsif (exists $data->{$target}->{bug}) { - return $data->{$target}->{bug}; - } else { - return; - } + my ($self, $data) = @_; + my $target = $data->{event}->{target}; + if ($target eq 'bug') { + return $data->{bug}; + } + elsif (exists $data->{$target}->{bug}) { + return $data->{$target}->{bug}; + } + else { + return; + } } sub _user_agent { - my ($self) = @_; - my $ua = LWP::UserAgent->new(agent => 'Bugzilla'); - $ua->timeout(10); - $ua->protocols_allowed(['http', 'https']); - - if (my $proxy_url = Bugzilla->params->{proxy_url}) { - $ua->proxy(['http', 'https'], $proxy_url); - } - else { - $ua->env_proxy(); - } - - $ua->default_header( - 'Authorization' => 'Bearer ' . $self->config->{spark_api_key} - ); - - return $ua; + my ($self) = @_; + my $ua = LWP::UserAgent->new(agent => 'Bugzilla'); + $ua->timeout(10); + $ua->protocols_allowed(['http', 'https']); + + if (my $proxy_url = Bugzilla->params->{proxy_url}) { + $ua->proxy(['http', 'https'], $proxy_url); + } + else { + $ua->env_proxy(); + } + + $ua->default_header( + 'Authorization' => 'Bearer ' . $self->config->{spark_api_key}); + + return $ua; } sub _spark_uri { - my ($self, $path) = @_; - return URI->new($self->config->{spark_endpoint} . "/" . $path); + my ($self, $path) = @_; + return URI->new($self->config->{spark_endpoint} . "/" . $path); } 1; diff --git a/extensions/Push/lib/Connectors.pm b/extensions/Push/lib/Connectors.pm index d3c55d3ca..9a3856c02 100644 --- a/extensions/Push/lib/Connectors.pm +++ b/extensions/Push/lib/Connectors.pm @@ -19,94 +19,97 @@ use File::Basename; use Try::Tiny; sub new { - my ($class) = @_; - my $self = {}; - bless($self, $class); - - $self->{names} = []; - $self->{objects} = {}; - $self->{path} = bz_locations->{'extensionsdir'} . '/Push/lib/Connector'; - - foreach my $file (glob($self->{path} . '/*.pm')) { - my $name = basename($file); - $name =~ s/\.pm$//; - next if $name eq 'Base'; - if (length($name) > 32) { - WARN("Ignoring connector '$name': Name longer than 32 characters"); - } - push @{$self->{names}}, $name; - TRACE("Found connector '$name'"); + my ($class) = @_; + my $self = {}; + bless($self, $class); + + $self->{names} = []; + $self->{objects} = {}; + $self->{path} = bz_locations->{'extensionsdir'} . '/Push/lib/Connector'; + + foreach my $file (glob($self->{path} . '/*.pm')) { + my $name = basename($file); + $name =~ s/\.pm$//; + next if $name eq 'Base'; + if (length($name) > 32) { + WARN("Ignoring connector '$name': Name longer than 32 characters"); } + push @{$self->{names}}, $name; + TRACE("Found connector '$name'"); + } - return $self; + return $self; } sub _load { - my ($self) = @_; - return if scalar keys %{$self->{objects}}; - - foreach my $name (@{$self->{names}}) { - next if exists $self->{objects}->{$name}; - my $file = $self->{path} . "/$name.pm"; - trick_taint($file); - require $file; - my $package = "Bugzilla::Extension::Push::Connector::$name"; - - TRACE("Loading connector '$name'"); - my $old_error_mode = Bugzilla->error_mode; - Bugzilla->error_mode(ERROR_MODE_DIE); - try { - my $connector = $package->new(); - $connector->load_config(); - $self->{objects}->{$name} = $connector; - } catch { - ERROR("Connector '$name' failed to load: " . clean_error($_)); - }; - Bugzilla->error_mode($old_error_mode); + my ($self) = @_; + return if scalar keys %{$self->{objects}}; + + foreach my $name (@{$self->{names}}) { + next if exists $self->{objects}->{$name}; + my $file = $self->{path} . "/$name.pm"; + trick_taint($file); + require $file; + my $package = "Bugzilla::Extension::Push::Connector::$name"; + + TRACE("Loading connector '$name'"); + my $old_error_mode = Bugzilla->error_mode; + Bugzilla->error_mode(ERROR_MODE_DIE); + try { + my $connector = $package->new(); + $connector->load_config(); + $self->{objects}->{$name} = $connector; } + catch { + ERROR("Connector '$name' failed to load: " . clean_error($_)); + }; + Bugzilla->error_mode($old_error_mode); + } } sub stop { - my ($self) = @_; - foreach my $connector ($self->list) { - next unless $connector->enabled; - TRACE("Stopping '" . $connector->name . "'"); - try { - $connector->stop(); - } catch { - ERROR("Connector '" . $connector->name . "' failed to stop: " . clean_error($_)); - }; + my ($self) = @_; + foreach my $connector ($self->list) { + next unless $connector->enabled; + TRACE("Stopping '" . $connector->name . "'"); + try { + $connector->stop(); } + catch { + ERROR( + "Connector '" . $connector->name . "' failed to stop: " . clean_error($_)); + }; + } } sub reload { - my ($self) = @_; - $self->stop(); - $self->{objects} = {}; - $self->_load(); + my ($self) = @_; + $self->stop(); + $self->{objects} = {}; + $self->_load(); } sub names { - my ($self) = @_; - return @{$self->{names}}; + my ($self) = @_; + return @{$self->{names}}; } sub list { - my ($self) = @_; - $self->_load(); - return sort { $a->name cmp $b->name } values %{$self->{objects}}; + my ($self) = @_; + $self->_load(); + return sort { $a->name cmp $b->name } values %{$self->{objects}}; } sub exists { - my ($self, $name) = @_; - $self->by_name($name) ? 1 : 0; + my ($self, $name) = @_; + $self->by_name($name) ? 1 : 0; } sub by_name { - my ($self, $name) = @_; - $self->_load(); - return unless exists $self->{objects}->{$name}; - return $self->{objects}->{$name}; + my ($self, $name) = @_; + $self->_load(); + return unless exists $self->{objects}->{$name}; + return $self->{objects}->{$name}; } 1; diff --git a/extensions/Push/lib/Constants.pm b/extensions/Push/lib/Constants.pm index 09c5e464c..c021b32f4 100644 --- a/extensions/Push/lib/Constants.pm +++ b/extensions/Push/lib/Constants.pm @@ -14,14 +14,14 @@ use warnings; use base 'Exporter'; our @EXPORT = qw( - PUSH_RESULT_OK - PUSH_RESULT_IGNORED - PUSH_RESULT_TRANSIENT - PUSH_RESULT_ERROR - PUSH_RESULT_UNKNOWN - push_result_to_string - - POLL_INTERVAL_SECONDS + PUSH_RESULT_OK + PUSH_RESULT_IGNORED + PUSH_RESULT_TRANSIENT + PUSH_RESULT_ERROR + PUSH_RESULT_UNKNOWN + push_result_to_string + + POLL_INTERVAL_SECONDS ); use constant PUSH_RESULT_OK => 1; @@ -31,12 +31,12 @@ use constant PUSH_RESULT_ERROR => 4; use constant PUSH_RESULT_UNKNOWN => 5; sub push_result_to_string { - my ($result) = @_; - return 'OK' if $result == PUSH_RESULT_OK; - return 'OK-IGNORED' if $result == PUSH_RESULT_IGNORED; - return 'TRANSIENT-ERROR' if $result == PUSH_RESULT_TRANSIENT; - return 'FATAL-ERROR' if $result == PUSH_RESULT_ERROR; - return 'UNKNOWN' if $result == PUSH_RESULT_UNKNOWN; + my ($result) = @_; + return 'OK' if $result == PUSH_RESULT_OK; + return 'OK-IGNORED' if $result == PUSH_RESULT_IGNORED; + return 'TRANSIENT-ERROR' if $result == PUSH_RESULT_TRANSIENT; + return 'FATAL-ERROR' if $result == PUSH_RESULT_ERROR; + return 'UNKNOWN' if $result == PUSH_RESULT_UNKNOWN; } use constant POLL_INTERVAL_SECONDS => 30; diff --git a/extensions/Push/lib/Daemon.pm b/extensions/Push/lib/Daemon.pm index 7f2459a95..7fb5352ca 100644 --- a/extensions/Push/lib/Daemon.pm +++ b/extensions/Push/lib/Daemon.pm @@ -20,7 +20,7 @@ use File::Basename; use Pod::Usage; sub start { - newdaemon(); + newdaemon(); } # @@ -28,70 +28,71 @@ sub start { # sub gd_preconfig { - my $self = shift; - my $pidfile = $self->{gd_args}{pidfile}; - if (!$pidfile) { - $pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname} . ".pid"; - } - return (pidfile => $pidfile); + my $self = shift; + my $pidfile = $self->{gd_args}{pidfile}; + if (!$pidfile) { + $pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname} . ".pid"; + } + return (pidfile => $pidfile); } sub gd_getopt { - my $self = shift; - $self->SUPER::gd_getopt(); - if ($self->{gd_args}{progname}) { - $self->{gd_progname} = $self->{gd_args}{progname}; - } else { - $self->{gd_progname} = basename($0); - } - $self->{_original_zero} = $0; - $0 = $self->{gd_progname}; + my $self = shift; + $self->SUPER::gd_getopt(); + if ($self->{gd_args}{progname}) { + $self->{gd_progname} = $self->{gd_args}{progname}; + } + else { + $self->{gd_progname} = basename($0); + } + $self->{_original_zero} = $0; + $0 = $self->{gd_progname}; } sub gd_postconfig { - my $self = shift; - $0 = delete $self->{_original_zero}; + my $self = shift; + $0 = delete $self->{_original_zero}; } sub gd_more_opt { - my $self = shift; - return ( - 'pidfile=s' => \$self->{gd_args}{pidfile}, - 'n=s' => \$self->{gd_args}{progname}, - ); + my $self = shift; + return ( + 'pidfile=s' => \$self->{gd_args}{pidfile}, + 'n=s' => \$self->{gd_args}{progname}, + ); } sub gd_usage { - pod2usage({ -verbose => 0, -exitval => 'NOEXIT' }); - return 0; -}; + pod2usage({-verbose => 0, -exitval => 'NOEXIT'}); + return 0; +} sub gd_redirect_output { - my $self = shift; + my $self = shift; - my $filename = bz_locations()->{datadir} . '/' . $self->{gd_progname} . ".log"; + my $filename = bz_locations()->{datadir} . '/' . $self->{gd_progname} . ".log"; + open(STDERR, ">>", $filename) or (print "could not open stderr: $!" && exit(1)); + close(STDOUT); + open(STDOUT, ">&", STDERR) or die "redirect STDOUT -> STDERR: $!"; + $SIG{HUP} = sub { + close(STDERR); open(STDERR, ">>", $filename) or (print "could not open stderr: $!" && exit(1)); - close(STDOUT); - open(STDOUT, ">&", STDERR) or die "redirect STDOUT -> STDERR: $!"; - $SIG{HUP} = sub { - close(STDERR); - open(STDERR, ">>", $filename) or (print "could not open stderr: $!" && exit(1)); - }; + }; } sub gd_setup_signals { - my $self = shift; - $self->SUPER::gd_setup_signals(); - $SIG{TERM} = sub { $self->gd_quit_event(); } + my $self = shift; + $self->SUPER::gd_setup_signals(); + $SIG{TERM} = sub { $self->gd_quit_event(); } } sub gd_run { - my $self = shift; - $::SIG{__DIE__} = \&Carp::confess if $self->{debug}; - my $push = Bugzilla->push_ext; - $push->logger->{debug} = $self->{debug}; - $push->is_daemon(1); - $push->start(); + my $self = shift; + $::SIG{__DIE__} = \&Carp::confess if $self->{debug}; + my $push = Bugzilla->push_ext; + $push->logger->{debug} = $self->{debug}; + $push->is_daemon(1); + $push->start(); } 1; diff --git a/extensions/Push/lib/Log.pm b/extensions/Push/lib/Log.pm index 8a35a6cf5..7358477ed 100644 --- a/extensions/Push/lib/Log.pm +++ b/extensions/Push/lib/Log.pm @@ -15,32 +15,30 @@ use Bugzilla; use Bugzilla::Extension::Push::Message; sub new { - my ($class) = @_; - my $self = {}; - bless($self, $class); - return $self; + my ($class) = @_; + my $self = {}; + bless($self, $class); + return $self; } sub count { - my ($self) = @_; - my $dbh = Bugzilla->dbh; - return $dbh->selectrow_array("SELECT COUNT(*) FROM push_log"); + my ($self) = @_; + my $dbh = Bugzilla->dbh; + return $dbh->selectrow_array("SELECT COUNT(*) FROM push_log"); } sub list { - my ($self, %args) = @_; - $args{limit} ||= 10; - $args{filter} ||= ''; - my @result; - my $dbh = Bugzilla->dbh; + my ($self, %args) = @_; + $args{limit} ||= 10; + $args{filter} ||= ''; + my @result; + my $dbh = Bugzilla->dbh; - my $ids = $dbh->selectcol_arrayref(" + my $ids = $dbh->selectcol_arrayref(" SELECT id FROM push_log - ORDER BY processed_ts DESC " . - $dbh->sql_limit(100) - ); - return Bugzilla::Extension::Push::LogEntry->new_from_list($ids); + ORDER BY processed_ts DESC " . $dbh->sql_limit(100)); + return Bugzilla::Extension::Push::LogEntry->new_from_list($ids); } 1; diff --git a/extensions/Push/lib/LogEntry.pm b/extensions/Push/lib/LogEntry.pm index f4e894b94..0d9770a8a 100644 --- a/extensions/Push/lib/LogEntry.pm +++ b/extensions/Push/lib/LogEntry.pm @@ -26,21 +26,19 @@ use Bugzilla::Extension::Push::Constants; # initialisation # -use constant DB_TABLE => 'push_log'; +use constant DB_TABLE => 'push_log'; use constant DB_COLUMNS => qw( - id - message_id - change_set - routing_key - connector - push_ts - processed_ts - result - data + id + message_id + change_set + routing_key + connector + push_ts + processed_ts + result + data ); -use constant VALIDATORS => { - data => \&_check_data, -}; +use constant VALIDATORS => {data => \&_check_data,}; use constant NAME_FIELD => ''; use constant LIST_ORDER => 'processed_ts DESC'; @@ -48,14 +46,14 @@ use constant LIST_ORDER => 'processed_ts DESC'; # accessors # -sub message_id { return $_[0]->{'message_id'}; } -sub change_set { return $_[0]->{'change_set'}; } -sub routing_key { return $_[0]->{'routing_key'}; } -sub connector { return $_[0]->{'connector'}; } -sub push_ts { return $_[0]->{'push_ts'}; } +sub message_id { return $_[0]->{'message_id'}; } +sub change_set { return $_[0]->{'change_set'}; } +sub routing_key { return $_[0]->{'routing_key'}; } +sub connector { return $_[0]->{'connector'}; } +sub push_ts { return $_[0]->{'push_ts'}; } sub processed_ts { return $_[0]->{'processed_ts'}; } -sub result { return $_[0]->{'result'}; } -sub data { return $_[0]->{'data'}; } +sub result { return $_[0]->{'result'}; } +sub data { return $_[0]->{'data'}; } sub result_string { return push_result_to_string($_[0]->result) } @@ -64,8 +62,8 @@ sub result_string { return push_result_to_string($_[0]->result) } # sub _check_data { - my ($invocant, $value) = @_; - return $value eq '' ? undef : $value; + my ($invocant, $value) = @_; + return $value eq '' ? undef : $value; } 1; diff --git a/extensions/Push/lib/Logger.pm b/extensions/Push/lib/Logger.pm index 5d92010ee..ec6dbe497 100644 --- a/extensions/Push/lib/Logger.pm +++ b/extensions/Push/lib/Logger.pm @@ -20,42 +20,38 @@ use Bugzilla::Extension::Push::LogEntry; Log::Log4perl->wrapper_register(__PACKAGE__); sub info { - my ($this, $message) = @_; - INFO($message); + my ($this, $message) = @_; + INFO($message); } sub error { - my ($this, $message) = @_; - ERROR($message); + my ($this, $message) = @_; + ERROR($message); } sub debug { - my ($this, $message) = @_; - DEBUG($message); + my ($this, $message) = @_; + DEBUG($message); } sub result { - my ($self, $connector, $message, $result, $data) = @_; - $data ||= ''; - - my $log_msg = sprintf - '%s: Message #%s: %s %s', - $connector->name, - $message->message_id, - push_result_to_string($result), - $data; - $self->info($log_msg); - - Bugzilla::Extension::Push::LogEntry->create({ - message_id => $message->message_id, - change_set => $message->change_set, - routing_key => $message->routing_key, - connector => $connector->name, - push_ts => $message->push_ts, - processed_ts => Bugzilla->dbh->selectrow_array('SELECT NOW()'), - result => $result, - data => $data, - }); + my ($self, $connector, $message, $result, $data) = @_; + $data ||= ''; + + my $log_msg = sprintf '%s: Message #%s: %s %s', $connector->name, + $message->message_id, push_result_to_string($result), $data; + $self->info($log_msg); + + Bugzilla::Extension::Push::LogEntry->create({ + message_id => $message->message_id, + change_set => $message->change_set, + routing_key => $message->routing_key, + connector => $connector->name, + push_ts => $message->push_ts, + processed_ts => Bugzilla->dbh->selectrow_array('SELECT NOW()'), + result => $result, + data => $data, + }); } sub _build_logger { Log::Log4perl->get_logger(__PACKAGE__); } diff --git a/extensions/Push/lib/Message.pm b/extensions/Push/lib/Message.pm index 1beb18ef0..3587de1d9 100644 --- a/extensions/Push/lib/Message.pm +++ b/extensions/Push/lib/Message.pm @@ -27,50 +27,50 @@ use Encode; # initialisation # -use constant DB_TABLE => 'push'; +use constant DB_TABLE => 'push'; use constant DB_COLUMNS => qw( - id - push_ts - payload - change_set - routing_key + id + push_ts + payload + change_set + routing_key ); use constant LIST_ORDER => 'push_ts'; use constant VALIDATORS => { - push_ts => \&_check_push_ts, - payload => \&_check_payload, - change_set => \&_check_change_set, - routing_key => \&_check_routing_key, + push_ts => \&_check_push_ts, + payload => \&_check_payload, + change_set => \&_check_change_set, + routing_key => \&_check_routing_key, }; # this creates an object which doesn't exist on the database sub new_transient { - my $invocant = shift; - my $class = ref($invocant) || $invocant; - my $object = shift; - bless($object, $class) if $object; - return $object; + my $invocant = shift; + my $class = ref($invocant) || $invocant; + my $object = shift; + bless($object, $class) if $object; + return $object; } # take a transient object and commit sub create_from_transient { - my ($self) = @_; - return $self->create($self); + my ($self) = @_; + return $self->create($self); } # # accessors # -sub push_ts { return $_[0]->{'push_ts'}; } -sub payload { return $_[0]->{'payload'}; } -sub change_set { return $_[0]->{'change_set'}; } +sub push_ts { return $_[0]->{'push_ts'}; } +sub payload { return $_[0]->{'payload'}; } +sub change_set { return $_[0]->{'change_set'}; } sub routing_key { return $_[0]->{'routing_key'}; } -sub message_id { return $_[0]->id; } +sub message_id { return $_[0]->id; } sub payload_decoded { - my ($self) = @_; - return from_json($self->{'payload'}); + my ($self) = @_; + return from_json($self->{'payload'}); } # @@ -78,27 +78,29 @@ sub payload_decoded { # sub _check_push_ts { - my ($invocant, $value) = @_; - $value ||= Bugzilla->dbh->selectrow_array('SELECT NOW()'); - return $value; + my ($invocant, $value) = @_; + $value ||= Bugzilla->dbh->selectrow_array('SELECT NOW()'); + return $value; } sub _check_payload { - my ($invocant, $value) = @_; - length($value) || ThrowCodeError('push_invalid_payload'); - return $value; + my ($invocant, $value) = @_; + length($value) || ThrowCodeError('push_invalid_payload'); + return $value; } sub _check_change_set { - my ($invocant, $value) = @_; - (defined($value) && length($value)) || ThrowCodeError('push_invalid_change_set'); - return $value; + my ($invocant, $value) = @_; + (defined($value) && length($value)) + || ThrowCodeError('push_invalid_change_set'); + return $value; } sub _check_routing_key { - my ($invocant, $value) = @_; - (defined($value) && length($value)) || ThrowCodeError('push_invalid_routing_key'); - return $value; + my ($invocant, $value) = @_; + (defined($value) && length($value)) + || ThrowCodeError('push_invalid_routing_key'); + return $value; } 1; diff --git a/extensions/Push/lib/Option.pm b/extensions/Push/lib/Option.pm index a08e4c11d..a8e67714c 100644 --- a/extensions/Push/lib/Option.pm +++ b/extensions/Push/lib/Option.pm @@ -21,27 +21,25 @@ use Bugzilla::Util; # initialisation # -use constant DB_TABLE => 'push_options'; +use constant DB_TABLE => 'push_options'; use constant DB_COLUMNS => qw( - id - connector - option_name - option_value + id + connector + option_name + option_value ); use constant UPDATE_COLUMNS => qw( - option_value + option_value ); -use constant VALIDATORS => { - connector => \&_check_connector, -}; +use constant VALIDATORS => {connector => \&_check_connector,}; use constant LIST_ORDER => 'connector'; # # accessors # -sub connector { return $_[0]->{'connector'}; } -sub name { return $_[0]->{'option_name'}; } +sub connector { return $_[0]->{'connector'}; } +sub name { return $_[0]->{'option_name'}; } sub value { return $_[0]->{'option_value'}; } # @@ -55,12 +53,12 @@ sub set_value { $_[0]->{'option_value'} = $_[1]; } # sub _check_connector { - my ($invocant, $value) = @_; - $value eq '*' - || $value eq 'global' - || Bugzilla->push_ext->connectors->exists($value) - || ThrowCodeError('push_invalid_connector'); - return $value; + my ($invocant, $value) = @_; + $value eq '*' + || $value eq 'global' + || Bugzilla->push_ext->connectors->exists($value) + || ThrowCodeError('push_invalid_connector'); + return $value; } 1; diff --git a/extensions/Push/lib/Push.pm b/extensions/Push/lib/Push.pm index ab640da81..97bac942b 100644 --- a/extensions/Push/lib/Push.pm +++ b/extensions/Push/lib/Push.pm @@ -24,269 +24,273 @@ use Bugzilla::Extension::Push::Util; use DateTime; use Try::Tiny; -has 'is_daemon' => ( - is => 'rw', - default => 0, -); +has 'is_daemon' => (is => 'rw', default => 0,); sub start { - my ($self) = @_; - my $connectors = $self->connectors; - $self->{config_last_modified} = $self->get_config_last_modified(); - $self->{config_last_checked} = (time); - - foreach my $connector ($connectors->list) { - $connector->backlog->reset_backoff(); - } - - my $pushd_loop = IO::Async::Loop->new; - my $main_timer = IO::Async::Timer::Periodic->new( - first_interval => 0, - interval => POLL_INTERVAL_SECONDS, - reschedule => 'drift', - on_tick => sub { - if ( $self->_dbh_check() ) { - $self->_reload(); - try { - $self->push(); - } - catch { - FATAL($_); - }; - } - }, + my ($self) = @_; + my $connectors = $self->connectors; + $self->{config_last_modified} = $self->get_config_last_modified(); + $self->{config_last_checked} = (time); + + foreach my $connector ($connectors->list) { + $connector->backlog->reset_backoff(); + } + + my $pushd_loop = IO::Async::Loop->new; + my $main_timer = IO::Async::Timer::Periodic->new( + first_interval => 0, + interval => POLL_INTERVAL_SECONDS, + reschedule => 'drift', + on_tick => sub { + if ($self->_dbh_check()) { + $self->_reload(); + try { + $self->push(); + } + catch { + FATAL($_); + }; + } + }, + ); + if (Bugzilla->datadog) { + my $dog_timer = IO::Async::Timer::Periodic->new( + interval => 120, + reschedule => 'drift', + on_tick => sub { $self->heartbeat }, ); - if ( Bugzilla->datadog ) { - my $dog_timer = IO::Async::Timer::Periodic->new( - interval => 120, - reschedule => 'drift', - on_tick => sub { $self->heartbeat }, - ); - $pushd_loop->add($dog_timer); - $dog_timer->start; - } + $pushd_loop->add($dog_timer); + $dog_timer->start; + } - $pushd_loop->add($main_timer); - $main_timer->start; - $pushd_loop->run; + $pushd_loop->add($main_timer); + $main_timer->start; + $pushd_loop->run; } sub heartbeat { - my ($self) = @_; - my $dd = Bugzilla->datadog('bugzilla.pushd'); - - $dd->gauge('scheduled_jobs', Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM push')); - - foreach my $connector ($self->connectors->list) { - if ($connector->enabled) { - my $lcname = lc $connector->name; - $dd->gauge("${lcname}.backlog", Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM push_backlog WHERE connector = ?', undef, $connector->name)); - } + my ($self) = @_; + my $dd = Bugzilla->datadog('bugzilla.pushd'); + + $dd->gauge('scheduled_jobs', + Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM push')); + + foreach my $connector ($self->connectors->list) { + if ($connector->enabled) { + my $lcname = lc $connector->name; + $dd->gauge( + "${lcname}.backlog", + Bugzilla->dbh->selectrow_array( + 'SELECT COUNT(*) FROM push_backlog WHERE connector = ?', undef, + $connector->name + ) + ); } + } } sub push { - my ($self) = @_; - my $logger = $self->logger; - my $connectors = $self->connectors; + my ($self) = @_; + my $logger = $self->logger; + my $connectors = $self->connectors; + + my $enabled = 0; + foreach my $connector ($connectors->list) { + if ($connector->enabled) { + $enabled = 1; + last; + } + } + return unless $enabled; - my $enabled = 0; + $logger->debug("polling"); + + # process each message + while (my $message = $self->queue->oldest) { foreach my $connector ($connectors->list) { - if ($connector->enabled) { - $enabled = 1; - last; + next unless $connector->enabled; + next unless $connector->should_send($message); + $logger->debug("pushing to " . $connector->name); + + my $is_backlogged = $connector->backlog->count; + + if (!$is_backlogged) { + + # connector isn't backlogged, immediate send + $logger->debug("immediate send"); + my ($result, $data); + eval { ($result, $data) = $connector->send($message); }; + if ($@) { + $result = PUSH_RESULT_TRANSIENT; + $data = clean_error($@); } - } - return unless $enabled; - - $logger->debug("polling"); - - # process each message - while(my $message = $self->queue->oldest) { - foreach my $connector ($connectors->list) { - next unless $connector->enabled; - next unless $connector->should_send($message); - $logger->debug("pushing to " . $connector->name); - - my $is_backlogged = $connector->backlog->count; - - if (!$is_backlogged) { - # connector isn't backlogged, immediate send - $logger->debug("immediate send"); - my ($result, $data); - eval { - ($result, $data) = $connector->send($message); - }; - if ($@) { - $result = PUSH_RESULT_TRANSIENT; - $data = clean_error($@); - } - if (!$result) { - $logger->error($connector->name . " failed to return a result code"); - $result = PUSH_RESULT_UNKNOWN; - } - $logger->result($connector, $message, $result, $data); - - if ($result == PUSH_RESULT_TRANSIENT) { - $is_backlogged = 1; - } - } - - # if the connector is backlogged, push to the backlog queue - if ($is_backlogged) { - INFO('connector is backlogged'); - my $backlog = Bugzilla::Extension::Push::BacklogMessage->create_from_message($message, $connector); - } + if (!$result) { + $logger->error($connector->name . " failed to return a result code"); + $result = PUSH_RESULT_UNKNOWN; } + $logger->result($connector, $message, $result, $data); - # message processed - $message->remove_from_db(); + if ($result == PUSH_RESULT_TRANSIENT) { + $is_backlogged = 1; + } + } + + # if the connector is backlogged, push to the backlog queue + if ($is_backlogged) { + INFO('connector is backlogged'); + my $backlog + = Bugzilla::Extension::Push::BacklogMessage->create_from_message($message, + $connector); + } } - # process backlog - foreach my $connector ($connectors->list) { - next unless $connector->enabled; - my $message = $connector->backlog->oldest(); - next unless $message; - - $logger->debug("processing backlog for " . $connector->name); - while ($message) { - my ($result, $data); - eval { - ($result, $data) = $connector->send($message); - }; - if ($@) { - $result = PUSH_RESULT_TRANSIENT; - $data = $@; - } - $message->inc_attempts($result == PUSH_RESULT_OK ? '' : $data); - if (!$result) { - $logger->error($connector->name . " failed to return a result code"); - $result = PUSH_RESULT_UNKNOWN; - } - $logger->result($connector, $message, $result, $data); - - if ($result == PUSH_RESULT_TRANSIENT) { - # connector is still down, stop trying - $connector->backlog->inc_backoff(); - last; - } - - # message was processed - $message->remove_from_db(); - - $message = $connector->backlog->oldest(); - } + # message processed + $message->remove_from_db(); + } + + # process backlog + foreach my $connector ($connectors->list) { + next unless $connector->enabled; + my $message = $connector->backlog->oldest(); + next unless $message; + + $logger->debug("processing backlog for " . $connector->name); + while ($message) { + my ($result, $data); + eval { ($result, $data) = $connector->send($message); }; + if ($@) { + $result = PUSH_RESULT_TRANSIENT; + $data = $@; + } + $message->inc_attempts($result == PUSH_RESULT_OK ? '' : $data); + if (!$result) { + $logger->error($connector->name . " failed to return a result code"); + $result = PUSH_RESULT_UNKNOWN; + } + $logger->result($connector, $message, $result, $data); + + if ($result == PUSH_RESULT_TRANSIENT) { + + # connector is still down, stop trying + $connector->backlog->inc_backoff(); + last; + } + + # message was processed + $message->remove_from_db(); + + $message = $connector->backlog->oldest(); } + } } sub _reload { - my ($self) = @_; - - # check for updated config every 60 seconds - my $now = (time); - if ($now - $self->{config_last_checked} < 60) { - return; - } - $self->{config_last_checked} = $now; - - $self->logger->debug('Checking for updated configuration'); - if ($self->get_config_last_modified eq $self->{config_last_modified}) { - return; - } - $self->{config_last_modified} = $self->get_config_last_modified(); - - $self->logger->debug('Configuration has been updated'); - $self->connectors->reload(); + my ($self) = @_; + + # check for updated config every 60 seconds + my $now = (time); + if ($now - $self->{config_last_checked} < 60) { + return; + } + $self->{config_last_checked} = $now; + + $self->logger->debug('Checking for updated configuration'); + if ($self->get_config_last_modified eq $self->{config_last_modified}) { + return; + } + $self->{config_last_modified} = $self->get_config_last_modified(); + + $self->logger->debug('Configuration has been updated'); + $self->connectors->reload(); } sub get_config_last_modified { - my ($self) = @_; - my $options_list = Bugzilla::Extension::Push::Option->match({ - connector => '*', - option_name => 'last-modified', + my ($self) = @_; + my $options_list + = Bugzilla::Extension::Push::Option->match({ + connector => '*', option_name => 'last-modified', }); - if (@$options_list) { - return $options_list->[0]->value; - } else { - return $self->set_config_last_modified(); - } + if (@$options_list) { + return $options_list->[0]->value; + } + else { + return $self->set_config_last_modified(); + } } sub set_config_last_modified { - my ($self) = @_; - my $options_list = Bugzilla::Extension::Push::Option->match({ - connector => '*', - option_name => 'last-modified', + my ($self) = @_; + my $options_list + = Bugzilla::Extension::Push::Option->match({ + connector => '*', option_name => 'last-modified', }); - my $now = DateTime->now->datetime(); - if (@$options_list) { - $options_list->[0]->set_value($now); - $options_list->[0]->update(); - } else { - Bugzilla::Extension::Push::Option->create({ - connector => '*', - option_name => 'last-modified', - option_value => $now, - }); - } - return $now; + my $now = DateTime->now->datetime(); + if (@$options_list) { + $options_list->[0]->set_value($now); + $options_list->[0]->update(); + } + else { + Bugzilla::Extension::Push::Option->create({ + connector => '*', option_name => 'last-modified', option_value => $now, + }); + } + return $now; } sub config { - my ($self) = @_; - if (!$self->{config}) { - $self->{config} = Bugzilla::Extension::Push::Config->new( - 'global', - { - name => 'log_purge', - label => 'Purge logs older than (days)', - type => 'string', - default => '7', - required => '1', - validate => sub { $_[0] =~ /\D/ && die "Invalid purge duration (must be numeric)\n"; }, - }, - ); - $self->{config}->load(); - } - return $self->{config}; + my ($self) = @_; + if (!$self->{config}) { + $self->{config} = Bugzilla::Extension::Push::Config->new( + 'global', + { + name => 'log_purge', + label => 'Purge logs older than (days)', + type => 'string', + default => '7', + required => '1', + validate => + sub { $_[0] =~ /\D/ && die "Invalid purge duration (must be numeric)\n"; }, + }, + ); + $self->{config}->load(); + } + return $self->{config}; } sub logger { - my ($self, $value) = @_; - $self->{logger} = $value if $value; - return $self->{logger}; + my ($self, $value) = @_; + $self->{logger} = $value if $value; + return $self->{logger}; } sub connectors { - my ($self, $value) = @_; - $self->{connectors} = $value if $value; - return $self->{connectors}; + my ($self, $value) = @_; + $self->{connectors} = $value if $value; + return $self->{connectors}; } sub queue { - my ($self) = @_; - $self->{queue} ||= Bugzilla::Extension::Push::Queue->new(); - return $self->{queue}; + my ($self) = @_; + $self->{queue} ||= Bugzilla::Extension::Push::Queue->new(); + return $self->{queue}; } sub log { - my ($self) = @_; - $self->{log} ||= Bugzilla::Extension::Push::Log->new(); - return $self->{log}; + my ($self) = @_; + $self->{log} ||= Bugzilla::Extension::Push::Log->new(); + return $self->{log}; } sub _dbh_check { - my ($self) = @_; - eval { - Bugzilla->dbh->selectrow_array("SELECT 1 FROM push"); - }; - if ($@) { - $self->logger->error(clean_error($@)); - return 0; - } else { - return 1; - } + my ($self) = @_; + eval { Bugzilla->dbh->selectrow_array("SELECT 1 FROM push"); }; + if ($@) { + $self->logger->error(clean_error($@)); + return 0; + } + else { + return 1; + } } 1; diff --git a/extensions/Push/lib/Queue.pm b/extensions/Push/lib/Queue.pm index 3ee0321d9..f59423e6a 100644 --- a/extensions/Push/lib/Queue.pm +++ b/extensions/Push/lib/Queue.pm @@ -15,59 +15,54 @@ use Bugzilla; use Bugzilla::Extension::Push::Message; sub new { - my ($class) = @_; - my $self = {}; - bless($self, $class); - return $self; + my ($class) = @_; + my $self = {}; + bless($self, $class); + return $self; } sub count { - my ($self) = @_; - my $dbh = Bugzilla->dbh; - return $dbh->selectrow_array("SELECT COUNT(*) FROM push"); + my ($self) = @_; + my $dbh = Bugzilla->dbh; + return $dbh->selectrow_array("SELECT COUNT(*) FROM push"); } sub oldest { - my ($self) = @_; - my @messages = $self->list(limit => 1); - return scalar(@messages) ? $messages[0] : undef; + my ($self) = @_; + my @messages = $self->list(limit => 1); + return scalar(@messages) ? $messages[0] : undef; } sub by_id { - my ($self, $id) = @_; - my @messages = $self->list( - limit => 1, - filter => "AND (push.id = $id)", - ); - return scalar(@messages) ? $messages[0] : undef; + my ($self, $id) = @_; + my @messages = $self->list(limit => 1, filter => "AND (push.id = $id)",); + return scalar(@messages) ? $messages[0] : undef; } sub list { - my ($self, %args) = @_; - $args{limit} ||= 10; - $args{filter} ||= ''; - my @result; - my $dbh = Bugzilla->dbh; + my ($self, %args) = @_; + $args{limit} ||= 10; + $args{filter} ||= ''; + my @result; + my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare(" + my $sth = $dbh->prepare(" SELECT id, push_ts, payload, change_set, routing_key FROM push - WHERE (1 = 1) " . - $args{filter} . " - ORDER BY push_ts " . - $dbh->sql_limit($args{limit}) - ); - $sth->execute(); - while (my $row = $sth->fetchrow_hashref()) { - push @result, Bugzilla::Extension::Push::Message->new({ - id => $row->{id}, - push_ts => $row->{push_ts}, - payload => $row->{payload}, - change_set => $row->{change_set}, - routing_key => $row->{routing_key}, - }); - } - return @result; + WHERE (1 = 1) " . $args{filter} . " + ORDER BY push_ts " . $dbh->sql_limit($args{limit})); + $sth->execute(); + while (my $row = $sth->fetchrow_hashref()) { + push @result, + Bugzilla::Extension::Push::Message->new({ + id => $row->{id}, + push_ts => $row->{push_ts}, + payload => $row->{payload}, + change_set => $row->{change_set}, + routing_key => $row->{routing_key}, + }); + } + return @result; } 1; diff --git a/extensions/Push/lib/Serialise.pm b/extensions/Push/lib/Serialise.pm index bb6834c13..c878ff4d9 100644 --- a/extensions/Push/lib/Serialise.pm +++ b/extensions/Push/lib/Serialise.pm @@ -19,140 +19,145 @@ use Scalar::Util 'blessed'; use JSON (); my $_instance; + sub instance { - $_instance ||= Bugzilla::Extension::Push::Serialise->_new(); - return $_instance; + $_instance ||= Bugzilla::Extension::Push::Serialise->_new(); + return $_instance; } sub _new { - my ($class) = @_; - my $self = {}; - bless($self, $class); - return $self; + my ($class) = @_; + my $self = {}; + bless($self, $class); + return $self; } # given an object, serliase to a hash sub object_to_hash { - my ($self, $object, $is_shallow) = @_; - - my $method = lc(blessed($object)); - $method =~ s/::/_/g; - $method =~ s/^bugzilla//; - return unless $self->can($method); - (my $name = $method) =~ s/^_//; - - # check for a cached hash - my $cache = Bugzilla->request_cache; - my $cache_id = "push." . ($is_shallow ? 'shallow.' : 'deep.') . $object; - if (exists($cache->{$cache_id})) { - return wantarray ? ($cache->{$cache_id}, $name) : $cache->{$cache_id}; - } - - # call the right method to serialise to a hash - my $rh = $self->$method($object, $is_shallow); - - # store in cache - if ($cache_id) { - $cache->{$cache_id} = $rh; - } - - return wantarray ? ($rh, $name) : $rh; + my ($self, $object, $is_shallow) = @_; + + my $method = lc(blessed($object)); + $method =~ s/::/_/g; + $method =~ s/^bugzilla//; + return unless $self->can($method); + (my $name = $method) =~ s/^_//; + + # check for a cached hash + my $cache = Bugzilla->request_cache; + my $cache_id = "push." . ($is_shallow ? 'shallow.' : 'deep.') . $object; + if (exists($cache->{$cache_id})) { + return wantarray ? ($cache->{$cache_id}, $name) : $cache->{$cache_id}; + } + + # call the right method to serialise to a hash + my $rh = $self->$method($object, $is_shallow); + + # store in cache + if ($cache_id) { + $cache->{$cache_id} = $rh; + } + + return wantarray ? ($rh, $name) : $rh; } # given a changes hash, return an event hash sub changes_to_event { - my ($self, $changes) = @_; - - my $event = {}; - - # create common (created and modified) fields - $event->{'user'} = $self->object_to_hash(Bugzilla->user); - my $timestamp = - $changes->{'timestamp'} - || Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); - $event->{'time'} = datetime_to_timestamp($timestamp); - - foreach my $change (@{$changes->{'changes'}}) { - if (exists $change->{'field'}) { - # map undef to emtpy - hash_undef_to_empty($change); - - # custom_fields change from undef to empty, ignore these changes - return if ($change->{'added'} || "") eq "" && - ($change->{'removed'} || "") eq ""; - - # use saner field serialisation - my $field = $change->{'field'}; - $change->{'field'} = $field; - - if ($field eq 'priority' || $field eq 'target_milestone') { - $change->{'added'} = _select($change->{'added'}); - $change->{'removed'} = _select($change->{'removed'}); - - } elsif ($field =~ /^cf_/) { - $change->{'added'} = _custom_field($field, $change->{'added'}); - $change->{'removed'} = _custom_field($field, $change->{'removed'}); - } - - $event->{'changes'} = [] unless exists $event->{'changes'}; - push @{$event->{'changes'}}, $change; - } + my ($self, $changes) = @_; + + my $event = {}; + + # create common (created and modified) fields + $event->{'user'} = $self->object_to_hash(Bugzilla->user); + my $timestamp = $changes->{'timestamp'} + || Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)'); + $event->{'time'} = datetime_to_timestamp($timestamp); + + foreach my $change (@{$changes->{'changes'}}) { + if (exists $change->{'field'}) { + + # map undef to emtpy + hash_undef_to_empty($change); + + # custom_fields change from undef to empty, ignore these changes + return + if ($change->{'added'} || "") eq "" && ($change->{'removed'} || "") eq ""; + + # use saner field serialisation + my $field = $change->{'field'}; + $change->{'field'} = $field; + + if ($field eq 'priority' || $field eq 'target_milestone') { + $change->{'added'} = _select($change->{'added'}); + $change->{'removed'} = _select($change->{'removed'}); + + } + elsif ($field =~ /^cf_/) { + $change->{'added'} = _custom_field($field, $change->{'added'}); + $change->{'removed'} = _custom_field($field, $change->{'removed'}); + } + + $event->{'changes'} = [] unless exists $event->{'changes'}; + push @{$event->{'changes'}}, $change; } + } - return $event; + return $event; } # bugzilla returns '---' or '--' for single-select fields that have no value # selected. it makes more sense to return an empty string. sub _select { - my ($value) = @_; - return '' if $value eq '---' or $value eq '--'; - return $value; + my ($value) = @_; + return '' if $value eq '---' or $value eq '--'; + return $value; } # return an object which serialises to a json boolean, but still acts as a perl # boolean sub _boolean { - my ($value) = @_; - return $value ? JSON::true : JSON::false; + my ($value) = @_; + return $value ? JSON::true : JSON::false; } sub _string { - my ($value) = @_; - return defined($value) ? $value : ''; + my ($value) = @_; + return defined($value) ? $value : ''; } sub _time { - my ($value) = @_; - return defined($value) ? datetime_to_timestamp($value) : undef; + my ($value) = @_; + return defined($value) ? datetime_to_timestamp($value) : undef; } sub _integer { - my ($value) = @_; - return defined($value) ? $value + 0 : undef; + my ($value) = @_; + return defined($value) ? $value + 0 : undef; } sub _array { - my ($value) = @_; - return defined($value) ? $value : []; + my ($value) = @_; + return defined($value) ? $value : []; } sub _custom_field { - my ($field, $value) = @_; - $field = Bugzilla::Field->new({ name => $field }) unless blessed $field; + my ($field, $value) = @_; + $field = Bugzilla::Field->new({name => $field}) unless blessed $field; - if ($field->type == FIELD_TYPE_DATETIME) { - return _time($value); + if ($field->type == FIELD_TYPE_DATETIME) { + return _time($value); - } elsif ($field->type == FIELD_TYPE_SINGLE_SELECT) { - return _select($value); + } + elsif ($field->type == FIELD_TYPE_SINGLE_SELECT) { + return _select($value); - } elsif ($field->type == FIELD_TYPE_MULTI_SELECT) { - return _array($value); + } + elsif ($field->type == FIELD_TYPE_MULTI_SELECT) { + return _array($value); - } else { - return _string($value); - } + } + else { + return _string($value); + } } # @@ -162,158 +167,148 @@ sub _custom_field { # sub _bug { - my ($self, $bug) = @_; - - my $version = $bug->can('version_obj') - ? $bug->version_obj - : Bugzilla::Version->new({ name => $bug->version, product => $bug->product_obj }); - - my $milestone; - if (_select($bug->target_milestone) ne '') { - $milestone = $bug->can('target_milestone_obj') - ? $bug->target_milestone_obj - : Bugzilla::Milestone->new({ name => $bug->target_milestone, product => $bug->product_obj }); - } - - my $status = $bug->can('status_obj') - ? $bug->status_obj - : Bugzilla::Status->new({ name => $bug->bug_status }); - - my $rh = { - id => _integer($bug->bug_id), - alias => _string($bug->alias), - assigned_to => $self->_user($bug->assigned_to), - classification => _string($bug->classification), - component => $self->_component($bug->component_obj), - creation_time => _time($bug->creation_ts || $bug->delta_ts), - flags => (mapr { $self->_flag($_) } $bug->flags), - is_private => _boolean(!is_public($bug)), - keywords => (mapr { _string($_->name) } $bug->keyword_objects), - last_change_time => _time($bug->delta_ts), - operating_system => _string($bug->op_sys), - platform => _string($bug->rep_platform), - priority => _select($bug->priority), - product => $self->_product($bug->product_obj), - qa_contact => $self->_user($bug->qa_contact), - reporter => $self->_user($bug->reporter), - resolution => _string($bug->resolution), - severity => _string($bug->bug_severity), - status => $self->_status($status), - summary => _string($bug->short_desc), - target_milestone => $self->_milestone($milestone), - url => _string($bug->bug_file_loc), - version => $self->_version($version), - whiteboard => _string($bug->status_whiteboard), - }; - - # add custom fields - my @custom_fields = Bugzilla->active_custom_fields( - { product => $bug->product_obj, component => $bug->component_obj }); - foreach my $field (@custom_fields) { - my $name = $field->name; - $rh->{$name} = _custom_field($field, $bug->$name); - } - - return $rh; + my ($self, $bug) = @_; + + my $version + = $bug->can('version_obj') + ? $bug->version_obj + : Bugzilla::Version->new( + {name => $bug->version, product => $bug->product_obj}); + + my $milestone; + if (_select($bug->target_milestone) ne '') { + $milestone + = $bug->can('target_milestone_obj') + ? $bug->target_milestone_obj + : Bugzilla::Milestone->new( + {name => $bug->target_milestone, product => $bug->product_obj}); + } + + my $status + = $bug->can('status_obj') + ? $bug->status_obj + : Bugzilla::Status->new({name => $bug->bug_status}); + + my $rh = { + id => _integer($bug->bug_id), + alias => _string($bug->alias), + assigned_to => $self->_user($bug->assigned_to), + classification => _string($bug->classification), + component => $self->_component($bug->component_obj), + creation_time => _time($bug->creation_ts || $bug->delta_ts), + flags => (mapr { $self->_flag($_) } $bug->flags), + is_private => _boolean(!is_public($bug)), + keywords => (mapr { _string($_->name) } $bug->keyword_objects), + last_change_time => _time($bug->delta_ts), + operating_system => _string($bug->op_sys), + platform => _string($bug->rep_platform), + priority => _select($bug->priority), + product => $self->_product($bug->product_obj), + qa_contact => $self->_user($bug->qa_contact), + reporter => $self->_user($bug->reporter), + resolution => _string($bug->resolution), + severity => _string($bug->bug_severity), + status => $self->_status($status), + summary => _string($bug->short_desc), + target_milestone => $self->_milestone($milestone), + url => _string($bug->bug_file_loc), + version => $self->_version($version), + whiteboard => _string($bug->status_whiteboard), + }; + + # add custom fields + my @custom_fields = Bugzilla->active_custom_fields( + {product => $bug->product_obj, component => $bug->component_obj}); + foreach my $field (@custom_fields) { + my $name = $field->name; + $rh->{$name} = _custom_field($field, $bug->$name); + } + + return $rh; } sub _user { - my ($self, $user) = @_; - return undef unless $user; - return { - id => _integer($user->id), - login => _string($user->login), - real_name => _string($user->name), - }; + my ($self, $user) = @_; + return undef unless $user; + return { + id => _integer($user->id), + login => _string($user->login), + real_name => _string($user->name), + }; } sub _component { - my ($self, $component) = @_; - return { - id => _integer($component->id), - name => _string($component->name), - }; + my ($self, $component) = @_; + return {id => _integer($component->id), name => _string($component->name),}; } sub _attachment { - my ($self, $attachment, $is_shallow) = @_; - my $rh = { - id => _integer($attachment->id), - content_type => _string($attachment->contenttype), - creation_time => _time($attachment->attached), - description => _string($attachment->description), - file_name => _string($attachment->filename), - flags => (mapr { $self->_flag($_) } $attachment->flags), - is_obsolete => _boolean($attachment->isobsolete), - is_patch => _boolean($attachment->ispatch), - is_private => _boolean(!is_public($attachment)), - last_change_time => _time($attachment->modification_time), - }; - if (!$is_shallow) { - $rh->{bug} = $self->_bug($attachment->bug); - } - return $rh; + my ($self, $attachment, $is_shallow) = @_; + my $rh = { + id => _integer($attachment->id), + content_type => _string($attachment->contenttype), + creation_time => _time($attachment->attached), + description => _string($attachment->description), + file_name => _string($attachment->filename), + flags => (mapr { $self->_flag($_) } $attachment->flags), + is_obsolete => _boolean($attachment->isobsolete), + is_patch => _boolean($attachment->ispatch), + is_private => _boolean(!is_public($attachment)), + last_change_time => _time($attachment->modification_time), + }; + if (!$is_shallow) { + $rh->{bug} = $self->_bug($attachment->bug); + } + return $rh; } sub _comment { - my ($self, $comment, $is_shallow) = @_; - my $rh = { - id => _integer($comment->bug_id), - body => _string($comment->body), - creation_time => _time($comment->creation_ts), - is_private => _boolean($comment->is_private), - number => _integer($comment->count), - }; - if (!$is_shallow) { - $rh->{bug} = $self->_bug($comment->bug); - } - return $rh; + my ($self, $comment, $is_shallow) = @_; + my $rh = { + id => _integer($comment->bug_id), + body => _string($comment->body), + creation_time => _time($comment->creation_ts), + is_private => _boolean($comment->is_private), + number => _integer($comment->count), + }; + if (!$is_shallow) { + $rh->{bug} = $self->_bug($comment->bug); + } + return $rh; } sub _product { - my ($self, $product) = @_; - return { - id => _integer($product->id), - name => _string($product->name), - }; + my ($self, $product) = @_; + return {id => _integer($product->id), name => _string($product->name),}; } sub _flag { - my ($self, $flag) = @_; - my $rh = { - id => _integer($flag->id), - name => _string($flag->type->name), - value => _string($flag->status), - }; - if ($flag->requestee) { - $rh->{'requestee'} = $self->_user($flag->requestee); - } - return $rh; + my ($self, $flag) = @_; + my $rh = { + id => _integer($flag->id), + name => _string($flag->type->name), + value => _string($flag->status), + }; + if ($flag->requestee) { + $rh->{'requestee'} = $self->_user($flag->requestee); + } + return $rh; } sub _version { - my ($self, $version) = @_; - return { - id => _integer($version->id), - name => _string($version->name), - }; + my ($self, $version) = @_; + return {id => _integer($version->id), name => _string($version->name),}; } sub _milestone { - my ($self, $milestone) = @_; - return undef unless $milestone; - return { - id => _integer($milestone->id), - name => _string($milestone->name), - }; + my ($self, $milestone) = @_; + return undef unless $milestone; + return {id => _integer($milestone->id), name => _string($milestone->name),}; } sub _status { - my ($self, $status) = @_; - return { - id => _integer($status->id), - name => _string($status->name), - }; + my ($self, $status) = @_; + return {id => _integer($status->id), name => _string($status->name),}; } 1; diff --git a/extensions/Push/lib/Util.pm b/extensions/Push/lib/Util.pm index bda6331bf..34a0879ea 100644 --- a/extensions/Push/lib/Util.pm +++ b/extensions/Push/lib/Util.pm @@ -22,142 +22,147 @@ use Time::HiRes; use base qw(Exporter); our @EXPORT = qw( - datetime_to_timestamp - debug_dump - get_first_value - hash_undef_to_empty - is_public - mapr - clean_error - change_set_id - canon_email - to_json from_json + datetime_to_timestamp + debug_dump + get_first_value + hash_undef_to_empty + is_public + mapr + clean_error + change_set_id + canon_email + to_json from_json ); # returns true if the specified object is public sub is_public { - my ($object) = @_; + my ($object) = @_; - my $default_user = Bugzilla::User->new(); + my $default_user = Bugzilla::User->new(); - if ($object->isa('Bugzilla::Bug')) { - return unless $default_user->can_see_bug($object->bug_id); - return 1; + if ($object->isa('Bugzilla::Bug')) { + return unless $default_user->can_see_bug($object->bug_id); + return 1; - } elsif ($object->isa('Bugzilla::Comment')) { - return if $object->is_private; - return unless $default_user->can_see_bug($object->bug_id); - return 1; + } + elsif ($object->isa('Bugzilla::Comment')) { + return if $object->is_private; + return unless $default_user->can_see_bug($object->bug_id); + return 1; - } elsif ($object->isa('Bugzilla::Attachment')) { - return if $object->isprivate; - return unless $default_user->can_see_bug($object->bug_id); - return 1; + } + elsif ($object->isa('Bugzilla::Attachment')) { + return if $object->isprivate; + return unless $default_user->can_see_bug($object->bug_id); + return 1; - } else { - warn "Unsupported class " . blessed($object) . " passed to is_public()\n"; - } + } + else { + warn "Unsupported class " . blessed($object) . " passed to is_public()\n"; + } - return 1; + return 1; } # return the first existing value from the hashref for the given list of keys sub get_first_value { - my ($rh, @keys) = @_; - foreach my $field (@keys) { - return $rh->{$field} if exists $rh->{$field}; - } - return; + my ($rh, @keys) = @_; + foreach my $field (@keys) { + return $rh->{$field} if exists $rh->{$field}; + } + return; } # wrapper for map that works on array references sub mapr(&$) { - my ($filter, $ra) = @_; - my @result = map(&$filter, @$ra); - return \@result; + my ($filter, $ra) = @_; + my @result = map(&$filter, @$ra); + return \@result; } # convert datetime string (from db) to a UTC json friendly datetime sub datetime_to_timestamp { - my ($datetime_string) = @_; - return '' unless $datetime_string; - return datetime_from($datetime_string, 'UTC')->datetime(); + my ($datetime_string) = @_; + return '' unless $datetime_string; + return datetime_from($datetime_string, 'UTC')->datetime(); } # replaces all undef values in a hashref with an empty string (deep) sub hash_undef_to_empty { - my ($rh) = @_; - foreach my $key (keys %$rh) { - my $value = $rh->{$key}; - if (!defined($value)) { - $rh->{$key} = ''; - } elsif (ref($value) eq 'HASH') { - hash_undef_to_empty($value); - } + my ($rh) = @_; + foreach my $key (keys %$rh) { + my $value = $rh->{$key}; + if (!defined($value)) { + $rh->{$key} = ''; + } + elsif (ref($value) eq 'HASH') { + hash_undef_to_empty($value); } + } } # debugging methods sub debug_dump { - my ($object) = @_; - local $Data::Dumper::Sortkeys = 1; - my $output = Dumper($object); - $output =~ s/</</g; - print "<pre>$output</pre>"; + my ($object) = @_; + local $Data::Dumper::Sortkeys = 1; + my $output = Dumper($object); + $output =~ s/</</g; + print "<pre>$output</pre>"; } # removes stacktrace and "at /some/path ..." from errors sub clean_error { - my ($error) = @_; - my $path = bz_locations->{'extensionsdir'}; - $error = $1 if $error =~ /^(.+?) at \Q$path/s; - $path = '/loader/0x'; - $error = $1 if $error =~ /^(.+?) at \Q$path/s; - $error =~ s/(^\s+|\s+$)//g; - return $error; + my ($error) = @_; + my $path = bz_locations->{'extensionsdir'}; + $error = $1 if $error =~ /^(.+?) at \Q$path/s; + $path = '/loader/0x'; + $error = $1 if $error =~ /^(.+?) at \Q$path/s; + $error =~ s/(^\s+|\s+$)//g; + return $error; } # generate a new change_set id sub change_set_id { - return "$$." . Time::HiRes::time(); + return "$$." . Time::HiRes::time(); } # remove guff from email addresses sub clean_email { - my $email = shift; - $email = trim($email); - $email = $1 if $email =~ /^(\S+)/; - $email =~ s/@/@/; - $email = lc $email; - return $email; + my $email = shift; + $email = trim($email); + $email = $1 if $email =~ /^(\S+)/; + $email =~ s/@/@/; + $email = lc $email; + return $email; } # resolve to canonised email form # eg. glob+bmo@mozilla.com --> glob@mozilla.com sub canon_email { - my $email = shift; - $email = clean_email($email); - $email =~ s/^([^\+]+)\+[^\@]+(\@.+)$/$1$2/; - return $email; + my $email = shift; + $email = clean_email($email); + $email =~ s/^([^\+]+)\+[^\@]+(\@.+)$/$1$2/; + return $email; } # json helpers sub to_json { - my ($object, $pretty) = @_; - if ($pretty) { - return decode('utf8', JSON->new->utf8(1)->pretty(1)->encode($object)); - } else { - return JSON->new->ascii(1)->shrink(1)->encode($object); - } + my ($object, $pretty) = @_; + if ($pretty) { + return decode('utf8', JSON->new->utf8(1)->pretty(1)->encode($object)); + } + else { + return JSON->new->ascii(1)->shrink(1)->encode($object); + } } sub from_json { - my ($json) = @_; - if (utf8::is_utf8($json)) { - $json = encode('utf8', $json); - } - return JSON->new->utf8(1)->decode($json); + my ($json) = @_; + if (utf8::is_utf8($json)) { + $json = encode('utf8', $json); + } + return JSON->new->utf8(1)->decode($json); } 1; |