File Coverage

blib/lib/Consul.pm
Criterion Covered Total %
statement 100 113 88.5
branch 8 20 40.0
condition 7 14 50.0
subroutine 31 35 88.5
pod n/a
total 146 182 80.2


line stmt bran cond sub pod time code
1             package Consul;
2             $Consul::VERSION = '0.025';
3             # ABSTRACT: Client library for consul
4              
5 9     9   2730593 use namespace::autoclean;
  9         35910  
  9         75  
6              
7 9     9   2404 use HTTP::Tiny 0.014;
  9         62030  
  9         268  
8 9     9   4210 use URI::Escape qw(uri_escape);
  9         13282  
  9         573  
9 9     9   533 use JSON::MaybeXS qw(JSON);
  9         8002  
  9         427  
10 9     9   4409 use Hash::MultiValue;
  9         21744  
  9         296  
11 9     9   68 use Try::Tiny;
  9         17  
  9         566  
12 9     9   56 use Carp qw(croak);
  9         19  
  9         429  
13              
14 9     9   1258 use Moo;
  9         14015  
  9         84  
15 9     9   10510 use Type::Utils qw(class_type);
  9         93225  
  9         88  
16 9     9   6655 use Types::Standard qw(Str Int Bool HashRef CodeRef);
  9         105065  
  9         62  
17              
18             has host => ( is => 'ro', isa => Str, default => sub { '127.0.0.1' } );
19             has port => ( is => 'ro', isa => Int, default => sub { 8500 } );
20              
21             has ssl => ( is => 'ro', isa => Bool, default => sub { 0 } );
22              
23             has timeout => ( is => 'ro', isa => Int, default => sub { 15 } );
24              
25             has token => ( is => 'ro', isa => Str, predicate => '_has_token' );
26              
27             has _http => ( is => 'lazy', isa => class_type('HTTP::Tiny') );
28 2     2   31 sub _build__http { HTTP::Tiny->new(timeout => shift->timeout) };
29              
30             has _version_prefix => ( is => 'ro', isa => Str, default => sub { '/v1' } );
31              
32             has _url_base => ( is => 'lazy' );
33             sub _build__url_base {
34 2     2   20 my ($self) = @_;
35 2 50       27 ($self->ssl ? 'https' : 'http') .'://'.$self->host.':'.$self->port;
36             }
37              
38             sub _prep_url {
39 4     4   10 my ($self, $path, %args) = @_;
40 4         9 my $trailing = $path =~ m{/$};
41 4         84 my $url = $self->_url_base.join('/', map { uri_escape($_) } split('/', $path));
  16         177  
42 4 50       54 $url .= '/' if $trailing;
43 4 50       11 $url .= '?'.$self->_http->www_form_urlencode(\%args) if %args;
44 4         79 $url;
45             }
46              
47             my $json = JSON->new->utf8->allow_nonref;
48              
49             sub _prep_request {
50 4     4   9 my $callback = pop @_;
51 4         9 my ($self, $path, $method, %args) = @_;
52              
53 4 0       8 my %uargs = map { m/^_/ ? () : ($_ => $args{$_}) } keys %args;
  0         0  
54              
55 4         21 my $headers = Hash::MultiValue->new;
56              
57 4 50       158 if ($self->_has_token()) {
58 0         0 $headers->set( 'X-Consul-Token', $self->token() );
59             }
60              
61             return Consul::Request->new(
62             method => $method,
63             url => $self->_prep_url($path, %uargs),
64             headers => $headers,
65 4 50       14 content => defined( $args{_content} ) ? $args{_content} : "",
66             callback => $callback,
67             args => \%uargs,
68             );
69             }
70              
71             sub _prep_response {
72 0     0   0 my ($self, $resp, %args) = @_;
73              
74 0         0 my $data;
75 0 0       0 $data = $json->decode($resp->content) if length $resp->content > 0;
76              
77 0     0   0 my $meta = try { Consul::Meta->new(%{$resp->headers}) };
  0         0  
  0         0  
78              
79 0         0 return ($data, $meta);
80             }
81              
82             has request_cb => ( is => 'lazy', isa => CodeRef );
83             sub _build_request_cb {
84             sub {
85 4     4   360 my ($self, $req) = @_;
86 4         66 my $res = $self->_http->request($req->method, $req->url, {
87             headers => $req->headers->mixed,
88             content => $req->content,
89             });
90 4   50     3766 my $rheaders = Hash::MultiValue->from_mixed(delete $res->{headers} || {});
91 4         210 my ($rstatus, $rreason, $rcontent) = @$res{qw(status reason content)};
92 4         88 $req->callback->(Consul::Response->new(
93             status => $rstatus,
94             reason => $rreason,
95             headers => $rheaders,
96             content => $rcontent,
97             request => $req,
98             ));
99             }
100 2     2   4594 }
101              
102             has error_cb => ( is => 'lazy', isa => CodeRef );
103             sub _build_error_cb {
104             sub {
105 1     1   20 croak shift;
106             }
107 1     1   29 }
108              
109             sub _api_exec {
110 0 50 33 0   0 my $resp_cb = $#_ % 2 == 1 && ref $_[$#_] eq 'CODE' ? pop @_ : sub { pop @_ };
  4     4   116  
111 4         13 my ($self, $path, $method, %args) = @_;
112              
113 4         58 my @r;
114 4   50 0   24 my $cli_cb = delete $args{cb} || sub { @r = @_ };
  0            
115 4   66     56 my $error_cb = delete $args{error_cb} || $self->error_cb;
116              
117             $self->request_cb->($self, $self->_prep_request($path, $method, %args, sub {
118 4     4   4575 my ($resp) = @_;
119              
120 4   50     33 my $valid_cb = $args{_valid_cb} || sub { int($resp->status/100) == 2 };
121              
122 4 50       13 unless ($valid_cb->($resp->status)) {
123 4   50     14 my $content = $resp->content || "[no content]";
124 4         31 $error_cb->(sprintf("%s %s: %s", $resp->status, $resp->reason, $content));
125 3         21 return;
126             }
127              
128 0         0 my ($data, $meta) = $self->_prep_response(@_);
129 0         0 $cli_cb->($resp_cb->($data), $meta);
130 4         60 }));
131              
132 3 50       13 return wantarray ? @r : shift @r;
133             };
134              
135             with qw(
136             Consul::API::ACL
137             Consul::API::Agent
138             Consul::API::Catalog
139             Consul::API::Event
140             Consul::API::Health
141             Consul::API::KV
142             Consul::API::Session
143             Consul::API::Status
144             );
145              
146 9     9   25360 use Consul::Check;
  9         35  
  9         372  
147 9     9   4729 use Consul::Service;
  9         28  
  9         354  
148 9     9   3772 use Consul::Session;
  9         28  
  9         405  
149              
150              
151             package
152             Consul::Request; # hide from PAUSE
153              
154 9     9   103 use Moo;
  9         19  
  9         41  
155 9     9   3159 use Types::Standard qw(Str CodeRef HashRef);
  9         23  
  9         67  
156 9     9   7306 use Type::Utils qw(class_type);
  9         20  
  9         311  
157              
158             has method => ( is => 'ro', isa => Str, required => 1 );
159             has url => ( is => 'ro', isa => Str, required => 1 );
160             has headers => ( is => 'ro', isa => class_type('Hash::MultiValue'), required => 1 );
161             has content => ( is => 'ro', isa => Str, required => 1 );
162             has callback => ( is => 'ro', isa => CodeRef, required => 1 );
163             has args => ( is => 'ro', isa => HashRef, required => 1 );
164              
165              
166             package
167             Consul::Response; # hide from PAUSE
168              
169 9     9   5975 use Moo;
  9         20  
  9         157  
170 9     9   2973 use Types::Standard qw(Str Int);
  9         20  
  9         41  
171 9     9   6214 use Type::Utils qw(class_type);
  9         17  
  9         85  
172              
173             has status => ( is => 'ro', isa => Int, required => 1 );
174             has reason => ( is => 'ro', isa => Str, required => 1 );
175             has headers => ( is => 'ro', isa => class_type('Hash::MultiValue'), default => sub { Hash::MultiValue->new } );
176             has content => ( is => 'ro', isa => Str, default => sub { "" } );
177             has request => ( is => 'ro', isa => class_type('Consul::Request'), required => 1 );
178              
179              
180             package
181             Consul::Meta; # hide from PAUSE
182              
183 9     9   5745 use Moo;
  9         20  
  9         41  
184 9     9   2877 use Types::Standard qw(Int Bool);
  9         22  
  9         45  
185              
186             has index => ( is => 'ro', isa => Int, init_arg => 'x-consul-index', required => 1 );
187             has last_contact => ( is => 'ro', isa => Int, init_arg => 'x-consul-lastcontact' );
188             has known_leader => ( is => 'ro', isa => Bool, init_arg => 'x-consul-knownleader', coerce => sub { my $r = { true => 1, false => 0 }->{$_[0]}; defined $r ? $r : $_[0] } );
189              
190              
191             1;
192              
193             =pod
194              
195             =encoding UTF-8
196              
197             =for markdown [![Build Status](https://secure.travis-ci.org/robn/Consul.png)](http://travis-ci.org/robn/Consul)
198              
199             =head1 NAME
200              
201             Consul - Client library for consul
202              
203             =head1 SYNOPSIS
204              
205             use Consul;
206            
207             my $consul = Consul->new;
208             say $consul->status->leader;
209            
210             # shortcut to single API
211             my $status = Consul->status;
212             say $status->leader;
213              
214             =head1 DESCRIPTION
215              
216             This is a client library for accessing and manipulating data in a Consul
217             cluster. It targets the Consul v1 HTTP API.
218              
219             This module is quite low-level. You're expected to have a good understanding of
220             Consul and its API to understand the methods this module provides. See L
221             for further reading.
222              
223             =head1 WARNING
224              
225             This is still under development. The documentation isn't all there yet (in
226             particular about the return types) and a couple of APIs aren't implemented.
227             It's still very useful and I don't expect huge changes, but please take care
228             when upgrading. Open an issue if there's something you need that isn't here and
229             I'll get right on it!
230              
231             =head1 CONSTRUCTOR
232              
233             =head2 new
234              
235             my $consul = Consul->new( %args );
236              
237             This constructor returns a new Consul client object. Valid arguments include:
238              
239             =over 4
240              
241             =item *
242              
243             C
244              
245             Hostname or IP address of an Consul server (default: C<127.0.0.1>)
246              
247             =item *
248              
249             C
250              
251             Port where the Consul server is listening (default: C<8500>)
252              
253             =item *
254              
255             C
256              
257             Use SSL/TLS (ie HTTPS) when talking to the Consul server (default: off)
258              
259             =item *
260              
261             C
262              
263             Request timeout. If a request to Consul takes longer that this, the endpoint
264             method will fail (default: 15).
265              
266             =item *
267              
268             C
269              
270             Consul ACL token. This is used to set the C HTTP header. Typically
271             Consul agents are pre-configured with a default ACL token, or ACLs are not enabled
272             at all, so this option only needs to be set in certain cases.
273              
274             =item *
275              
276             C
277              
278             A callback to an alternative method to make the actual HTTP request. The
279             callback is of the form:
280              
281             sub {
282             my ($self, $req) = @_;
283             ... do HTTP call
284             $req->callback->(Consul::Response->new(...));
285             }
286              
287             C<$req> is a C object, and has the following attributes:
288              
289             =over 4
290              
291             =item *
292              
293             C
294              
295             The HTTP method for the request.
296              
297             =item *
298              
299             C
300              
301             The complete URL to request. This is fully formed, and includes scheme, host,
302             port and query parameters. You shouldn't need to touch it.
303              
304             =item *
305              
306             C
307              
308             A L object containing any headers that should be added to the
309             request.
310              
311             =item *
312              
313             C
314              
315             The body content for the request.
316              
317             =item *
318              
319             C
320              
321             A callback to call when the request is completed. It takes a single
322             C object as its parameter.
323              
324             =item *
325              
326             C
327              
328             A hashref containing the original arguments passed in to the endpoint method.
329              
330             =back
331              
332             The C function should be called with a C object
333             containing the values returned by the Consul server in response to the request.
334             Create one with C, passing the following attributes:
335              
336             =over 4
337              
338             =item *
339              
340             C
341              
342             The integer status code.
343              
344             =item *
345              
346             C
347              
348             The status reason phrase.
349              
350             =item *
351              
352             C
353              
354             A L containing the response headers.
355              
356             =item *
357              
358             C
359              
360             Any body content returned in the response.
361              
362             =item *
363              
364             C
365              
366             The C object passed to the callback.
367              
368             =back
369              
370             Consul itself provides a default C that uses L to make
371             calls to the server. If you provide one, you should honour the value of the
372             C argument.
373              
374             C can be used in conjunction with the C option to all API method
375             endpoints to get asynchronous behaviour. It's recommended however that you
376             don't use this directly, but rather use a module like L to
377             take care of that for you.
378              
379             If you just want to use this module to make simple calls to your Consul
380             cluster, you can ignore this option entirely.
381              
382             =item *
383              
384             C
385              
386             A callback to an alternative method to handle internal errors (usually HTTP
387             errors). The callback is of the form:
388              
389             sub {
390             my ($err) = @_;
391             ... output $err ...
392             }
393              
394             The default callback simply calls C.
395              
396             =back
397              
398             =head1 ENDPOINTS
399              
400             Individual API endpoints are implemented in separate modules. The following
401             methods will return a context objects for the named API. Alternatively, you can
402             request an API context directly from the Consul package. In that case,
403             Cnew> is called implicitly.
404              
405             # these are equivalent
406             my $agent = Consul->new( %args )->agent;
407             my $agent = Consul->agent( %args );
408              
409             =head2 kv
410              
411             Key/value store API. See L.
412              
413             =head2 agent
414              
415             Agent API. See L.
416              
417             =head2 catalog
418              
419             Catalog (nodes and services) API. See L.
420              
421             =head2 health
422              
423             Health check API. See L.
424              
425             =head2 session
426              
427             Sessions API. See L.
428              
429             =head2 acl
430              
431             Access control API. See L.
432              
433             =head2 event
434              
435             User event API. See L.
436              
437             =head2 status
438              
439             System status API. See L.
440              
441             =head1 METHOD OPTIONS
442              
443             All API methods implemented by the endpoints can take a number of arguments.
444             Most of those are documented in the endpoint documentation. There are however
445             some that are common to all methods:
446              
447             =over 4
448              
449             =item *
450              
451             C
452              
453             A callback to call with the results of the method. Without this, the results
454             are returned from the method, but only if C is synchronous. If an
455             asynchronous C is used without a C being passed to the method, the
456             method return value is undefined.
457              
458             If you just want to use this module to make simple calls to your Consul
459             cluster, you can ignore this option entirely.
460              
461             C
462              
463             A callback to an alternative method to handle internal errors (usually HTTP
464             errors). errors). The callback is of the form:
465              
466             sub {
467             my ($err) = @_;
468             ... output $err ...
469             }
470              
471             The default callback calls the C for the API object itself, which by
472             default, simply calls croak.
473              
474             =back
475              
476             =head1 BLOCKING QUERIES
477              
478             Some Consul API endpoints support a feature called a "blocking query". These
479             endpoints allow long-polling for changes, and support some extra information
480             about the server state, including the Raft index, in the response headers.
481              
482             The corresponding endpoint methods, when called in array context, will return a
483             second value. This is an object with three methods, C, C
484             and C, corresponding to the similarly-named header fields. You
485             can use these to set up state watches, CAS writes, and so on.
486              
487             See the Consul API docs for more information.
488              
489             =head1 SEE ALSO
490              
491             =over 4
492              
493             =item *
494              
495             L - a wrapper providing asynchronous operation
496              
497             =item *
498              
499             L - Consul HTTP API documentation
500              
501             =back
502              
503             =head1 SUPPORT
504              
505             =head2 Bugs / Feature Requests
506              
507             Please report any bugs or feature requests through the issue tracker
508             at L.
509             You will be notified automatically of any progress on your issue.
510              
511             =head2 Source Code
512              
513             This is open source software. The code repository is available for
514             public review and contribution under the terms of the license.
515              
516             L
517              
518             git clone https://github.com/robn/Consul.git
519              
520             =head1 CONTRIBUTORS
521              
522             =over 4
523              
524             =item *
525              
526             Rob N ★
527              
528             =item *
529              
530             Aran Deltac
531              
532             =item *
533              
534             Michael McClimon
535              
536             =back
537              
538             =head1 COPYRIGHT AND LICENSE
539              
540             This software is copyright (c) 2015 by Rob N ★.
541              
542             This is free software; you can redistribute it and/or modify it under
543             the same terms as the Perl 5 programming language system itself.
544              
545             =cut