File Coverage

blib/lib/Net/Proxmox/VE.pm
Criterion Covered Total %
statement 36 156 23.0
branch 0 94 0.0
condition 0 26 0.0
subroutine 12 22 54.5
pod 10 10 100.0
total 58 308 18.8


line stmt bran cond sub pod time code
1             #!/bin/false
2             # PODNAME: Net::Proxmox::VE
3             # ABSTRACT: Pure perl API for Proxmox virtualisation
4              
5 1     1   63511 use strict;
  1         11  
  1         28  
6 1     1   5 use warnings;
  1         2  
  1         49  
7              
8             package Net::Proxmox::VE;
9             $Net::Proxmox::VE::VERSION = '0.36';
10              
11 1     1   5 use Carp qw( croak );
  1         2  
  1         52  
12 1     1   525 use HTTP::Headers;
  1         3717  
  1         50  
13 1     1   904 use HTTP::Request::Common qw(GET POST DELETE);
  1         16422  
  1         111  
14 1     1   727 use JSON qw(decode_json);
  1         9588  
  1         8  
15 1     1   960 use LWP::UserAgent;
  1         26694  
  1         52  
16              
17             # done
18 1     1   668 use Net::Proxmox::VE::Pools;
  1         3  
  1         76  
19 1     1   366 use Net::Proxmox::VE::Storage;
  1         4  
  1         49  
20 1     1   420 use Net::Proxmox::VE::Access;
  1         3  
  1         142  
21 1     1   532 use Net::Proxmox::VE::Cluster;
  1         3  
  1         94  
22              
23             # wip
24 1     1   447 use Net::Proxmox::VE::Nodes;
  1         3  
  1         1562  
25              
26              
27             sub action {
28              
29 0 0   0 1   my $self = shift or return;
30 0           my %params = @_;
31              
32 0 0         unless (%params) {
33 0           croak 'new requires a hash for params';
34             }
35 0 0         croak 'path param is required' unless $params{path};
36              
37 0   0       $params{method} ||= 'GET';
38 0   0       $params{post_data} ||= {};
39              
40             # Check its a valid method
41              
42             croak "invalid http method specified: $params{method}"
43 0 0         unless $params{method} =~ m/^(GET|PUT|POST|DELETE)$/;
44              
45             # Strip prefixed / to path if present
46 0           $params{path} =~ s{^/}{};
47              
48             # Collapse duplicate slashes
49 0           $params{path} =~ s{//+}{/};
50              
51 0 0 0       unless ( $params{path} eq 'access/domains'
52             or $self->check_login_ticket )
53             {
54             print "DEBUG: invalid login ticket\n"
55 0 0         if $self->{params}->{debug};
56 0 0         return unless $self->login();
57             }
58              
59 0           my $url = $self->url_prefix . '/api2/json/' . $params{path};
60              
61             # Grab the useragent
62 0           my $ua = $self->{ua};
63              
64             # Set up the request object
65 0           my $request = HTTP::Request->new();
66 0           $request->uri($url);
67             $request->header( 'Cookie' => 'PVEAuthCookie=' . $self->{ticket}->{ticket} )
68 0 0         if defined $self->{ticket};
69              
70 0           my $response;
71              
72             # all methods other than get require the prevention token
73             # (ie anything that makes modification)
74 0 0         unless ( $params{method} eq 'GET' ) {
75             $request->header(
76 0           'CSRFPreventionToken' => $self->{ticket}->{CSRFPreventionToken} );
77             }
78              
79 0 0         if ( $params{method} =~ m/^(PUT|POST)$/ ) {
    0          
80 0           $request->method( $params{method} );
81 0           my $content = join '&', map { $_ . '=' . $params{post_data}->{$_} }
82 0           sort keys %{ $params{post_data} };
  0            
83 0           $request->content($content);
84 0           $response = $ua->request($request);
85             }
86             elsif ( $params{method} =~ m/^(GET|DELETE)$/ ) {
87 0           $request->method( $params{method} );
88 0 0         if ( %{$params{post_data}} ) {
  0            
89 0           my $qstring = join '&', map { $_ . '=' . $params{post_data}->{$_} }
90 0           sort keys %{ $params{post_data} };
  0            
91 0           $request->uri( "$url?$qstring" );
92             }
93 0           $response = $ua->request($request);
94             }
95             else {
96              
97             # this shouldnt happen
98 0           croak 'this shouldnt happen';
99             }
100              
101 0 0         if ( $response->is_success ) {
102             print "DEBUG: successful request: " . $request->as_string . "\n"
103 0 0         if $self->{params}->{debug};
104              
105             # my $content = $response->decoded_content;
106 0           my $data = decode_json( $response->decoded_content );
107              
108 0 0 0       if ( ref $data eq 'HASH'
109             && exists $data->{data} )
110             {
111 0 0         if ( ref $data->{data} eq 'ARRAY' ) {
112              
113             return wantarray
114 0           ? @{ $data->{data} }
115 0 0         : $data->{data};
116              
117             }
118              
119             return $data->{data}
120              
121 0           }
122              
123             # just return true
124 0           return 1
125              
126             }
127             else {
128 0           croak "WARNING: request failed: " . $request->as_string . "\n" .
129             "WARNING: response status: " . $response->status_line . "\n";
130             }
131             return
132              
133 0           }
134              
135              
136             sub api_version {
137 0 0   0 1   my $self = shift or return;
138 0           return $self->action( path => '/version', method => 'GET' );
139             }
140              
141              
142             sub api_version_check {
143 0 0   0 1   my $self = shift or return;
144              
145 0           my $data = $self->api_version;
146              
147 0 0 0       if ( ref $data eq 'HASH' && $data->{version} ) {
148 0           my ($version) = $data->{version} =~ m/^(\d+)/;
149 0 0         return 1 if $version > 2.0;
150             }
151              
152 0           return;
153             }
154              
155              
156             sub debug {
157 0 0   0 1   my $self = shift or return;
158 0           my $d = shift;
159              
160 0 0         if ($d) {
    0          
161 0           $self->{debug} = 1;
162             }
163             elsif ( defined $d ) {
164 0           $self->{debug} = 0;
165             }
166              
167 0 0         return 1 if $self->{debug};
168             return
169              
170 0           }
171              
172              
173             sub delete {
174 0 0   0 1   my $self = shift or return;
175 0 0         my @path = @_ or return; # using || breaks this
176              
177 0 0         if ( $self->nodes ) {
178 0           return $self->action( path => join( '/', @path ), method => 'DELETE' );
179             }
180             return
181 0           }
182              
183              
184             sub get {
185 0 0   0 1   my $self = shift or return;
186 0           my $post_data;
187 0 0         $post_data = pop
188             if ref $_[-1];
189 0 0         my @path = @_ or return; # using || breaks this
190              
191             # Calling nodes method here would call get method itself and so on
192             # Commented out to avoid an infinite loop
193             #if ( $self->nodes ) {
194 0           return $self->action( path => join( '/', @path ), method => 'GET', post_data => $post_data );
195             #}
196 0           return;
197             }
198              
199              
200             sub new {
201              
202 0     0 1   my $c = shift;
203 0           my @p = @_;
204 0   0       my $class = ref($c) || $c;
205              
206 0           my %params;
207              
208 0 0         if ( scalar @p == 1 ) {
    0          
209              
210 0 0         croak 'new() requires a hash for params'
211             unless ref $p[0] eq 'HASH';
212              
213 0           %params = %{ $p[0] };
  0            
214              
215             }
216             elsif ( scalar @p % 2 != 0 ) { # 'unless' is better than != but anyway
217 0           croak 'new() called with an odd number of parameters'
218              
219             }
220             else {
221 0 0         %params = @p
222             or croak 'new() requires a hash for params';
223             }
224              
225 0 0         croak 'host param is required' unless $params{'host'};
226 0 0         croak 'password param is required' unless $params{'password'};
227              
228 0   0       $params{port} ||= 8006;
229 0   0       $params{username} ||= 'root';
230 0   0       $params{realm} ||= 'pam';
231 0   0       $params{debug} ||= undef;
232 0   0       $params{timeout} ||= 10;
233              
234 0           my $self->{params} = \%params;
235 0           $self->{'ticket'} = undef;
236 0           $self->{'ticket_timestamp'} = undef;
237 0           $self->{'ticket_life'} = 7200; # 2 Hours
238              
239 0           my %lwpUserAgentOptions;
240 0 0         if ($self->{params}->{ssl_opts}) {
241 0           $lwpUserAgentOptions{ssl_opts} = $self->{params}->{ssl_opts};
242             }
243              
244 0           my $ua = LWP::UserAgent->new( %lwpUserAgentOptions );
245 0           $ua->timeout($self->{params}->{timeout});
246 0           $self->{ua} = $ua;
247              
248 0           bless $self, $class;
249 0           return $self
250              
251             }
252              
253              
254             sub post {
255              
256 0 0   0 1   my $self = shift or return;
257 0           my $post_data;
258 0 0         $post_data = pop
259             if ref $_[-1];
260 0 0         my @path = @_ or return; # using || breaks this
261              
262 0 0         if ( $self->nodes ) {
263              
264 0           return $self->action(
265             path => join( '/', @path ),
266             method => 'POST',
267             post_data => $post_data
268             )
269              
270             }
271             return
272 0           }
273              
274              
275             sub put {
276              
277 0 0   0 1   my $self = shift or return;
278 0           my $post_data;
279 0 0         $post_data = pop
280             if ref $_[-1];
281 0 0         my @path = @_ or return; # using || breaks this
282              
283 0 0         if ( $self->nodes ) {
284              
285 0           return $self->action(
286             path => join( '/', @path ),
287             method => 'PUT',
288             post_data => $post_data
289             )
290              
291             }
292             return
293 0           }
294              
295              
296              
297             sub url_prefix {
298              
299 0 0   0 1   my $self = shift or return;
300              
301             # Prepare prefix for request
302             my $url_prefix = sprintf( 'https://%s:%s',
303             $self->{params}->{host},
304 0           $self->{params}->{port} );
305              
306 0           return $url_prefix
307              
308             }
309              
310              
311             1;
312              
313             =pod
314              
315             =encoding UTF-8
316              
317             =head1 NAME
318              
319             Net::Proxmox::VE - Pure perl API for Proxmox virtualisation
320              
321             =head1 VERSION
322              
323             version 0.36
324              
325             =head1 SYNOPSIS
326              
327             use Net::Proxmox::VE;
328              
329             %args = (
330             host => 'proxmox.local.domain',
331             password => 'barpassword',
332             username => 'root', # optional
333             port => 8006, # optional
334             realm => 'pam', # optional
335             );
336              
337             $host = Net::Proxmox::VE->new(%args);
338              
339             $host->login() or die ('Couldn\'t log in to proxmox host');
340              
341             =head1 DESCRIPTION
342              
343             This Class provides the framework for talking to Proxmox VE 2.0 API instances.
344             This just provides a get/delete/put/post abstraction layer as methods on Proxmox VE REST API
345             This also handles the ticket headers required for authentication
346              
347             More details on the API can be found at L and
348             L
349              
350             This class provides the building blocks for someone wanting to use PHP to talk to Proxmox 2.0. Relatively simple piece of code, just provides a get/put/post/delete abstraction layer as methods on top of Proxmox's REST API, while also handling the Login Ticket headers required for authentication.
351              
352             =head1 WARNING
353              
354             We are still moving things around and trying to come up with something
355             that makes sense. We havent yet implemented all the API functions,
356             so far we only have a basic internal abstraction of the REST interface
357             and a few modules for each function tree within the API.
358              
359             Any enchancements are greatly appreciated ! (use github, link below)
360              
361             Please dont be offended if we refactor and rework submissions.
362             Perltidy with default settings is prefered style.
363              
364             Oh, our tests are all against a running server. Care to help make them better?
365              
366             =head1 DESIGN NOTE
367              
368             This API would be far nicer if it returned nice objects representing different aspects of the system.
369             Such an arrangement would be far better than how this module is currently layed out. It might also be
370             less repetitive code.
371              
372             =head1 METHODS
373              
374             =head2 action
375              
376             This calls raw actions against your proxmox server.
377             Ideally you don't use this directly.
378              
379             =head2 api_version
380              
381             Returns the API version of the proxmox server we are talking to
382              
383             =head2 api_version_check
384              
385             Checks that the api we are talking to is at least version 2.0
386              
387             Returns true if the api version is at least 2.0 (perl style true or false)
388              
389             =head2 debug
390              
391             Has a single optional argument of 1 or 0 representing enable or disable debugging.
392              
393             Undef (ie no argument) leaves the debug status untouched, making this method call simply a query.
394              
395             Returns the resultant debug status (perl style true or false)
396              
397             =head2 delete
398              
399             An action helper method that just takes a path as an argument and returns the
400             value of action() with the DELETE method
401              
402             =head2 get
403              
404             An action helper method that just takes a path as an argument and returns the
405             value of action with the GET method
406              
407             =head2 new
408              
409             Creates the Net::Proxmox::VE object and returns it.
410              
411             Examples...
412              
413             my $obj = Net::Proxmox::VE->new(%args);
414             my $obj = Net::Proxmox::VE->new(\%args);
415              
416             Valid arguments are...
417              
418             =over 4
419              
420             =item I
421              
422             Proxmox host instance to interact with. Required so no default.
423              
424             =item I
425              
426             User name used for authentication. Defaults to 'root', optional.
427              
428             =item I
429              
430             Pass word user for authentication. Required so no default.
431              
432             =item I
433              
434             TCP port number used to by the Proxmox host instance. Defaults to 8006, optional.
435              
436             =item I
437              
438             Authentication realm to request against. Defaults to 'pam' (local auth), optional.
439              
440             =item I
441              
442             If you're using a self-signed certificate, SSL verification is going to fail, and we need to tell C not to attempt certificate verification.
443              
444             This option is passed on as C options to Cnew()>, ultimately for C.
445              
446             Using it like this, causes C and C not to attempt SSL verification:
447              
448             use IO::Socket::SSL qw(SSL_VERIFY_NONE);
449             ..
450             %args = (
451             ...
452             ssl_opts => {
453             SSL_verify_mode => SSL_VERIFY_NONE,
454             verify_hostname => 0
455             },
456             ...
457             );
458             my $proxmox = Net::Proxmox::VE->new(%args);
459              
460             Your connection will work now, but B.
461              
462             =item I
463              
464             Enabling debugging of this API (not related to proxmox debugging in any way). Defaults to false, optional.
465              
466             =back
467              
468             =head2 post
469              
470             An action helper method that takes two parameters: $path, \%post_data
471             $path to post to, hash ref to %post_data
472              
473             You are returned what action() with the POST method returns
474              
475             =head2 put
476              
477             An action helper method that takes two parameters:
478             path
479             hash ref to post data
480             your returned what post returns
481              
482             =head2 url_prefix
483              
484             returns the url prefix used in the rest api calls
485              
486             =head1 SEE ALSO
487              
488             =over 4
489              
490             =item Proxmox Website
491              
492             http://www.proxmox.com
493              
494             =item API reference
495              
496             http://pve.proxmox.com/pve2-api-doc
497              
498             =back
499              
500             =head1 AUTHOR
501              
502             Brendan Beveridge , Dean Hamstead
503              
504             =head1 COPYRIGHT AND LICENSE
505              
506             This software is Copyright (c) 2022 by Dean Hamstad.
507              
508             This is free software, licensed under:
509              
510             The MIT (X11) License
511              
512             =cut
513              
514             __END__