File Coverage

blib/lib/Monitis.pm
Criterion Covered Total %
statement 101 189 53.4
branch 20 64 31.2
condition 9 45 20.0
subroutine 23 30 76.6
pod 16 16 100.0
total 169 344 49.1


line stmt bran cond sub pod time code
1             package Monitis;
2              
3 2     2   46596 use warnings;
  2         5  
  2         80  
4 2     2   10 use strict;
  2         4  
  2         83  
5              
6 2     2   2228 use LWP::UserAgent;
  2         102427  
  2         63  
7 2     2   18 use HTTP::Request;
  2         4  
  2         46  
8 2     2   9 use HTTP::Headers;
  2         4  
  2         45  
9 2     2   1922 use HTTP::Request::Common;
  2         4253  
  2         173  
10              
11 2     2   1770 use Digest::SHA 'hmac_sha1_base64';
  2         8155  
  2         217  
12 2     2   2081 use JSON;
  2         26738  
  2         12  
13              
14             our $VERSION = '0.9';
15              
16 2   50 2   383 use constant DEBUG => $ENV{MONITIS_DEBUG} || 0;
  2         6  
  2         4510  
17              
18             require Carp;
19              
20             our $TOKEN_TTL = $ENV{MONITIS_TOKEN_TTL} || 60 * 60 * 24; # 24 hours
21             my $API_VERSION = 2;
22              
23             our $MAPPING = {
24             sub_accounts => 'SubAccounts',
25             layout => 'Layout',
26             contacts => 'Contacts',
27             predefined_monitors => 'PredefinedMonitors',
28             external_monitors => 'ExternalMonitors',
29             internal_monitors => 'InternalMonitors',
30             agents => 'Agents',
31             cpu => 'CPU',
32             memory => 'Memory',
33             drive => 'Drive',
34             process => 'Process',
35             load_average => 'LoadAverage',
36             http => 'HTTP',
37             ping => 'Ping',
38             transaction_monitors => 'TransactionMonitors',
39             custom_monitors => 'CustomMonitors',
40             full_page_load_monitors => 'FullPageLoadMonitors',
41             visitor_trackers => 'VisitorTrackers',
42             cloud_instances => 'CloudInstances'
43             };
44              
45             our $MAPPING_LOADED = {};
46              
47             sub new {
48 1     1 1 653 my $class = shift;
49 1         2 my $self = {@_};
50              
51 1   33     15 $self->{ua} ||= LWP::UserAgent->new(agent => "perl-monitis-api/$VERSION");
52              
53 1   33     2996 $self->{json} ||= JSON->new;
54              
55 1         8 bless $self, $class;
56             }
57              
58             sub auth_token {
59 0     0 1 0 my ($self, $token, $expires) = @_;
60              
61             # Set
62 0 0 0     0 if ($token && ref $token ne 'CODE') {
63 0         0 $self->{auth_token} = $token;
64 0   0     0 $self->{auth_token_expires} = $expires || $self->token_ttl + time;
65 0         0 return $self;
66             }
67              
68 0 0       0 my $callback = ref $token eq 'CODE' ? $token : undef;
69              
70             # Cached token
71 0 0 0     0 if ($self->{auth_token} && $self->{auth_token_expires} > time) {
72 0 0       0 return $callback->($self, $self->{auth_token}) if $callback;
73 0         0 return $self->{auth_token};
74             }
75              
76             # Token expired
77 0         0 delete $self->{auth_token};
78 0         0 delete $self->{auth_token_expires};
79              
80 0 0 0     0 unless ($self->api_key && $self->secret_key) {
81 0         0 Carp::croak("API key and Secret key required for this action");
82             }
83              
84 0         0 my $uri = URI->new($self->api_url);
85              
86 0         0 $uri->query_form(
87             action => 'authToken',
88             output => 'json',
89             version => $API_VERSION,
90             secretkey => $self->secret_key,
91             apikey => $self->api_key
92             );
93              
94 0         0 my $response = $self->ua->get($uri);
95              
96 0 0       0 unless ($response->is_success) {
97 0         0 die "Failed to get auth token: " . $response->status_line;
98             }
99              
100 0         0 my $result = $self->json->decode($response->decoded_content);
101              
102 0 0 0     0 unless ($result || exists $result->{authToken}) {
103 0         0 die "Failed to get auth token, wrong response:\n"
104             . $response->decoded_content . "\n";
105             }
106              
107             #Success
108 0         0 $self->auth_token($result->{authToken});
109              
110 0         0 $result->{authToken};
111             }
112              
113             sub api_get {
114 0     0 1 0 my $self = shift;
115 0         0 my $request = $self->build_get_request(@_);
116              
117 0         0 warn "GET>\n" if DEBUG;
118 0         0 warn $request->as_string if DEBUG;
119              
120 0         0 my $response = $self->ua->request($request);
121              
122 0         0 warn "GET<\n" if DEBUG;
123 0         0 warn $response->decoded_content if DEBUG;
124              
125 0         0 $self->parse_response($response);
126              
127             }
128              
129             sub api_post {
130 0     0 1 0 my $self = shift;
131 0         0 my $request = $self->build_post_request(@_);
132              
133 0         0 warn "POST>\n" if DEBUG;
134 0         0 warn $request->as_string if DEBUG;
135              
136 0         0 my $response = $self->ua->request($request);
137              
138 0         0 warn "POST<\n" if DEBUG;
139 0         0 warn $response->decoded_content if DEBUG;
140              
141 0         0 $self->parse_response($response);
142             }
143              
144             sub parse_response {
145 1     1 1 647 my ($self, $res) = @_;
146              
147 1         5 my $obj = $self->json->decode($res->decoded_content);
148              
149 1 50       314 Carp::croak("Wrong responce: " . $res->decoded_content) unless $obj;
150              
151 1         4 $obj;
152             }
153              
154             sub build_get_request {
155 1     1 1 7 my ($self, $action, $params) = @_;
156 1   50     4 $params ||= [];
157              
158 1 50       4 unless ($self->api_key) {
159 0         0 Carp::croak("API key and Secret key required for this action");
160             }
161              
162 1         4 my @auth = (
163             apikey => $self->api_key,
164             output => 'JSON',
165             version => $API_VERSION,
166             );
167 1         6 my $url = URI->new($self->api_url);
168 1         10693 $url->query_form(@$params, @auth, action => $action);
169              
170 1         292 HTTP::Request::Common::GET($url);
171             }
172              
173             sub build_post_request {
174 1     1 1 22 my ($self, $action, $params) = @_;
175 1   50     10 $params ||= [];
176              
177 1 50 33     5 unless ($self->api_key && $self->secret_key) {
178 0         0 Carp::croak("API key and Secret key required for this action");
179             }
180              
181 1         56 my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time);
182 1         9 my $timestamp = sprintf "%4d-%02d-%02d %02d:%02d:%02d",
183             $year + 1900, $mon + 1, $mday, $hour, $min, $sec;
184              
185 1         8 my @auth = (
186             apikey => $self->api_key,
187             validation => 'HMACSHA1',
188             output => 'JSON',
189             version => $API_VERSION,
190             timestamp => $timestamp
191             );
192              
193 1         6 my @final_params = (@$params, @auth, 'action' => $action);
194              
195 1         6 push @final_params, checksum => $self->checksum(\@final_params);
196              
197 1         6 HTTP::Request::Common::POST($self->api_url => \@final_params);
198             }
199              
200             sub checksum {
201 1     1 1 4 my ($self, $parameters) = @_;
202              
203 1 50       3 Carp::croak("Monitis secret key required for checksum generation")
204             unless $self->secret_key;
205              
206 1         2 my @sorted_params;
207              
208 1         6 for (my $i = 0; $i <= $#$parameters; $i += 2) {
209 8         14 push @sorted_params, [@{$parameters}[$i, $i + 1]];
  8         46  
210             }
211              
212 17 50       40 @sorted_params =
213 1         6 sort { $a->[0] cmp $b->[0] or $a->[1] cmp $b->[1] } @sorted_params;
214              
215 1         4 my $validation_string = join '', map { $_->[0] . $_->[1] } @sorted_params;
  8         20  
216              
217 1         5 my $checksum = hmac_sha1_base64 $validation_string, $self->secret_key;
218              
219 1         9 $checksum .= '=' while length($checksum) % 4;
220              
221 1         7 $checksum;
222             }
223              
224             sub api_url {
225 6     6 1 1777 my $self = shift;
226 6 100 50     24 my $typeof = ref($self) ? $self->{_typeof} || '' : '';
227 6   66     26 my $class = ref($self) || $self;
228              
229 6 100       99 return 'http://monitis.com/api' unless $typeof;
230 3 50       10 return 'http://monitis.com/api' if $class eq $typeof;
231              
232 3 50       31 return 'http://monitis.com/api' unless $typeof->can('api_url');
233              
234             # Mapped class has own API url
235 3         13 return $typeof->api_url;
236             }
237              
238 0     0   0 sub DESTROY {
239              
240             # Placeholder for AUTOLOAD
241              
242             }
243              
244             sub AUTOLOAD {
245 19     19   12676 my $self = shift;
246 19         154 my ($package, $method) = our $AUTOLOAD =~ /^([\w\:]+)\:\:(\w+)$/;
247              
248             # Map instance to certain package
249 19 50       117 if (exists $MAPPING->{$method}) {
250 19         87 return $self->_map_to($MAPPING->{$method});
251             }
252              
253 0 0 0     0 unless ($self->{_typeof} && $self->{_typeof}->can($method)) {
254 0         0 Carp::croak qq/Can't locate object method "$method" via "$package"/;
255             }
256              
257             # Get mapped package method
258 2     2   32 no strict 'refs';
  2         4  
  2         104  
259 0         0 my $package_method = \&{"$self->{_typeof}::$method"};
  0         0  
260 2     2   8 use strict;
  2         3  
  2         1601  
261              
262             # Unmap instance
263 0         0 my $typeof = delete $self->{_typeof};
264 0         0 local $self->{_typeof} = $typeof;
265              
266 0         0 $self->$package_method(@_);
267             }
268              
269             sub ua {
270 0     0 1 0 my $self = shift;
271              
272 0 0       0 return $self->{ua} unless @_;
273              
274 0         0 $self->{ua} = shift;
275 0         0 $self;
276             }
277              
278             sub json {
279 1     1 1 2 my $self = shift;
280              
281 1 50       15 return $self->{json} unless @_;
282              
283 0         0 $self->{json} = shift;
284 0         0 $self;
285             }
286              
287             sub token_ttl {
288 0     0 1 0 my $self = shift;
289              
290 0 0 0     0 return $self->{token_ttl} || $TOKEN_TTL unless @_;
291              
292 0         0 $self->{token_ttl} = shift;
293 0         0 $self;
294             }
295              
296             sub _map_to {
297 19     19   36 my ($self, $package) = @_;
298              
299 19         60 $self->{_typeof} = "Monitis::$package";
300              
301 19 50       156 if (!$MAPPING_LOADED->{$package}) {
    0          
302              
303             # TODO: Make pretty warning
304 19         1286 eval "require $self->{_typeof};";
305 19 50       114 if ($@) {
306 0         0 delete $self->{_typeof};
307 0         0 $MAPPING_LOADED->{$package} = -1;
308 0         0 Carp::croak($@);
309             }
310 19         75 $MAPPING_LOADED->{$package} = 1;
311             }
312             elsif ($MAPPING_LOADED->{$package} < 0) {
313 0         0 my $error = delete $self->{_typeof};
314 0         0 Carp::croak("Package '$error' contains errors");
315             }
316              
317 19         149 $self;
318             }
319              
320             sub context {
321 19     19 1 35 my $self = shift;
322              
323 19 50       612 return $self->{_typeof} unless @_;
324              
325 0         0 $self->{_typeof} = shift;
326 0         0 $self;
327             }
328              
329             sub api_key {
330 5     5 1 1001 my $self = shift;
331              
332 5 100       37 return $self->{api_key} unless @_;
333              
334 1         4 $self->{api_key} = shift;
335 1         3 $self;
336             }
337              
338             sub secret_key {
339 4     4 1 1455 my $self = shift;
340              
341 4 100       54 return $self->{secret_key} unless @_;
342              
343 1         4 $self->{secret_key} = shift;
344 1         5 $self;
345             }
346              
347             sub prepare_params {
348 0     0 1   my ($self, $params, $mandatory, $optional) = @_;
349              
350 0   0       $mandatory ||= [];
351 0   0       $optional ||= [];
352              
353 0           my %existing_keys;
354              
355             # Save callback, if provided
356 0 0         my $callback = ref $params->[-1] eq 'CODE' ? pop @$params : undef;
357              
358 0           for (my $i = 0; $i <= $#$params; $i += 2) {
359 0           $existing_keys{$params->[$i]} = 1;
360             }
361              
362 0           my @lack = grep !exists $existing_keys{$_}, @$mandatory;
363 0 0         if (@lack) {
364 0           Carp::croak("Missing mandatory parameters: " . join(', ', @lack));
365             }
366              
367 0           my %param_keys = map { $_ => 1 } @$mandatory, @$optional;
  0            
368 0           my @final_params;
369              
370              
371             my @extra;
372 0           for (my $i = 0; $i <= $#$params; $i += 2) {
373 0 0         unless (exists $param_keys{$params->[$i]}) {
374 0           push @extra, $params->[$i];
375             }
376 0           push @final_params, @{$params}[$i, $i + 1];
  0            
377             }
378              
379 0 0         if (@extra) {
380 0           Carp::carp("Unexpected parameters: " . join(', ', @extra));
381             }
382              
383 0 0         push @final_params, $callback if $callback;
384              
385 0           return \@final_params;
386             }
387              
388             1;
389              
390             __END__