ensembl-hive  2.5
Slack.pm
Go to the documentation of this file.
1 =head1 LICENSE
2 
3 Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
4 Copyright [2016-2022] EMBL-European Bioinformatics Institute
5 
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9 
10  http://www.apache.org/licenses/LICENSE-2.0
11 
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 
18 =cut
19 
20 =pod
21 
22 =head1 NAME
23 
25 
26 =head1 DESCRIPTION
27 
28 A library to interface eHive with Slack
29 
30 =cut
31 
32 package Bio::EnsEMBL::Hive::Utils::Slack;
33 
34 use strict;
35 use warnings;
36 
37 use Exporter 'import';
38 our @EXPORT_OK = qw(send_message_to_slack send_beekeeper_message_to_slack);
39 
40 
41 =head2 send_message_to_slack
42 
43  Description: A core method to send a message to a Slack webhook.
44  The payload should be a hash-structure that can be encoded in
45  JSON and follows Slack's message structure. See more details at
46  <https://api.slack.com/incoming-webhooks> and
47  <https://api.slack.com/docs/formatting>
48 
49 =cut
50 
51 sub send_message_to_slack {
52  my ($slack_webhook, $payload) = @_;
53 
54  require HTTP::Request::Common;
55  require LWP::UserAgent;
56  require JSON;
57 
58  # Fix the channel name (it *must* start with a hash)
59  $payload->{'channel'} = '#'.$payload->{'channel'} if ($payload->{'channel'} || '') =~ /^[^#@]/;
60 
61  my $json_payload = JSON::encode_json($payload);
62  my $req = HTTP::Request::Common::POST($slack_webhook, ['payload' => $json_payload]);
63 
64  my $ua = LWP::UserAgent->new;
65  $ua->timeout(15);
66  my $resp = $ua->request($req);
67 
68  if ($resp->is_success) {
69  # well done
70  } else {
71  # All taken from https://api.slack.com/changelog/2016-05-17-changes-to-errors-for-incoming-webhooks
72  if ($resp->code == 400) {
73  if ($resp->content eq 'invalid_payload') {
74  _pretty_die($resp, 'The data sent in your request cannot be understood as presented; verify your content body matches your content type and is structurally valid'. "\n$json_payload");
75  } elsif ($resp->content eq 'user_not_found') {
76  _pretty_die($resp, 'The user used in your request does not actually exist' . "\n$json_payload");
77  }
78  } elsif ($resp->code == 403) {
79  if ($resp->content eq 'action_prohibited') {
80  _pretty_die($resp, 'The team associated with your request has some kind of restriction on the webhook posting in this context.'. "\n$json_payload");
81  }
82  } elsif ($resp->code == 404) {
83  if ($resp->content eq 'channel_not_found') {
84  _pretty_die($resp, sprintf('The channel associated with your request (%s) does not exist', $payload->{'channel'}));
85  }
86  } elsif ($resp->code == 405) {
87  if ($resp->content eq 'channel_is_archived') {
88  _pretty_die($resp, sprintf(q{The channel '%s' has been archived and doesn't accept further messages, even from your incoming webhook.}, $payload->{'channel'}));
89  }
90  } elsif ($resp->code == 500) {
91  if ($resp->content eq 'rollup_error') {
92  _pretty_die($resp, 'Something strange and unusual happened that was likely not your fault at all.'. "\n$json_payload");
93  }
94  }
95  _pretty_die($resp, $resp->content);
96  }
97 }
98 
99 
100 =head2 _pretty_die
101 
102  Description: Helper method to have uniform error messages
103 
104 =cut
105 
106 sub _pretty_die {
107  my ($resp, $explanation) = @_;
108  die sprintf("Got a %d error (%s): %s\n", $resp->code, $resp->message, $explanation);
109 }
110 
111 
112 =head2 send_beekeeper_message_to_slack
113 
114  Arg [1] : $slack_webhook (string URL)
115  Arg [2] : $hive_pipeline (Bio::EnsEMBL::Hive::HivePipeline)
116  Arg [3] : $is_error (non-zero if message should be displayed as an error)
117  Arg [4] : $is_exit (non-zero if message should be displayed as an exit -
118  a non-zero $is_error overrides $is_exit)
119  Arg [5] : $beekeeper_message (string)
120  Arg [6] : (optional) $loop_until (string) beekeeper's loop_until setting
121  Description : Formats and packages a message from the beekeeper, then sends to Slack.
122 
123 =cut
124 
125 sub send_beekeeper_message_to_slack {
126  my ($slack_webhook, $hive_pipeline, $is_error, $is_exit, $beekeeper_message, $loop_until) = @_;
127 
128  my @attachments;
129  my $error_fallback = "this beekeeper has detected an error condition";
130  my $exit_fallback = "this beekeeper has stopped";
131 
132  $beekeeper_message =~ s/###,/###\n/g;
133  if ($loop_until) {
134  $beekeeper_message .= "\nBeekeeper's loop_until set to '$loop_until'";
135  }
136  if ($is_error) {
137  push @attachments, {
138  'color' => 'danger',
139  'fallback' => $error_fallback,
140  'title' => 'Beekeeper encountered an error',
141  'text' => $beekeeper_message,
142  }
143  } elsif ($is_exit) {
144  push @attachments, {
145  'color' => 'warning',
146  'fallback' => $exit_fallback,
147  'title' => 'Beekeeper has exited',
148  'text' => $beekeeper_message,
149  }
150  } else {
151  # FIXME: this can never happen because $is_exit is always set to 1
152  push @attachments, {
153  'color' => 'good',
154  'fallback' => 'beekeeper sent a non-error, non-exit message',
155  'title' => 'Beekeeper message',
156  'text' => $beekeeper_message,
157  }
158  }
159 
160  my $dbc = $hive_pipeline->hive_dba()->dbc();
161  my $payload = {
162  'text' => sprintf('Message from %s@%s:%s', $hive_pipeline->hive_pipeline_name, $dbc->host, $dbc->port),
163  'attachments' => \@attachments,
164  };
165  send_message_to_slack($slack_webhook, $payload);
166 }
167 
168 1;