File Coverage

blib/lib/Net/Marathon.pm
Criterion Covered Total %
statement 58 127 45.6
branch 7 34 20.5
condition 3 15 20.0
subroutine 16 34 47.0
pod 14 15 93.3
total 98 225 43.5


line stmt bran cond sub pod time code
1             package Net::Marathon;
2              
3 4     4   46493 use 5.006;
  4         12  
4 4     4   18 use strict;
  4         5  
  4         92  
5 4     4   16 use warnings;
  4         13  
  4         124  
6 4     4   2572 use LWP::UserAgent;
  4         157876  
  4         156  
7 4     4   2246 use JSON::XS;
  4         14673  
  4         253  
8 4     4   2055 use Net::Marathon::App;
  4         8  
  4         192  
9 4     4   1571 use Net::Marathon::Group;
  4         7  
  4         117  
10 4     4   1667 use Net::Marathon::Events;
  4         13  
  4         148  
11 4     4   2860 use Net::Marathon::Deployment;
  4         7  
  4         4709  
12              
13             =head1 NAME
14              
15             Net::Marathon - An object-oriented Mapper for the Marathon REST API, fork of Marathon module
16              
17             =cut
18              
19             our $VERSION = '0.1.0';
20             our $verbose = 0;
21              
22              
23             =head1 SYNOPSIS
24              
25             Net::Marathon 0.1.0 is a fork of Marathon 0.9 with a fix on Events API (applied this patch https://github.com/geidies/perl-Marathon/pull/1).
26             Otherwise it is the same, more differences may come in future versions.
27              
28             This module is a wrapper around the [Marathon REST API](http://mesosphere.github.io/marathon/docs/rest-api.html), so it can be used without having to write JSON by hand.
29              
30             For the most common tasks, there is a helper method in the main module. Some additional methods are found in the Net::Marathon::App etc. submodules.
31              
32             To start, create a marathon object:
33              
34             my $m = Net::Marathon->new( url => 'http://my.marathon.here:8080' );
35              
36             my $app = $m->get_app('hello-marathon');
37              
38             $app->instances( 23 );
39             $app->update();
40             print STDERR Dumper( $app->deployments );
41              
42             sleep 10;
43              
44             $app->instances( 1 );
45             $app->update( {force => 'true'} ); # should work even if the scaling up is not done yet.
46              
47              
48             =head1 SUBROUTINES/METHODS
49              
50             =head2 new
51              
52             Creates a Marathon object. You can pass in the URL to the marathon REST interface:
53              
54             use Net::Marathon;
55             my $marathon = Net::Marathon->new( url => 'http://169.254.47.11:8080', verbose => 0 );
56              
57             The "verbose" parameter makes the module more chatty on STDERR.
58              
59             =cut
60              
61             sub new {
62 4     4 1 1528 my ($class, %conf) = @_;
63 4   100     19 my $url = delete $conf{url} || 'http://localhost:8080/';
64 4   50     21 $Net::Marathon::verbose = delete $conf{verbose} || 0;
65 4         24 my $ua = LWP::UserAgent->new;
66 4         6181 my $self = bless {
67             _ua => $ua,
68             };
69 4         15 $self->_set_url($url);
70 4         13 return $self;
71             }
72              
73             sub _set_url { # void
74 4     4   7 my ($self, $url) = @_;
75 4 100       33 unless ( $url =~ m,^https?\://, ) {
76 1         3 $url = 'http://' . $url;
77             }
78 4 100       18 unless ( $url =~ m,/$, ) {
79 1         2 $url .= '/';
80             }
81 4         16 $self->{_url} = $url;
82             }
83              
84             =head2 get_app( $id )
85              
86             Returns a Net::Marathon::App as identified by the single argument "id". In case there is no such app, will return undef.
87              
88             my $app = $marathon->get_app('such-1');
89             print $app->id . "\n";
90              
91             =cut
92              
93             sub get_app { # Net::Marathon::App
94 0     0 1 0 my ( $self, $id ) = @_;
95 0         0 my $api_response = $self->_get_obj('/v2/apps/' . $id);
96 0 0       0 return undef unless defined $api_response;
97 0         0 return Net::Marathon::App->new( $api_response->{app}, $self );
98             }
99              
100             =head2 new_app( $config )
101              
102             Returns a new Net::Marathon::App as described in the $config hash. Example:
103              
104             my $app = $marathon->new_app({ id => 'very-1', mem => 4, cpus => 0.1, cmd => "while [ 1 ]; do echo 'wow.'; done" });
105              
106             This will not (!) start the app in marathon. To do so, call create() on the returned object:
107              
108             $app->create();
109              
110             =cut
111              
112             sub new_app {
113 0     0 1 0 my ($self, $config) = @_;
114 0         0 return Net::Marathon::App->new( $config, $self );
115             }
116              
117             =head2 get_group( $id )
118              
119             Works like get_app, just for groups.
120              
121             =cut
122              
123             sub get_group { # Net::Marathon::App
124 0     0 1 0 my ( $self, $id ) = @_;
125 0         0 return Net::Marathon::Group->get( $id, $self );
126             }
127              
128             =head2 new_group( $config )
129              
130             Creates a new group. You can either specify the apps in-line:
131              
132             my $group = $marathon->new_group( { id => 'very-1', apps: [{ id => "such-2", cmd => ... }, { id => "such-3", cmd => ... }] } );
133              
134             Or add them to the created group later:
135              
136             my $group = $marathon->new_group( { id => 'very-1' } );
137             $group->add( $marathon->new_app( { id => "such-2", cmd => ... } );
138             $group->add( $marathon->new_app( { id => "such-3", cmd => ... } );
139              
140             In any case, new_group will just return a Net::Marathon::Group object, it will not commit to marathon until you call create() on the returned object:
141              
142             $group->create();
143              
144             =cut
145              
146             sub new_group {
147 0     0 1 0 my ($self, $config) = @_;
148 0         0 return Net::Marathon::Group->new( $config, $self );
149             }
150              
151             =head2 events()
152              
153             Returns a Net::Marathon::Events objects. You can register callbacks on it and start listening to the events stream.
154              
155             =cut
156              
157             sub events {
158 0     0 1 0 my $self = shift;
159 0         0 return Net::Marathon::Events->new( $self );
160             }
161              
162             =head2 get_tasks( $status )
163              
164             Returns an array of currently running tasks. If $status is "running" or "staging", will filter and return only those tasks.
165              
166             =cut
167              
168             sub get_tasks {
169 0     0 1 0 my ($self, $status) = @_;
170 0 0 0     0 $status = '' unless $status && $status =~ m/^running|staging$/;
171 0 0       0 if ( $status ) {
172 0         0 $status = '?status='.$status;
173             }
174 0         0 my $task_obj = $self->_get_obj_from_json('/v2/tasks'.$status);
175 0   0     0 my $task_arrayref = ( defined $task_obj && exists $task_obj->{tasks} && $task_obj->{tasks} ) || [];
176 0 0       0 return wantarray ? @{$task_arrayref} : $task_arrayref;
  0         0  
177             }
178              
179             =head2 kill_tasks({ tasks => $@ids, scale => bool })
180              
181             Kills the tasks with the given @ids. Scales if the scale param is true.
182              
183             =cut
184              
185             sub kill_tasks {
186 0     0 1 0 my ($self, $args) = @_;
187 0 0 0     0 my $param = $args && $args->{scale} && $args->{scale} && $args->{scale} !~ /false/i ? '?scale=true' : ''; #default is false
188 0         0 return $self->_put_post_delete( 'POST', '/v2/tasks/delete'.$param, { ids => $args->{tasks} } );
189             }
190              
191             =head2 get_deployments
192              
193             Returns a list of Net::Marathon::Deployment objects with the currently running deployments.
194              
195             =cut
196              
197             sub get_deployments {
198 0     0 1 0 my $self = shift;
199 0         0 my $deployments = $self->_get_obj('/v2/deployments');
200 0         0 my @depl_objs = ();
201 0         0 foreach ( @{$deployments} ) {
  0         0  
202 0         0 push @depl_objs, Net::Marathon::Deployment->new( $_, $self );
203             }
204 0 0       0 return wantarray ? @depl_objs : \@depl_objs;
205             }
206              
207             =head2 kill_deployment( $id, { force => bool } )
208              
209             Stop the deployment with given id.
210              
211             =cut
212              
213             sub kill_deployment {
214 0     0 1 0 my ($self, $id, $args) = @_;
215 0 0 0     0 my $param = $args && $args->{force} && $args->{force} && $args->{force} !~ /false/i ? '?force=true' : ''; #default is false
216 0         0 return $self->_put_post_delete( 'DELETE', '/v2/deployments/' . $id . $param );
217             }
218              
219             sub get_endpoint {
220 1     1 0 5 my ( $self, $path ) = @_;
221 1         6 my $url = $self->{_url} . $path;
222 1         11 $url =~ s,/+,/,g;
223 1         6 $url =~ s,^http:/,http://,;
224 1         4 return $url;
225             }
226              
227             =head2 metrics
228              
229             returns the metrics returned by the /metrics endpoint, converted from json to perl.
230              
231             =cut
232              
233             sub metrics {
234 0     0 1 0 my $self = shift;
235 0         0 return $self->_get_obj('/metrics');
236             }
237              
238             =head2 help
239              
240             returns the HTML returned by the /help endpoint.
241              
242             =cut
243              
244             sub help { # string (html)
245 0     0 1 0 my $self = shift;
246 0         0 return $self->_get_html('/help');
247             }
248              
249             =head2 logging
250              
251             returns the HTML returned by the /logging endpoint.
252              
253             =cut
254              
255             sub logging { # string (html)
256 0     0 1 0 my $self = shift;
257 0         0 return $self->_get_html('/logging');
258             }
259              
260             =head2 ping
261              
262             returns 1 if the master responds to a ping request.
263              
264             =cut
265              
266             sub ping { # string (plaintext)
267 1     1 1 14 my $self = shift;
268 1 50       6 return $self->_get_html('/ping') =~ m,pong, ? 'pong' : undef;
269             }
270              
271             sub _get { # HTTP::Response
272 1     1   3 my ( $self, $path ) = @_;
273 1         7 my $url = $self->get_endpoint( $path );
274 1         9 my $response = $self->{_ua}->get( $url );
275 1         64592 $self->_response_handler( 'GET', $response );
276 1         3 return $response;
277             }
278              
279             sub _get_html { # string (html) or undef on error
280 1     1   3 my ( $self, $path ) = @_;
281 1         5 my $response = $self->_get($path);
282 1 50       5 if ( $response->is_success ) {
283 0         0 return $response->decoded_content;
284             }
285 1         29 return '';
286             }
287              
288             sub _get_obj { # hashref
289 0     0   0 my ( $self, $path ) = @_;
290 0         0 my $response = $self->_get_html($path);
291 0 0       0 if ($response) {
292 0         0 return decode_json $response;
293             }
294 0         0 return undef;
295             }
296              
297             sub _get_obj_from_json { # hashref
298 0     0   0 my ( $self, $path ) = @_;
299 0         0 my $response = $self->_put_post_delete('GET', $path);
300 0 0       0 if ($response) {
301 0         0 return decode_json $response;
302             }
303 0         0 return undef;
304             }
305              
306             sub _post {
307 0     0   0 my ($self, $path, $payload) = @_;
308 0         0 return $self->_put_post_delete( 'POST', $path, $payload );
309             }
310              
311             sub _put {
312 0     0   0 my ($self, $path, $payload) = @_;
313 0         0 return $self->_put_post_delete( 'PUT', $path, $payload );
314             }
315              
316             sub _delete {
317 0     0   0 my ($self, $path, $payload) = @_;
318 0         0 return $self->_put_post_delete( 'DELETE', $path, $payload );
319             }
320              
321             sub _put_post_delete {
322 0     0   0 my ($self, $method, $path, $payload) = @_;
323 0         0 my $req = HTTP::Request->new( $method, $self->get_endpoint($path) );
324 0         0 $req->header( 'Accept' => 'application/json' );
325 0 0       0 if ( $payload ) {
326 0         0 $req->header( 'Content-Type' => 'application/json' );
327 0         0 $req->content( encode_json $payload );
328             }
329 0         0 my $response = $self->{_ua}->request( $req );
330 0         0 $self->_response_handler( $method, $response );
331 0 0       0 return $response->is_success ? $response->decoded_content : undef;
332             }
333              
334             sub _response_handler {
335 1     1   3 my ( $self, $method, $response ) = @_;
336 1 50       6 if ( $verbose ) {
337 0 0       0 unless ( $response->is_success ) {
338 0         0 print STDERR 'Error doing '.$method.' against '. $response->base.': ' . $response->status_line . "\n";
339 0         0 print STDERR $response->decoded_content ."\n";
340             } else {
341 0         0 print STDERR $response->status_line . "\n"
342             }
343             }
344 1         3 return $response;
345             }
346              
347             =head1 AUTHOR
348              
349             Sebastian Geidies C<< >> (original Marathon module)
350              
351             Miroslav Tynovsky
352              
353             =cut
354              
355             1;