From a688b5290fada147874dab2f34ebdcff2742208a Mon Sep 17 00:00:00 2001 From: UrBnW <40244829+UrBnW@users.noreply.github.com> Date: Wed, 5 Feb 2020 15:40:22 +0100 Subject: [PATCH] Add Sirportly notification plugin --- notification/sirportly/mode/ticket.pm | 368 ++++++++++++++++++++++++++ notification/sirportly/plugin.pm | 48 ++++ 2 files changed, 416 insertions(+) create mode 100644 notification/sirportly/mode/ticket.pm create mode 100644 notification/sirportly/plugin.pm diff --git a/notification/sirportly/mode/ticket.pm b/notification/sirportly/mode/ticket.pm new file mode 100644 index 000000000..9e488432c --- /dev/null +++ b/notification/sirportly/mode/ticket.pm @@ -0,0 +1,368 @@ +# +# Copyright 2020 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package notification::sirportly::mode::ticket; + +use base qw(centreon::plugins::mode); + +use strict; +use warnings; +use centreon::plugins::http; +use centreon::plugins::statefile; +use JSON::XS; + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $options{options}->add_options(arguments => { + 'hostname:s' => { name => 'hostname', default => 'api.sirportly.com' }, + 'port:s' => { name => 'port', default => 443 }, + 'proto:s' => { name => 'proto', default => 'https' }, + 'api-token:s' => { name => 'api_token' }, + 'api-secret:s' => { name => 'api_secret' }, + 'cache' => { name => 'cache' }, + 'contact-name:s' => { name => 'contact_name', default => 'Centreon Notifications' }, + 'contact-type:s' => { name => 'contact_type', default => 'telephone' }, + 'contact-data:s' => { name => 'contact_data', default => '-' }, + 'brand:s' => { name => 'brand' }, + 'department:s' => { name => 'department' }, + 'submit-user:s' => { name => 'submit_user' }, + 'close-user:s' => { name => 'close_user' }, + 'submit-status:s' => { name => 'submit_status' }, + 'close-status:s' => { name => 'close_status' }, + 'priority-mapping:s' => { name => 'priority_mapping' }, + 'priority:s' => { name => 'priority' }, + 'item-id:s' => { name => 'item_id' }, + 'subject:s' => { name => 'subject' }, + 'message:s' => { name => 'message' }, + 'timeout:s' => { name => 'timeout' }, + }); + + $self->{http} = centreon::plugins::http->new(%options); + $self->{cache} = centreon::plugins::statefile->new(%options); + + return $self; +} + + +sub check_options { + my ($self, %options) = @_; + + $self->SUPER::init(%options); + if (!defined($self->{option_results}->{api_token})) { + $self->{output}->add_option_msg(short_msg => "You need to set --api-token option"); + $self->{output}->option_exit(); + } + if (!defined($self->{option_results}->{api_secret})) { + $self->{output}->add_option_msg(short_msg => "You need to set --api-secret option"); + $self->{output}->option_exit(); + } + if (!defined($self->{option_results}->{brand})) { + $self->{output}->add_option_msg(short_msg => "You need to set --brand option"); + $self->{output}->option_exit(); + } + if (!defined($self->{option_results}->{department})) { + $self->{output}->add_option_msg(short_msg => "You need to set --department option"); + $self->{output}->option_exit(); + } + if (!defined($self->{option_results}->{submit_status})) { + $self->{output}->add_option_msg(short_msg => "You need to set --submit-status option"); + $self->{output}->option_exit(); + } + if (!defined($self->{option_results}->{close_status})) { + $self->{output}->add_option_msg(short_msg => "You need to set --close-status option"); + $self->{output}->option_exit(); + } + if (!defined($self->{option_results}->{priority})) { + $self->{output}->add_option_msg(short_msg => "You need to set --priority option"); + $self->{output}->option_exit(); + } + if (!defined($self->{option_results}->{item_id})) { + $self->{output}->add_option_msg(short_msg => "You need to set --item-id option"); + $self->{output}->option_exit(); + } + if (!defined($self->{option_results}->{subject})) { + $self->{output}->add_option_msg(short_msg => "You need to set --subject option"); + $self->{output}->option_exit(); + } + if (!defined($self->{option_results}->{message})) { + $self->{output}->add_option_msg(short_msg => "You need to set --message option"); + $self->{output}->option_exit(); + } + + $self->{http}->set_options(%{$self->{option_results}}); + $self->{cache}->check_options(option_results => $self->{option_results}); +} + +sub proceed { + my ($self, %options) = @_; + + # Let's compute a priority mapping hash, with lc keys + my %priority_mapping; + if (defined($self->{option_results}->{priority_mapping})) { + my $i = 0; + %priority_mapping = map { $i++ % 2 ? $_ : lc } split /[:,]/, $self->{option_results}->{priority_mapping}; + } + + # Let's compute a ticket tag to be able to find the opened ticket later on + my $tag = $options{item_id}; + if ($options{priority} =~ /^(UP|DOWN|UNREACHABLE)$/i) { + $tag = "centreon-hst:$tag"; + } else { + $tag = "centreon-svc:$tag"; + } + + # Is there an existing Sirportly ticket for this item ? + my $post_param = [ + "spql=SELECT tickets.reference,statuses.name FROM tickets WHERE ticket_tags.tag=\"$tag\" AND statuses.name!=\"$self->{option_results}->{close_status}\" ORDER BY tickets.submitted_at DESC LIMIT 1", + ]; + my $response = $self->{http}->request(method => 'POST', url_path =>'/api/v2/tickets/spql', post_param => $post_param, warning_status => '', unknown_status => '', critical_status => ''); + my $ticket_info; + eval { + $ticket_info = JSON::XS->new->decode($response)->{results}[0]; + }; + + # Close the ticket + if ($options{priority} =~ /^(UP|OK)$/i) { + if (defined($ticket_info)) { + $post_param = [ + "ticket=$ticket_info->[0]", + "contact=$self->{option_results}->{contact_name}", + "message=$options{message}", + ]; + $self->{http}->request(method => 'POST', url_path =>'/api/v2/tickets/post_update', post_param => $post_param, warning_status => '', unknown_status => '', critical_status => ''); + if ($ticket_info->[1] eq $self->{option_results}->{submit_status}) { + $post_param = [ + "ticket=$ticket_info->[0]", + "user=" . (defined($self->{option_results}->{close_user}) ? $self->{option_results}->{close_user} : ""), + "status=$self->{option_results}->{close_status}", + ]; + my $decoded = $self->{http}->request(method => 'POST', url_path =>'/api/v2/tickets/update', post_param => $post_param); + eval { + $decoded = JSON::XS->new->decode($response); + }; + if ($@) { + $ticket_info->[0] = "failed"; + } + } + } + } + + # Update an existing ticket + elsif (defined($ticket_info)) { + $post_param = [ + "ticket=$ticket_info->[0]", + "contact=$self->{option_results}->{contact_name}", + "message=$options{message}", + ]; + $self->{http}->request(method => 'POST', url_path =>'/api/v2/tickets/post_update', post_param => $post_param, warning_status => '', unknown_status => '', critical_status => ''); + $post_param = [ + "ticket=$ticket_info->[0]", + "priority=" . (defined($priority_mapping{lc($options{priority})}) ? $priority_mapping{lc($options{priority})} : $options{priority}), + ]; + my $decoded = $self->{http}->request(method => 'POST', url_path =>'/api/v2/tickets/update', post_param => $post_param); + eval { + $decoded = JSON::XS->new->decode($response); + }; + if ($@) { + $ticket_info->[0] = "failed"; + } + } + + # Open a new ticket + else { + $post_param = [ + "contact_name=$self->{option_results}->{contact_name}", + "contact_method_type=$self->{option_results}->{contact_type}", + "contact_method_data=$self->{option_results}->{contact_data}", + "brand=$self->{option_results}->{brand}", + "department=$self->{option_results}->{department}", + "user=" . (defined($self->{option_results}->{submit_user}) ? $self->{option_results}->{submit_user} : ""), + "status=$self->{option_results}->{submit_status}", + "priority=" . (defined($priority_mapping{lc($options{priority})}) ? $priority_mapping{lc($options{priority})} : $options{priority}), + "tag_list=$tag", + "subject=$options{subject}", + "message=$options{message}", + ]; + my $response = $self->{http}->request(method => 'POST', url_path =>'/api/v2/tickets/submit', post_param => $post_param, warning_status => '', unknown_status => '', critical_status => ''); + my $decoded; + eval { + $decoded = JSON::XS->new->decode($response); + }; + if ($@ || !defined($decoded->{reference})) { + $ticket_info->[0] = "failed"; + } else { + $ticket_info->[0] = $decoded->{reference}; + } + } + + # Return proceeded ticket + return $ticket_info->[0]; +} + +sub run { + my ($self, %options) = @_; + + # Authentication headers + $self->{http}->add_header(key => 'X-Auth-Token', value => $self->{option_results}->{api_token}); + $self->{http}->add_header(key => 'X-Auth-Secret', value => $self->{option_results}->{api_secret}); + + # Load cache + $self->{cache}->read(statefile => 'sirportly_api_' . $self->{option_results}->{api_token}); + my $cache; + if (defined($self->{option_results}->{cache})) { + $cache = $self->{cache}->get(name => 'cache'); + } else { + $cache = {}; + } + + # Add current item to cache + my $cache_index = time(); + $cache->{$cache_index}->{item_id} = $self->{option_results}->{item_id}; + $cache->{$cache_index}->{priority} = $self->{option_results}->{priority}; + $cache->{$cache_index}->{subject} = $self->{option_results}->{subject}; + $cache->{$cache_index}->{message} = $self->{option_results}->{message}; + $self->{cache}->write(data => {cache => $cache}); + + # Proceed cached notifications + my $cache_size = keys %{$cache}; + my $cache_done = 0; + foreach $cache_index (sort keys %{$cache}) { + my $ticket_reference = $self->proceed( + priority => $cache->{$cache_index}->{priority}, + item_id => $cache->{$cache_index}->{item_id}, + subject => $cache->{$cache_index}->{subject}, + message => $cache->{$cache_index}->{message}, + ); + if (!defined($ticket_reference) || $ticket_reference ne "failed") { + delete($cache->{$cache_index}); + $self->{cache}->write(data => {cache => $cache}); + $cache_done++; + } else { + last; + } + } + + # Exit + $self->{output}->output_add(severity => ($cache_size == $cache_done) ? 'OK' : 'CRITICAL', + short_msg => "Proceeded " . $cache_done . "/" . $cache_size . " notifications"); + $self->{output}->display(force_ignore_perfdata => 1); + $self->{output}->exit(); +} + +1; + +__END__ + +=head1 MODE + +Open tickets via Sirportly API (https://sirportly.com/docs/api-specification/) + +=over 6 + +=item B<--hostname> + +Hostname of the OVH SMS API (default: 'api.sirportly.com'). + +=item B<--port> + +Port used by API (default: '443'). + +=item B<--proto> + +Specify https if needed (Default: 'https'). + +=item B<--api-token> + +Specify the API authentication token. + +=item B<--api-secret> + +Specify the API authentication secret. + +=item B<--cache> + +Cache notifications not correctly delivered, to retry at next run. + +=item B<--contact-name> + +Specify the contact name for this ticket (default: 'Centreon Notifications'). + +=item B<--contact-type> + +Specify the contact type (email or telephone) of the contact (default: 'telephone'). + +=item B<--contact-data> + +Specify the email address or telephone number of the contact (default: '-'). + +=item B<--brand> + +Specify the brand for this ticket. + +=item B<--department> + +Specify the department for this ticket. + +=item B<--submit-user> + +Specify the user to assign the ticket to when submitting the ticket. + +=item B<--close-user> + +Specify the user to assign the ticket to when closing the ticket. + +=item B<--submit-status> + +Specify the Sirportly status to be used when submitting the ticket. + +=item B<--close-status> + +Specify the Sirportly status to be used when closing the ticket. + +=item B<--priority-mapping> + +Map the Centreon priorities to Sirportly ones, comma separated (syntax: (down|unreachable|warning|critical|unknown):priority). + +=item B<--priority> + +Specify the priority for this ticket. + +=item B<--item-id> + +Specify the host or service ID for this ticket. + +=item B<--subject> + +Specify the subject for this ticket. + +=item B<--message> + +Specify the message for this ticket. + +=item B<--timeout> + +Threshold for HTTP timeout + +=back + +=cut diff --git a/notification/sirportly/plugin.pm b/notification/sirportly/plugin.pm new file mode 100644 index 000000000..347dfbffa --- /dev/null +++ b/notification/sirportly/plugin.pm @@ -0,0 +1,48 @@ +# +# Copyright 2020 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package notification::sirportly::plugin; + +use strict; +use warnings; +use base qw(centreon::plugins::script_simple); + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $self->{version} = '1.0'; + %{$self->{modes}} = ( + 'ticket' => 'notification::sirportly::mode::ticket', + ); + + return $self; +} + +1; + +__END__ + +=head1 PLUGIN DESCRIPTION + +Open tickets via Sirportly API. + +=cut