File Coverage

blib/lib/WWW/Liquidweb/Lite.pm
Criterion Covered Total %
statement 48 119 40.3
branch 3 28 10.7
condition 5 25 20.0
subroutine 10 15 66.6
pod 1 1 100.0
total 67 188 35.6


line stmt bran cond sub pod time code
1             package WWW::Liquidweb::Lite;
2              
3 1     1   31844 use 5.006;
  1         4  
  1         45  
4 1     1   6 use strict;
  1         1  
  1         31  
5 1     1   5 use warnings;
  1         6  
  1         40  
6              
7 1     1   1463 use WWW::Liquidweb::Lite::Error;
  1         4  
  1         32  
8              
9 1     1   1453 use LWP::UserAgent;
  1         63752  
  1         29  
10 1     1   1250 use JSON;
  1         14650  
  1         4  
11 1     1   1005 use Want;
  1         1701  
  1         598  
12              
13             our $VERSION = '0.01';
14              
15             =head2 new
16              
17             Create a new WWW::Liquidweb::Lite object
18              
19             =head3 USAGE
20              
21             This module builds the necessary tools to interact with the Liquidweb API from a JSON file that is
22             usually located at the following address:
23              
24             https://www.stormondemand.com/api/docs/v1/docs.json
25              
26             This is built up in a way that slightly resembles WSDL, but the JSON structure is Liquidweb specific.
27              
28             Example:
29             my $liquidweb = WWW::Liquidweb::Lite->new(username => 'USERNAME', password => 'PASSWORD');
30              
31             ARGUMENT DESCRIPTION
32             ---
33             username Liquidweb API username
34             password Liquidweb API password
35             json Location of the JSON describing the Liquidweb API
36             [DEFAULTS: https://www.stormondemand.com/api/docs/v1/docs.json]
37             timeout LWP::UserAgent timeout
38             [DEFAULTS: 60]
39             json Location of the JSON to build the API object
40             [DEFAULTS: https://www.stormondemand.com/api/docs/v1/docs.json]
41             version Version of the API to use
42             [DEFAULTS: v1]
43             host The "base" host for the API
44             [DEFAULTS: https://api.stormondemand.com]
45             ---
46              
47              
48             =cut
49              
50             sub new
51             {
52 1     1 1 8 my $class = shift;
53 1 50       7 my $params = ref($_[0]) ? $_[0] : {@_};
54 1         2 my $self = {};
55              
56 1 50 33     9 unless ($params->{username} and $params->{password}) {
57 0         0 return WWW::Liquidweb::Lite::Error->throw(
58             type => 'Missing Arguments',
59             message => 'username and password are both required',
60             );
61             }
62              
63 1         3 $self->{__username} = $params->{username};
64 1         3 $self->{__password} = $params->{password};
65              
66 1   50     6 $params->{host} ||= 'https://api.stormondemand.com';
67 1   50     5 $params->{json} ||= 'https://www.stormondemand.com/api/docs/v1/docs.json';
68 1   50     5 $params->{timeout} ||= 60;
69 1   50     5 $params->{version} ||= 'v1';
70              
71 1         2 $self->{__host} = $params->{host};
72 1         2 $self->{__jsonUrl} = $params->{json};
73 1         2 $self->{__timeout} = $params->{timeout};
74 1         3 $self->{__version} = $params->{version};
75              
76              
77 1         34 $self->{__ua} = LWP::UserAgent->new;
78 1         2734 $self->{__ua}->agent("p5-www-liquidweb-lite/$VERSION");
79 1         56 $self->{__ua}->timeout($self->{__timeout});
80              
81 1         20 my $req = HTTP::Request->new(POST => $self->{__jsonUrl});
82 1         10597 my $res = $self->{__ua}->request($req);
83              
84 1 50       2212 unless ($res->is_success) {
85 1         28 return WWW::Liquidweb::Lite::Error->throw(
86             category => 'LWP::UserAgent',
87             type => $res->{_rc},
88             message => "$res->{_msg}$res->{_content}",
89             );
90             }
91              
92 0           bless($self, $class);
93 0           $self->__build(decode_json($res->{_content}));
94              
95 0           return $self;
96             }
97              
98             sub AUTOLOAD
99             {
100 0     0     my $self = shift;
101              
102 0           our $AUTOLOAD;
103 0           my ($key) = $AUTOLOAD =~ /.*::([\w_]+)/o;
104 0 0         return if ($key eq 'DESTROY');
105              
106 0           push @{$self->{__chain}}, $key;
  0            
107              
108 0 0         my $args = ref($_[0]) ? $_[0] : {@_};
109              
110 0 0 0       if (want('OBJECT') || want('VOID')) {
111 0           return $self;
112             }
113              
114 0           my @chain = @{delete($self->{__chain})};
  0            
115              
116 0           my $code = pop(@chain);
117              
118 0           my $help;
119 0 0         $help = 1 if ($code =~ /help/i);
120              
121 0 0         if ($help) {
122 0           return $self->__help(@chain);
123             } else {
124 1     1   7 no strict qw(refs);
  1         2  
  1         171  
125              
126 0           @chain = map {ucfirst} @chain;
  0            
127 0           my $package = join('::', @chain);
128              
129 0 0         unless (defined &{"WWW::Liquidweb::Lite::${package}::${code}"}) {
  0            
130 0           return WWW::Liquidweb::Lite::Error->throw(
131             type => 'Invalid Method',
132             message => "$package::$code is not a valid method",
133             );
134             }
135              
136 0           return &{"WWW::Liquidweb::Lite::${package}::${code}"}(
  0            
137             {
138             username => $self->{__username},
139             password => $self->{__password},
140             timeout => $self->{__timeout},
141             version => $self->{__version},
142             host => $self->{__host},
143             },
144             $args,
145             );
146             }
147             }
148              
149             sub __build
150             {
151 0     0     my $self = shift;
152 0           my $json = $self->{__json} = shift;
153              
154 1     1   6 no strict qw(refs);
  1         2  
  1         2220  
155              
156 0           while (my ($namespace, $details) = each(%{$json})) {
  0            
157 0           my $package = $namespace;
158 0           $package =~ s{/}{::}g;
159 0           for my $method (keys %{$details->{__methods}}) {
  0            
160 0     0     *{"WWW::Liquidweb::Lite::${package}::${method}"} = sub {__doRemoteApiCall("$namespace/$method", shift, shift)};
  0            
  0            
161             }
162             }
163             }
164              
165             sub __doRemoteApiCall
166             {
167 0     0     my $method = shift;
168 0           my $params = shift;
169 0   0       my $args = shift || {};
170              
171 0           my $ua = LWP::UserAgent->new;
172 0           $ua->timeout($params->{timeout});
173              
174 0           my $url = "$params->{host}/$params->{version}/$method.json";
175 0           my $req = HTTP::Request->new(POST => $url);
176 0           $req->authorization_basic($params->{username}, $params->{password});
177              
178 0           my $parser = JSON->new->utf8(1);
179 0           my $json = $parser->encode({
180             metadata => { ip => $ENV{REMOTE_ADDR} },
181             params => $args,
182             });
183 0           $req->content($json);
184              
185 0           my $response = $ua->request($req);
186              
187 0           my $code = $response->code;
188 0           my $content = $response->content;
189              
190 0 0         if ($code == 200) {
191 0           my $results = $parser->decode($content);
192 0           return $results;
193             }
194              
195 0 0 0       if ($content =~ /timeout/i) {
    0          
    0          
196 0           my $timeout = $params->{timeout};
197 0           return WWW::Liquidweb::Lite::Error->throw(
198             category => 'HTTP::Request',
199             type => 'timeout',
200             message => "Request to $url timed out after $timeout seconds",
201             );
202             } elsif ($code == 401 || $code == 403) {
203 0           return WWW::Liquidweb::Lite::Error->throw(
204             category => 'HTTP::Request',
205             type => $code,
206             message => "Authorization failed for $url",
207             );
208             } elsif ($code == 500) {
209 0           return WWW::Liquidweb::Lite::Error->throw(
210             category => 'HTTP::Request',
211             type => $code,
212             message => "Request to $url failed due to a network error: $content",
213             );
214             } else {
215 0           return WWW::Liquidweb::Lite::Error->throw(
216             category => 'HTTP::Request',
217             message => "Request to $url failed: $content",
218             );
219             }
220             }
221              
222             =head2 help
223              
224             Get help with the Liquidweb API
225              
226             =head3 USAGE
227              
228             Calling the method 'help' at the end of your method API calls will return a hash of helpful information:
229              
230             my $server_list_help = $liquidweb->server->list->help;
231              
232             In this example, $server_list_help will contain information about the 'Server/list' method in the Liquidweb API.
233              
234             $server_list_help->{__input} This will contain a hash describing valid input parameters for this
235             method.
236              
237             $server_list_help->{__description} This will contain a string that will describe aspects of the method,
238             and occasionally examples, insights, or other helpful information.
239              
240             $server_list_help->{__output} This will contain a hash describing valid output that you may receive
241             back from the API call.
242             =cut
243              
244             sub __help
245             {
246 0     0     my $self = shift;
247 0           my @chain = @_;
248              
249 0           my $method = pop(@chain);
250 0           @chain = map {ucfirst} @chain;
  0            
251              
252 0           my $namespace = join('/', @chain);
253              
254 0 0 0       unless ($method && $namespace && $self->{__json}{$namespace}{__methods}{$method}) {
      0        
255 0           return WWW::Liquidweb::Lite::Error->throw(
256             type => 'Invalid Arguments',
257             message => 'Could not find documentation for given method, or did not receive a method.',
258             );
259             }
260              
261 0           return $self->{__json}{$namespace}{__methods}{$method};
262             }
263              
264             =head1 NAME
265              
266             WWW::Liquidweb::Lite - A module to interface with the Liquidweb API
267              
268             =head1 SYNOPSIS
269              
270             You can create an object in this manner:
271              
272             my $liquidweb = WWW::Liquidweb::Lite->new(username => "USERNAME", password => "PASSWORD");
273              
274             USERNAME and PASSWORD correspond with the login credentials you would use for the Liquidweb API, as detailed here:
275              
276             http://www.liquidweb.com/StormServers/api/index.html
277              
278             Once you have the object you can call a method from the api in "object fashion". For example:
279              
280             'Storm/Server/list'
281              
282             Can be called from the object in this manner:
283              
284             $liquidweb->server->list
285              
286             The casing does not have to be correct.
287              
288             Arguments can be sent to the method as well:
289              
290             $liquidweb->server->list(category => 'Dedicated');
291              
292             =head1 VERSION
293              
294             Version 0.01
295              
296             =cut
297              
298             =head1 AUTHOR
299              
300             Shane Utt, C<< >>
301              
302             =head1 BUGS
303              
304             Please report any bugs or feature requests to C, or through
305             the web interface at L. I will be notified, and then you'll
306             automatically be notified of progress on your bug as I make changes.
307              
308             =head1 SUPPORT
309              
310             You can find documentation for this module with the perldoc command.
311              
312             perldoc WWW::Liquidweb::Lite
313              
314             You can also look for information at:
315              
316             =over 4
317              
318             =item * RT: CPAN's request tracker (report bugs here)
319              
320             L
321              
322             =item * AnnoCPAN: Annotated CPAN documentation
323              
324             L
325              
326             =item * CPAN Ratings
327              
328             L
329              
330             =item * Search CPAN
331              
332             L
333              
334             =back
335              
336             =head1 LICENSE AND COPYRIGHT
337              
338             Copyright (C) 2014 Shane Utt, All Rights Reserved.
339              
340             This program is free software; you can redistribute it and/or modify it
341             under the terms of the the Artistic License (2.0). You may obtain a
342             copy of the full license at:
343              
344             L
345              
346             Any use, modification, and distribution of the Standard or Modified
347             Versions is governed by this Artistic License. By using, modifying or
348             distributing the Package, you accept this license. Do not use, modify,
349             or distribute the Package, if you do not accept this license.
350              
351             If your Modified Version has been derived from a Modified Version made
352             by someone other than you, you are nevertheless required to ensure that
353             your Modified Version complies with the requirements of this license.
354              
355             This license does not grant you the right to use any trademark, service
356             mark, tradename, or logo of the Copyright Holder.
357              
358             This license includes the non-exclusive, worldwide, free-of-charge
359             patent license to make, have made, use, offer to sell, sell, import and
360             otherwise transfer the Package with respect to any patent claims
361             licensable by the Copyright Holder that are necessarily infringed by the
362             Package. If you institute patent litigation (including a cross-claim or
363             counterclaim) against any party alleging that the Package constitutes
364             direct or contributory patent infringement, then this Artistic License
365             to you shall terminate on the date that such litigation is filed.
366              
367             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
368             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
369             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
370             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
371             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
372             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
373             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
374             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
375              
376             =cut
377              
378             1;