File Coverage

blib/lib/Net/Docker.pm
Criterion Covered Total %
statement 56 157 35.6
branch 3 12 25.0
condition 0 10 0.0
subroutine 16 37 43.2
pod 0 20 0.0
total 75 236 31.7


line stmt bran cond sub pod time code
1             package Net::Docker;
2 2     2   47661 use strict;
  2         6  
  2         79  
3 2     2   56 use 5.010;
  2         7  
  2         105  
4             our $VERSION = '0.002005';
5              
6 2     2   4299 use Moo;
  2         77500  
  2         15  
7 2     2   13713 use JSON;
  2         58122  
  2         15  
8 2     2   2501 use URI;
  2         10004  
  2         77  
9 2     2   1920 use URI::QueryParam;
  2         1609  
  2         74  
10 2     2   4081 use LWP::UserAgent;
  2         144793  
  2         75  
11 2     2   21 use Carp;
  2         5  
  2         171  
12 2     2   3052 use AnyEvent;
  2         11625  
  2         72  
13 2     2   2194 use AnyEvent::Socket 'tcp_connect';
  2         60802  
  2         261  
14 2     2   2202 use AnyEvent::HTTP;
  2         45842  
  2         4096  
15              
16             has address => (is => 'ro', default => sub { $ENV{DOCKER_HOST} || 'http:var/run/docker.sock/' });
17             has ua => (is => 'lazy');
18              
19             sub _build_ua {
20 1     1   392 my $self = shift;
21 1 50       10 if ( $self->address !~ m!http://! ) {
22 1         1001 require LWP::Protocol::http::SocketUnixAlt;
23 1         90807 LWP::Protocol::implementor( http => 'LWP::Protocol::http::SocketUnixAlt' );
24             }
25 1         24 my $ua = LWP::UserAgent->new;
26 1         6393 return $ua;
27             }
28              
29 1     1   5 sub _uri { my $self = shift; return $self->uri(@_); }
  1         9  
30              
31             sub uri {
32 1     1 0 9 my ($self, $rel, %options) = @_;
33 1         25 my $uri = URI->new($self->address . $rel);
34 1         12196 $uri->query_form(%options);
35 1         279 return $uri;
36             }
37              
38             sub _parse {
39 0     0   0 my ($self, $uri, %options) = @_;
40 0         0 my $res = $self->ua->get($self->_uri($uri, %options));
41 0 0       0 if ($res->content_type eq 'application/json') {
    0          
42 0         0 return decode_json($res->decoded_content);
43             }
44             elsif ($res->content_type eq 'text/plain') {
45 0         0 return eval { decode_json($res->decoded_content) };
  0         0  
46             }
47 0         0 $res->dump;
48             }
49              
50             sub _parse_request {
51 1     1   5 my ($self, $res) = @_;
52 1 50       26 if ($res->content_type eq 'application/json') {
53 0         0 my $json = JSON::XS->new;
54 0         0 return $json->incr_parse($res->decoded_content);
55             }
56 1         100 my $message = $res->decoded_content;
57 1         4530 $message =~ s/\r?\n$//;
58 1         354 croak $message;
59             }
60              
61             sub create {
62 0     0 0 0 my ($self, %options) = @_;
63 0   0     0 $options{AttachStderr} //= \1;
64 0   0     0 $options{AttachStdout} //= \1;
65 0   0     0 $options{AttachStdin} //= \0;
66 0   0     0 $options{OpenStdin} //= \0;
67 0   0     0 $options{Tty} //= \1;
68              
69             ## workaround for an odd API implementation of
70             ## container naming
71 0         0 my %query;
72 0 0       0 if (my $name = delete $options{Name}) {
73 0         0 $query{name} = $name;
74             }
75              
76 0         0 my $input = encode_json(\%options);
77              
78 0         0 my $res = $self->ua->post($self->uri('/containers/create', %query), 'Content-Type' => 'application/json', Content => $input);
79              
80 0         0 my $json = JSON::XS->new;
81 0         0 my $out = $json->incr_parse($res->decoded_content);
82 0         0 return $out->{Id};
83             }
84              
85             sub ps {
86 0     0 0 0 my ($self, %options) = @_;
87 0         0 return $self->_parse('/containers/ps', %options);
88             }
89              
90             sub images {
91 0     0 0 0 my ($self, %options) = @_;
92 0         0 return $self->_parse('/images/json', %options);
93             }
94              
95             sub images_viz {
96 0     0 0 0 my ($self, %options) = @_;
97 0         0 return $self->_parse('/images/viz', %options);
98             }
99              
100             sub search {
101 0     0 0 0 my ($self, %options) = @_;
102 0         0 return $self->_parse('/images/search', %options);
103             }
104              
105             sub history {
106 0     0 0 0 my ($self, $image, %options) = @_;
107 0         0 return $self->_parse('/images/'.$image.'/history', %options);
108             }
109              
110             sub inspect {
111 0     0 0 0 my ($self, $image, %options) = @_;
112 0         0 return $self->_parse('/images/'.$image.'/json', %options);
113             }
114              
115             sub version {
116 0     0 0 0 my ($self, %options) = @_;
117 0         0 return $self->_parse('/version', %options);
118             }
119              
120             sub info {
121 0     0 0 0 my ($self, %options) = @_;
122 0         0 return $self->_parse('/info', %options);
123             }
124              
125             sub inspect_container {
126 0     0 0 0 my ($self, $name, %options) = @_;
127 0         0 return $self->_parse('/containers/'.$name.'/json', %options);
128             }
129              
130             sub export {
131 0     0 0 0 my ($self, $name, %options) = @_;
132 0         0 return $self->_parse('/containers/'.$name.'/export', %options);
133             }
134              
135             sub diff {
136 0     0 0 0 my ($self, $name, %options) = @_;
137 0         0 return $self->_parse('/containers/'.$name.'/changes', %options);
138             }
139              
140             sub remove_image {
141 0     0 0 0 my ($self, @names) = @_;
142 0         0 for my $image (@names) {
143 0         0 $self->ua->request(HTTP::Request->new('DELETE', $self->_uri('/images/'.$image)));
144             }
145 0         0 return;
146             }
147              
148             sub remove_container {
149 0     0 0 0 my ($self, @names) = @_;
150 0         0 for my $container (@names) {
151 0         0 $self->ua->request(HTTP::Request->new('DELETE', $self->_uri('/containers/'.$container)));
152             }
153 0         0 return;
154             }
155              
156             sub pull {
157 1     1 0 372 my ($self, $repository, $tag, $registry) = @_;
158              
159 1 50       7 if ($repository =~ m/:/) {
160 0         0 ($repository, $tag) = split/:/, $repository;
161             }
162 1         7 my %options = (
163             fromImage => $repository,
164             tag => $tag,
165             registry => $registry,
166             );
167 1         2 my $uri = '/images/create';
168 1         6 my $res = $self->ua->post($self->_uri($uri, %options));
169 1         15168 return $self->_parse_request($res);
170             }
171              
172             sub start {
173 0     0 0   my ($self, $name, %options) = @_;
174 0           $self->ua->post($self->_uri('/containers/'.$name.'/start'));
175 0           return;
176             }
177              
178             sub stop {
179 0     0 0   my ($self, $name, %options) = @_;
180 0           $self->ua->post($self->_uri('/containers/'.$name.'/stop'));
181 0           return;
182             }
183              
184             sub logs {
185 0     0 0   my ($self, $container) = @_;
186 0           my %params = (
187             logs => 1,
188             stdout => 1,
189             stderr => 1,
190             );
191 0           my $url = $self->_uri('/containers/'.$container.'/attach');
192 0           my $res = $self->ua->post($url, \%params);
193 0           return $res->content;
194             }
195              
196             sub streaming_logs {
197 0     0 0   my ($self, $container, %options) = @_;
198              
199 0           *STDOUT->autoflush(1);
200              
201 0           my $input = delete $options{in_fh};
202 0           my $output = delete $options{out_fh};
203              
204 0           my $cv = AnyEvent->condvar;
205              
206 0           my $in_hndl;
207             my $out_hndl;
208              
209 0           my $callback; $callback = sub {
210 0     0     my ($fh, $headers) = @_;
211              
212 0           $fh->on_error(sub {$cv->send});
  0            
213 0           $fh->on_eof(sub {$cv->send});
  0            
214              
215 0           $out_hndl = AnyEvent::Handle->new(fh => $output);
216              
217             $fh->on_read(sub {
218 0           my ($handle) = @_;
219             $handle->unshift_read(sub {
220 0           my ($h) = @_;
221 0           my $length = length $h->{rbuf};
222 0           $out_hndl->push_write($h->{rbuf});
223 0           substr $h->{rbuf}, 0, $length, '';
224 0           });
225 0           });
226              
227 0           $in_hndl = AnyEvent::Handle->new(fh => $input);
228             $in_hndl->on_read(sub {
229 0           my ($h) = @_;
230             $h->push_read(line => sub {
231 0           my ($h2, $line, $eol) = @_;
232 0           $fh->push_write($line . $eol);
233 0           });
234 0           });
235             $in_hndl->on_eof(sub {
236 0           $cv->send;
237 0           });
238 0           };
239              
240             my %post_opt = (
241             want_body_handle => 1,
242             tcp_connect => sub {
243 0     0     my ($host, $port, $connect_cb, $prepare_cb) = @_;
244 0           return tcp_connect('unix/', '/var/run/docker.sock', $connect_cb, $prepare_cb);
245             },
246 0           );
247              
248 0           my $uri = URI->new('http://localhost/v1.7/containers/'.$container.'/attach');
249 0           $uri->query_form(%options);
250              
251 0           http_request(POST => $uri->as_string, %post_opt, $callback);
252              
253 0           return $cv;
254             }
255              
256             1;
257              
258             =head1 NAME
259              
260             Net::Docker - Interface to the Docker API
261              
262             =head1 SYNOPSIS
263              
264             use Net::Docker;
265              
266             my $api = Net::Docker->new;
267              
268             my $id = $api->create(
269             Image => 'ubuntu',
270             Cmd => ['/bin/bash'],
271             AttachStdin => \1,
272             OpenStdin => \1,
273             Name => 'my-container',
274             );
275              
276             say $id;
277             $api->start($id);
278              
279             my $cv = $api->streaming_logs($id,
280             stream => 1, logs => 1,
281             stdin => 1, stderr => 1, stdout => 1,
282             in_fh => \*STDIN, out_fh => \*STDOUT,
283             );
284             $cv->recv;
285              
286             =head1 DESCRIPTION
287              
288             Perl module for using the Docker Remote API.
289              
290             =head1 AUTHOR
291              
292             Peter Stuifzand Epeter@stuifzand.euE
293              
294             =head1 COPYRIGHT
295              
296             Copyright 2013 - Peter Stuifzand
297              
298             =head1 LICENSE
299              
300             This library is free software; you can redistribute it and/or modify
301             it under the same terms as Perl itself.
302              
303             =head1 SEE ALSO
304              
305             L
306              
307             =cut