File Coverage

blib/lib/Net/Proxmox/VE.pm
Criterion Covered Total %
statement 36 158 22.7
branch 0 92 0.0
condition 0 30 0.0
subroutine 12 22 54.5
pod 10 10 100.0
total 58 312 18.5


line stmt bran cond sub pod time code
1             #!/bin/false
2             # vim: softtabstop=2 tabstop=2 shiftwidth=2 ft=perl expandtab smarttab
3             # PODNAME: Net::Proxmox::VE
4             # ABSTRACT: Pure perl API for Proxmox virtualisation
5              
6 1     1   71047 use strict;
  1         9  
  1         30  
7 1     1   5 use warnings;
  1         2  
  1         46  
8              
9             package Net::Proxmox::VE;
10             $Net::Proxmox::VE::VERSION = '0.38';
11              
12 1     1   5 use Carp qw( croak );
  1         2  
  1         55  
13 1     1   572 use HTTP::Headers;
  1         7074  
  1         38  
14 1     1   522 use HTTP::Request::Common qw(GET POST DELETE);
  1         19721  
  1         67  
15 1     1   492 use JSON::MaybeXS qw(decode_json);
  1         5743  
  1         56  
16 1     1   700 use LWP::UserAgent;
  1         31037  
  1         34  
17              
18             # done
19 1     1   425 use Net::Proxmox::VE::Pools;
  1         3  
  1         62  
20 1     1   435 use Net::Proxmox::VE::Storage;
  1         3  
  1         53  
21 1     1   465 use Net::Proxmox::VE::Access;
  1         3  
  1         123  
22 1     1   479 use Net::Proxmox::VE::Cluster;
  1         2  
  1         111  
23              
24             # wip
25 1     1   533 use Net::Proxmox::VE::Nodes;
  1         2  
  1         1690  
26              
27              
28             sub action {
29              
30 0 0   0 1   my $self = shift or return;
31 0           my %params = @_;
32              
33 0 0         unless (%params) {
34 0           croak 'new requires a hash for params';
35             }
36 0 0         croak 'path param is required' unless $params{path};
37              
38 0   0       $params{method} ||= 'GET';
39 0   0       $params{post_data} ||= {};
40              
41             # Check its a valid method
42              
43             croak "invalid http method specified: $params{method}"
44 0 0         unless $params{method} =~ m/^(GET|PUT|POST|DELETE)$/;
45              
46             # Strip prefixed / to path if present
47 0           $params{path} =~ s{^/}{};
48              
49             # Collapse duplicate slashes
50 0           $params{path} =~ s{//+}{/};
51              
52 0 0 0       unless ( $params{path} eq 'access/domains'
53             or $self->check_login_ticket )
54             {
55             print "DEBUG: invalid login ticket\n"
56 0 0         if $self->{params}->{debug};
57 0 0         return unless $self->login();
58             }
59              
60 0           my $url = $self->url_prefix . '/api2/json/' . $params{path};
61              
62             # Grab the useragent
63 0           my $ua = $self->{ua};
64              
65             # Set up the request object
66 0           my $request = HTTP::Request->new();
67 0           $request->uri($url);
68             $request->header( 'Cookie' => 'PVEAuthCookie=' . $self->{ticket}->{ticket} )
69 0 0         if defined $self->{ticket};
70              
71 0           my $response;
72              
73             # all methods other than get require the prevention token
74             # (ie anything that makes modification)
75 0 0         unless ( $params{method} eq 'GET' ) {
76             $request->header(
77 0           'CSRFPreventionToken' => $self->{ticket}->{CSRFPreventionToken} );
78             }
79              
80 0 0         if ( $params{method} =~ m/^(PUT|POST)$/ ) {
    0          
81 0           $request->method( $params{method} );
82 0           my $content = join '&', map { $_ . '=' . $params{post_data}->{$_} }
83 0           sort keys %{ $params{post_data} };
  0            
84 0           $request->content($content);
85 0           $response = $ua->request($request);
86             }
87             elsif ( $params{method} =~ m/^(GET|DELETE)$/ ) {
88 0           $request->method( $params{method} );
89 0 0         if ( %{$params{post_data}} ) {
  0            
90 0           my $qstring = join '&', map { $_ . '=' . $params{post_data}->{$_} }
91 0           sort keys %{ $params{post_data} };
  0            
92 0           $request->uri( "$url?$qstring" );
93             }
94 0           $response = $ua->request($request);
95             }
96             else {
97              
98             # this shouldnt happen
99 0           croak 'this shouldnt happen';
100             }
101              
102 0 0         if ( $response->is_success ) {
103             print "DEBUG: successful request: " . $request->as_string . "\n"
104 0 0         if $self->{params}->{debug};
105              
106             # my $content = $response->decoded_content;
107 0           my $data = decode_json( $response->decoded_content );
108              
109 0 0 0       if ( ref $data eq 'HASH'
110             && exists $data->{data} )
111             {
112 0 0         if ( ref $data->{data} eq 'ARRAY' ) {
113              
114             return wantarray
115 0           ? @{ $data->{data} }
116 0 0         : $data->{data};
117              
118             }
119              
120             return $data->{data}
121              
122 0           }
123              
124             # just return true
125 0           return 1
126              
127             }
128             else {
129 0           croak "WARNING: request failed: " . $request->as_string . "\n" .
130             "WARNING: response status: " . $response->status_line . "\n";
131             }
132             return
133              
134 0           }
135              
136              
137             sub api_version {
138 0 0   0 1   my $self = shift or return;
139 0           return $self->action( path => '/version', method => 'GET' );
140             }
141              
142              
143             sub api_version_check {
144 0 0   0 1   my $self = shift or return;
145              
146 0           my $data = $self->api_version;
147              
148 0 0 0       if ( ref $data eq 'HASH' && $data->{version} ) {
149 0           my ($version) = $data->{version} =~ m/^(\d+)/;
150 0 0         return 1 if $version > 2.0;
151             }
152              
153 0           return;
154             }
155              
156              
157             sub debug {
158 0 0   0 1   my $self = shift or return;
159 0           my $d = shift;
160              
161 0 0         if ($d) {
    0          
162 0           $self->{params}->{debug} = 1;
163             }
164             elsif ( defined $d ) {
165 0           $self->{params}->{debug} = 0;
166             }
167              
168 0 0         return 1 if $self->{params}->{debug};
169             return
170              
171 0           }
172              
173              
174             sub delete {
175 0 0   0 1   my $self = shift or return;
176 0 0         my @path = @_ or return; # using || breaks this
177              
178 0 0         if ( $self->nodes ) {
179 0           return $self->action( path => join( '/', @path ), method => 'DELETE' );
180             }
181             return
182 0           }
183              
184              
185             sub get {
186 0 0   0 1   my $self = shift or return;
187 0           my $post_data;
188 0 0         $post_data = pop
189             if ref $_[-1];
190 0 0         my @path = @_ or return; # using || breaks this
191              
192             # Calling nodes method here would call get method itself and so on
193             # Commented out to avoid an infinite loop
194             #if ( $self->nodes ) {
195 0           return $self->action( path => join( '/', @path ), method => 'GET', post_data => $post_data );
196             #}
197 0           return;
198             }
199              
200              
201             sub new {
202              
203 0     0 1   my $c = shift;
204 0           my @p = @_;
205 0   0       my $class = ref($c) || $c;
206              
207 0           my %params;
208              
209 0 0         if ( scalar @p == 1 ) {
    0          
210              
211 0 0         croak 'new() requires a hash for params'
212             unless ref $p[0] eq 'HASH';
213              
214 0           %params = %{ $p[0] };
  0            
215              
216             }
217             elsif ( scalar @p % 2 != 0 ) { # 'unless' is better than != but anyway
218 0           croak 'new() called with an odd number of parameters'
219              
220             }
221             else {
222 0 0         %params = @p
223             or croak 'new() requires a hash for params';
224             }
225              
226 0   0       my $host = delete $params{host} || croak 'host param is required';
227 0   0       my $password = delete $params{password} || croak 'password param is required';
228 0   0       my $port = delete $params{port} || 8006;
229 0   0       my $username = delete $params{username} || 'root';
230 0   0       my $realm = delete $params{realm} || 'pam';
231 0           my $debug = delete $params{debug};
232 0   0       my $timeout = delete $params{timeout} || 10;
233 0           my $ssl_opts = delete $params{ssl_opts};
234 0 0         croak 'unknown parameters to new: ' . join(', ', keys %params) if keys %params;
235              
236             my $self->{params} = {
237 0           host => $host,
238             password => $password,
239             port => $port,
240             username => $username,
241             realm => $realm,
242             debug => $debug,
243             timeout => $timeout,
244             ssl_opts => $ssl_opts,
245             };
246              
247 0           $self->{'ticket'} = undef;
248 0           $self->{'ticket_timestamp'} = undef;
249 0           $self->{'ticket_life'} = 7200; # 2 Hours
250              
251 0           my %lwpUserAgentOptions;
252 0 0         if ($ssl_opts) {
253 0           $lwpUserAgentOptions{ssl_opts} = $ssl_opts;
254             }
255              
256 0           my $ua = LWP::UserAgent->new( %lwpUserAgentOptions );
257 0           $ua->timeout($timeout);
258 0           $self->{ua} = $ua;
259              
260 0           bless $self, $class;
261 0           return $self
262              
263             }
264              
265              
266             sub post {
267              
268 0 0   0 1   my $self = shift or return;
269 0           my $post_data;
270 0 0         $post_data = pop
271             if ref $_[-1];
272 0 0         my @path = @_ or return; # using || breaks this
273              
274 0 0         if ( $self->nodes ) {
275              
276 0           return $self->action(
277             path => join( '/', @path ),
278             method => 'POST',
279             post_data => $post_data
280             )
281              
282             }
283             return
284 0           }
285              
286              
287             sub put {
288              
289 0 0   0 1   my $self = shift or return;
290 0           my $post_data;
291 0 0         $post_data = pop
292             if ref $_[-1];
293 0 0         my @path = @_ or return; # using || breaks this
294              
295 0 0         if ( $self->nodes ) {
296              
297 0           return $self->action(
298             path => join( '/', @path ),
299             method => 'PUT',
300             post_data => $post_data
301             )
302              
303             }
304             return
305 0           }
306              
307              
308              
309             sub url_prefix {
310              
311 0 0   0 1   my $self = shift or return;
312              
313             # Prepare prefix for request
314             my $url_prefix = sprintf( 'https://%s:%s',
315             $self->{params}->{host},
316 0           $self->{params}->{port} );
317              
318 0           return $url_prefix
319              
320             }
321              
322              
323             1;
324              
325             __END__