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             # 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   69545 use strict;
  1         10  
  1         29  
7 1     1   5 use warnings;
  1         3  
  1         47  
8              
9             package Net::Proxmox::VE;
10             $Net::Proxmox::VE::VERSION = '0.37';
11              
12 1     1   6 use Carp qw( croak );
  1         1  
  1         53  
13 1     1   609 use HTTP::Headers;
  1         6858  
  1         50  
14 1     1   500 use HTTP::Request::Common qw(GET POST DELETE);
  1         19240  
  1         68  
15 1     1   469 use JSON::MaybeXS qw(decode_json);
  1         5798  
  1         55  
16 1     1   725 use LWP::UserAgent;
  1         31125  
  1         37  
17              
18             # done
19 1     1   437 use Net::Proxmox::VE::Pools;
  1         2  
  1         112  
20 1     1   412 use Net::Proxmox::VE::Storage;
  1         2  
  1         53  
21 1     1   491 use Net::Proxmox::VE::Access;
  1         3  
  1         137  
22 1     1   469 use Net::Proxmox::VE::Cluster;
  1         3  
  1         109  
23              
24             # wip
25 1     1   531 use Net::Proxmox::VE::Nodes;
  1         2  
  1         1614  
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         croak 'host param is required' unless $params{'host'};
227 0 0         croak 'password param is required' unless $params{'password'};
228              
229 0   0       $params{port} ||= 8006;
230 0   0       $params{username} ||= 'root';
231 0   0       $params{realm} ||= 'pam';
232 0   0       $params{debug} ||= undef;
233 0   0       $params{timeout} ||= 10;
234              
235 0           my $self->{params} = \%params;
236 0           $self->{'ticket'} = undef;
237 0           $self->{'ticket_timestamp'} = undef;
238 0           $self->{'ticket_life'} = 7200; # 2 Hours
239              
240 0           my %lwpUserAgentOptions;
241 0 0         if ($self->{params}->{ssl_opts}) {
242 0           $lwpUserAgentOptions{ssl_opts} = $self->{params}->{ssl_opts};
243             }
244              
245 0           my $ua = LWP::UserAgent->new( %lwpUserAgentOptions );
246 0           $ua->timeout($self->{params}->{timeout});
247 0           $self->{ua} = $ua;
248              
249 0           bless $self, $class;
250 0           return $self
251              
252             }
253              
254              
255             sub post {
256              
257 0 0   0 1   my $self = shift or return;
258 0           my $post_data;
259 0 0         $post_data = pop
260             if ref $_[-1];
261 0 0         my @path = @_ or return; # using || breaks this
262              
263 0 0         if ( $self->nodes ) {
264              
265 0           return $self->action(
266             path => join( '/', @path ),
267             method => 'POST',
268             post_data => $post_data
269             )
270              
271             }
272             return
273 0           }
274              
275              
276             sub put {
277              
278 0 0   0 1   my $self = shift or return;
279 0           my $post_data;
280 0 0         $post_data = pop
281             if ref $_[-1];
282 0 0         my @path = @_ or return; # using || breaks this
283              
284 0 0         if ( $self->nodes ) {
285              
286 0           return $self->action(
287             path => join( '/', @path ),
288             method => 'PUT',
289             post_data => $post_data
290             )
291              
292             }
293             return
294 0           }
295              
296              
297              
298             sub url_prefix {
299              
300 0 0   0 1   my $self = shift or return;
301              
302             # Prepare prefix for request
303             my $url_prefix = sprintf( 'https://%s:%s',
304             $self->{params}->{host},
305 0           $self->{params}->{port} );
306              
307 0           return $url_prefix
308              
309             }
310              
311              
312             1;
313              
314             __END__