File Coverage

blib/lib/VMware/vCloudDirector2/API.pm
Criterion Covered Total %
statement 219 303 72.2
branch 63 164 38.4
condition 5 11 45.4
subroutine 63 73 86.3
pod n/a
total 350 551 63.5


line stmt bran cond sub pod time code
1             package VMware::vCloudDirector2::API;
2              
3             # ABSTRACT: Module to do stuff!
4              
5 4     4   823 use strict;
  4         12  
  4         141  
6 4     4   23 use warnings;
  4         10  
  4         119  
7 4     4   49 use v5.10; # needed for state variable
  4         16  
8              
9             our $VERSION = '0.107'; # VERSION
10             our $AUTHORITY = 'cpan:NIGELM'; # AUTHORITY
11              
12 4     4   623 use Moose;
  4         473640  
  4         31  
13 4     4   29251 use Method::Signatures;
  4         64769  
  4         36  
14 4     4   2774 use MooseX::Types::Path::Tiny qw(Path);
  4         534637  
  4         35  
15 4     4   13672 use MooseX::Types::URI qw(Uri);
  4         622078  
  4         25  
16 4     4   11191 use Cpanel::JSON::XS;
  4         11800  
  4         282  
17 4     4   2177 use LWP::UserAgent::Determined;
  4         124367  
  4         133  
18 4     4   2024 use MIME::Base64;
  4         2611  
  4         273  
19 4     4   1808 use Mozilla::CA;
  4         1060  
  4         129  
20 4     4   31 use Path::Tiny;
  4         10  
  4         215  
21 4     4   1944 use Ref::Util qw(is_plain_hashref is_plain_arrayref);
  4         6708  
  4         348  
22 4     4   32 use Scalar::Util qw(looks_like_number);
  4         43  
  4         252  
23 4     4   2019 use Syntax::Keyword::Try 0.04; # Earlier versions throw errors
  4         2812  
  4         23  
24 4     4   2193 use VMware::vCloudDirector2::Error;
  4         16  
  4         181  
25 4     4   2199 use VMware::vCloudDirector2::Object;
  4         19  
  4         237  
26 4     4   2350 use XML::Fast qw(); # just for the versions document
  4         50838  
  4         135  
27 4     4   2071 use Data::Dump qw(pp);
  4         22278  
  4         886  
28              
29             # ------------------------------------------------------------------------
30              
31              
32             has hostname => ( is => 'ro', isa => 'Str', required => 1 );
33             has username => ( is => 'ro', isa => 'Str', required => 1 );
34             has password => ( is => 'ro', isa => 'Str', required => 1 );
35             has orgname => ( is => 'ro', isa => 'Str', required => 1, default => 'System' );
36             has ssl_verify => ( is => 'ro', isa => 'Bool', default => 1 );
37             has debug => ( is => 'rw', isa => 'Int', default => 0, );
38             has timeout => ( is => 'rw', isa => 'Int', default => 120 ); # Defaults to 120 seconds
39             has _debug_trace_directory =>
40             ( is => 'ro', isa => Path, coerce => 1, predicate => '_has_debug_trace_directory' );
41              
42             has default_accept_header => (
43             is => 'ro',
44             isa => 'Str',
45             lazy => 1,
46             builder => '_build_default_accept_header',
47             clearer => '_clear_default_accept_header',
48             );
49              
50             has _base_url => (
51             is => 'ro',
52             isa => Uri,
53             lazy => 1,
54             builder => '_build_base_url',
55             writer => '_set_base_url',
56             clearer => '_clear_base_url',
57             );
58              
59             has ssl_ca_file => (
60             is => 'ro',
61             isa => Path,
62             coerce => 1,
63             lazy => 1,
64             builder => '_build_ssl_ca_file'
65             );
66              
67 4 0   4   4058 method _build_ssl_ca_file () { return path( Mozilla::CA::SSL_ca_file() ); }
  0     0   0  
  0         0  
  0         0  
68 4 50   4   3256 method _build_base_url () { return URI->new( sprintf( 'https://%s/', $self->hostname ) ); }
  1     1   3  
  1         4  
  1         32  
69 4 50   4   3215 method _build_default_accept_header () { return ( 'application/*+json;version=' . $self->api_version ); }
  1     1   3  
  1         4  
  1         33  
70 4 0   4   11953 method _debug (@parameters) { warn join( '', '# ', @parameters, "\n" ) if ( $self->debug ); }
  0     0   0  
  0         0  
  0         0  
71              
72             # ------------------------------------------------------------------------
73              
74 4 50   4   9592 method BUILD ($args) {
  1 50   1   2  
  1         4  
  1         2  
  1         3  
75              
76             # deal with setting debug if needed
77 1         3 my $env_debug = $ENV{VCLOUD_API_DEBUG};
78 1 50       36 if ( defined($env_debug) ) {
79 0 0       0 $self->debug($env_debug) if ( looks_like_number($env_debug) );
80             }
81             }
82              
83             # ------------------------------------------------------------------------
84             has _ua => (
85             is => 'ro',
86             isa => 'LWP::UserAgent',
87             lazy => 1,
88             clearer => '_clear_ua',
89             builder => '_build_ua'
90             );
91              
92             has _ua_module_version => (
93             is => 'ro',
94             isa => 'Str',
95             default => sub { our $VERSION //= '0.00'; sprintf( '%s/%s', __PACKAGE__, $VERSION ) }
96             );
97              
98 4 0   4   3915 method _build_ua () {
  0     0   0  
  0         0  
99 0         0 return LWP::UserAgent::Determined->new(
100             agent => $self->_ua_module_version . ' ',
101             cookie_jar => {},
102             ssl_opts => { verify_hostname => $self->ssl_verify, SSL_ca_file => $self->ssl_ca_file },
103             timeout => $self->timeout,
104             env_proxy => 1,
105             );
106             }
107              
108             # ------------------------------------------------------------------------
109             has _json => (
110             is => 'ro',
111             isa => 'Cpanel::JSON::XS',
112             lazy => 1,
113             builder => '_build_json',
114             );
115              
116 4 50   4   3295 method _build_json () { return Cpanel::JSON::XS->new->utf8->allow_blessed->convert_blessed; }
  1     1   3  
  1         4  
  1         42  
117              
118             # ------------------------------------------------------------------------
119 4 50   4   9284 method _decode_xml_response ($response) {
  1 50   1   3  
  1         4  
  1         3  
  1         4  
120 1         8 my $content = $response->decoded_content;
121 1 50       2226 return if ( length($content) == 0 );
122              
123 1 50       9 VMware::vCloudDirector2::Error->throw(
124             { message => "Not a XML response as expected - $content", response => $response } )
125             unless ( $response->content_type() =~ m|\bxml\b| );
126             try {
127             return unless ( defined($content) and length($content) );
128             return XML::Fast::xml2hash($content);
129             }
130 1         39 catch {
131             VMware::vCloudDirector::Error->throw(
132             { message => "XML decode failed - " . join( ' ', $@ ),
133             response => $response
134             }
135             );
136             }
137             }
138              
139             # ------------------------------------------------------------------------
140 4 50   4   10143 method _decode_json_response ($response) {
  3 50   3   7  
  3         9  
  3         5  
  3         8  
141 3         10 my $content = $response->decoded_content;
142 3 50       386 return if ( length($content) == 0 );
143              
144 3 50       9 VMware::vCloudDirector2::Error->throw(
145             { message => "Not a JSON response as expected - $content",
146             response => $response
147             }
148             ) unless ( $response->content_type() =~ m|\bjson\b| );
149              
150             try {
151             return unless ( defined($content) and length($content) );
152             return $self->_json->decode($content);
153             }
154 3         86 catch {
155             VMware::vCloudDirector2::Error->throw(
156             { message => "JSON decode failed - " . join( ' ', $@ ),
157             response => $response
158             }
159             );
160             }
161             }
162              
163             # ------------------------------------------------------------------------
164 4 0   4   10100 method _encode_json_content ($hash) {
  0 0   0   0  
  0         0  
  0         0  
  0         0  
165 0         0 return $self->_json->encode($hash);
166             }
167              
168             # ------------------------------------------------------------------------
169 4 50   4   21837 method _request ($method, $url, $content?, $headers?) {
  4 50   4   8  
  4 50       13  
  4         7  
  4         11  
  4         9  
  4         8  
  4         6  
  4         17  
170 4         132 my $uri = URI->new_abs( $url, $self->_base_url );
171 4 50       1058 $self->_debug("API: _request [$method] $uri") if ( $self->debug );
172              
173 4         20 my $request = HTTP::Request->new( $method => $uri );
174              
175             # build headers
176 4 50 33     294 if ( defined $content && length($content) ) {
177 0         0 $request->content($content);
178 0         0 $request->header( 'Content-Length', length($content) );
179             }
180             else {
181 4         21 $request->header( 'Content-Length', 0 );
182             }
183              
184             # add any supplied headers
185 4         263 my $seen_accept;
186 4 100       12 if ( defined($headers) ) {
187 2         3 foreach my $h_name ( keys %{$headers} ) {
  2         9  
188 2         8 $request->header( $h_name, $headers->{$h_name} );
189 2 100       101 $seen_accept = 1 if ( lc($h_name) eq 'accept' );
190             }
191             }
192              
193             # set accept header
194 4 100       132 $request->header( 'Accept', $self->default_accept_header ) unless ($seen_accept);
195              
196             # set auth header
197 4 100       318 $request->header( 'x-vcloud-authorization', $self->authorization_token )
198             if ( $self->has_authorization_token );
199              
200             # do request
201 4         150 my $response;
202             try { $response = $self->_ua->request($request); }
203 4         11 catch {
204             VMware::vCloudDirector2::Error->throw(
205             { message => "$method request bombed",
206             uri => $uri,
207             request => $request,
208             }
209             );
210             }
211              
212             # if _debug_trace_directory is set - we dump info from each request out into
213             # a pair of files, one with the dumped response object, the other with the content
214 4 50       4620 if ( $self->_has_debug_trace_directory ) {
215 0         0 state $xcount = 0;
216 0 0       0 die "No trace directory - " . $self->_debug_trace_directory
217             unless ( $self->_debug_trace_directory->is_dir );
218 0         0 $self->_debug_trace_directory->child( sprintf( '%06d.txt', ++$xcount ) )
219             ->spew( pp($response) );
220 0 0       0 my $ext = ( $response->content_type =~ /json/ ) ? 'json' : 'xml';
221 0         0 $self->_debug_trace_directory->child( sprintf( '%06d.%s', $xcount, $ext ) )
222             ->spew( $response->decoded_content );
223             }
224              
225             # Throw if this went wrong
226 4 50       16 if ( $response->is_error ) {
227 0         0 my $message = "$method request failed [$uri] - ";
228             try {
229             my $decoded_response = $self->_decode_json_response($response);
230             $message .=
231             ( exists( $decoded_response->{message} ) )
232             ? $decoded_response->{message}
233             : ( 'Unknown after decode: ' . $response->decoded_content );
234             }
235 0         0 catch { $message .= 'Unknown'; }
236 0         0 VMware::vCloudDirector2::Error->throw(
237             { message => $message,
238             uri => $uri,
239             request => $request,
240             response => $response
241             }
242             );
243             }
244              
245 4         43 return $response;
246             }
247              
248             # ------------------------------------------------------------------------
249              
250              
251             has api_version => (
252             is => 'ro',
253             isa => 'Str',
254             lazy => 1,
255             clearer => '_clear_api_version',
256             builder => '_build_api_version'
257             );
258             has _url_login => (
259             is => 'rw',
260             isa => Uri,
261             lazy => 1,
262             clearer => '_clear_url_login',
263             builder => '_build_url_login'
264             );
265             has _raw_version => (
266             is => 'rw',
267             isa => 'HashRef',
268             lazy => 1,
269             clearer => '_clear_raw_version',
270             builder => '_build_raw_version'
271             );
272             has _raw_version_full => (
273             is => 'rw',
274             isa => 'HashRef',
275             lazy => 1,
276             clearer => '_clear_raw_version_full',
277             builder => '_build_raw_version_full'
278             );
279              
280 4 50   4   6457 method _build_api_version () { return $self->_raw_version->{Version}; }
  1     1   3  
  1         4  
  1         33  
281 4 50   4   3195 method _build_url_login () { return URI->new( $self->_raw_version->{LoginUrl} ); }
  1     1   3  
  1         4  
  1         34  
282              
283 4 50   4   3037 method _build_raw_version () {
  1     1   2  
  1         4  
284 1         45 my $hash = $self->_raw_version_full;
285 1         3 my $version = 0;
286 1         2 my $version_block;
287 1         3 for my $verblock ( @{ $hash->{SupportedVersions}{VersionInfo} } ) {
  1         4  
288 13 100 50     33 next if ( ( $verblock->{-deprecated} || '' ) eq 'true' );
289 4 50       14 if ( $verblock->{Version} > $version ) {
290 4         6 $version_block = $verblock;
291 4         9 $version = $verblock->{Version};
292             }
293             }
294              
295 1 50       34 $self->_debug("API: version used: $version") if ( $self->debug );
296 1 50       5 die "No valid version block seen" unless ($version_block);
297              
298 1         32 return $version_block;
299             }
300              
301 4 50   4   3664 method _build_raw_version_full () {
  1     1   2  
  1         4  
302 1         7 my $response = $self->_request( 'GET', '/api/versions', undef, { Accept => 'text/xml' } );
303 1         5 return $self->_decode_xml_response($response);
304             }
305              
306             # ------------------------ ------------------------------------------------
307              
308              
309             has authorization_token => (
310             is => 'ro',
311             isa => 'Str',
312             writer => '_set_authorization_token',
313             clearer => '_clear_authorization_token',
314             predicate => 'has_authorization_token'
315             );
316              
317             has current_session => (
318             is => 'ro',
319             isa => 'VMware::vCloudDirector2::Object',
320             clearer => '_clear_current_session',
321             predicate => 'has_current_session',
322             lazy => 1,
323             builder => '_build_current_session'
324             );
325              
326 4 50   4   3351 method _build_current_session () {
  1     1   3  
  1         4  
327 1         32 my $login_id = join( '@', $self->username, $self->orgname );
328 1         31 my $encoded_auth = 'Basic ' . MIME::Base64::encode( join( ':', $login_id, $self->password ) );
329 1 50       43 $self->_debug("API: attempting login as: $login_id") if ( $self->debug );
330 1         36 my $response =
331             $self->_request( 'POST', $self->_url_login, undef, { Authorization => $encoded_auth } );
332              
333             # if we got here then it succeeded, since we throw on failure
334 1         7 my $token = $response->header('x-vcloud-authorization');
335 1         90 $self->_set_authorization_token($token);
336 1 50       32 $self->_debug("API: authentication token: $token") if ( $self->debug );
337              
338             # we also reset the base url to match the login URL
339             ## $self->_set_base_url( $self->_url_login->clone->path('') );
340              
341 1         5 my ($session) = $self->_build_returned_objects($response);
342 1         40 return $session;
343             }
344              
345 4 50   4   3600 method login () { return $self->current_session; }
  1     1   3  
  1         5  
  1         36  
346              
347 4 0   4   3073 method logout () {
  0     0   0  
  0         0  
348 0 0       0 if ( $self->has_current_session ) {
349              
350             # just do this - it might fail, but little you can do now
351             try { $self->DELETE( $self->_url_login ); }
352 0         0 catch { warn "DELETE of session failed: ", @_; }
353             }
354 0         0 $self->_clear_api_data;
355             }
356              
357             # ------------------------------------------------------------------------
358 4 50   4   9600 method _build_returned_objects ($response) {
  2 50   2   4  
  2         7  
  2         3  
  2         6  
359              
360 2 50       8 if ( $response->is_success ) {
361 2 50       76 $self->_debug("API: building objects") if ( $self->debug );
362              
363 2         7 my $hash = $self->_decode_json_response($response);
364 2 50       7 unless ( defined($hash) ) {
365 0 0       0 $self->_debug("API: returned null object") if ( $self->debug );
366 0         0 return;
367             }
368              
369             # See if this is a list of things, in which case the type element will
370             # be thingList and it will have a set of thing in it
371 2         5 my $mime_type = $hash->{type};
372 2 50       13 unless ( defined($mime_type) ) {
373 0         0 $mime_type = $response->header('Content-Type');
374 0         0 $mime_type =~ s/;.*//;
375 0         0 $hash->{type} = $mime_type;
376             }
377 2 50       22 my $type = ( $mime_type =~ m|^application/vnd\..*\.(\w+)\+json$| ) ? $1 : $mime_type;
378 2 100       11 my $thing_type = ( substr( $type, -4, 4 ) eq 'List' ) ? substr( $type, 0, -4 ) : $type;
379              
380 2 100 66     14 if ( ( $type ne $thing_type )
      33        
381             and ( exists( $hash->{$thing_type} ) )
382             and is_plain_arrayref( $hash->{$thing_type} ) ) {
383 1         3 my @thing_objects;
384 1 50       34 $self->_debug("API: building a set of [$thing_type] objects") if ( $self->debug );
385 1         8 foreach my $thing ( $self->_listify( $hash->{$thing_type} ) ) {
386 5         174 my $object = VMware::vCloudDirector2::Object->new(
387             hash => $thing,
388             api => $self,
389             _partial_object => 1
390             );
391 5         14 push @thing_objects, $object;
392             }
393 1         48 return @thing_objects;
394             }
395              
396             # was not a list of things, so just objectify the one thing here
397             else {
398 1 50       45 $self->_debug("API: building a single [$thing_type] object") if ( $self->debug );
399 1         5 return VMware::vCloudDirector2::Object->new(
400             hash => $hash,
401             api => $self,
402             href => $response->request->uri,
403             );
404             }
405             }
406              
407             # there was an error here - so bomb out
408             else {
409 0         0 VMware::vCloudDirector2::Error->throw(
410             { message => 'Error reponse passed to object builder', response => $response } );
411             }
412             }
413              
414             # ------------------------------------------------------------------------
415              
416              
417 4 50   4   11206 method GET ($url) {
  1 50   1   2  
  1         5  
  1         3  
  1         3  
418 1         33 $self->current_session; # ensure/force valid session in place
419 1         3 my $response = $self->_request( 'GET', $url );
420 1         5 return $self->_build_returned_objects($response);
421             }
422              
423 4 50   4   9539 method GET_hash ($url) {
  1 50   1   2  
  1         5  
  1         3  
  1         3  
424 1         34 $self->current_session; # ensure/force valid session in place
425 1         5 my $response = $self->_request( 'GET', $url );
426 1         4 return $self->_decode_json_response($response);
427             }
428              
429 4 0   4   16662 method PUT ($url, $hash, $content_type) {
  0 0   0   0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
430 0         0 $self->current_session; # ensure/force valid session in place
431 0 0       0 my $content = is_plain_hashref($hash) ? $self->_encode_json_content($hash) : $hash;
432 0         0 my $response = $self->_request( 'PUT', $url, $content, { 'Content-Type' => $content_type } );
433 0         0 return $self->_build_returned_objects($response);
434             }
435              
436 4 0   4   16994 method POST ($url, $hash, $content_type) {
  0 0   0   0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
437 0         0 $self->current_session; # ensure/force valid session in place
438 0 0       0 my $content = is_plain_hashref($hash) ? $self->_encode_json_content($hash) : $hash;
439 0         0 my $response = $self->_request( 'POST', $url, $content, { 'Content-Type' => $content_type } );
440 0         0 return $self->_build_returned_objects($response);
441             }
442              
443 4 0   4   10017 method DELETE ($url) {
  0 0   0   0  
  0         0  
  0         0  
  0         0  
444 0         0 $self->current_session; # ensure/force valid session in place
445 0         0 my $response = $self->_request( 'DELETE', $url );
446 0         0 return $self->_build_returned_objects($response);
447             }
448              
449             # ------------------------------------------------------------------------
450              
451              
452             has query_uri => (
453             is => 'ro',
454             isa => Uri,
455             lazy => 1,
456             builder => '_build_query_uri',
457             clearer => '_clear_query_uri',
458             );
459              
460 4 0   4   4197 method _build_query_uri () {
  0     0   0  
  0         0  
461 0         0 my @links = $self->current_session->find_links( rel => 'down', type => 'queryList' );
462 0 0       0 VMware::vCloudDirector2::Error->throw('Cannot find single query URL')
463             unless ( scalar(@links) == 1 );
464 0         0 return $links[0]->href;
465             }
466              
467             # ------------------------------------------------------------------------
468              
469              
470 4 0   4   3330 method _clear_api_data () {
  0     0   0  
  0         0  
471 0         0 $self->_clear_default_accept_header;
472 0         0 $self->_clear_base_url;
473 0         0 $self->_clear_ua;
474 0         0 $self->_clear_api_version;
475 0         0 $self->_clear_url_login;
476 0         0 $self->_clear_raw_version;
477 0         0 $self->_clear_raw_version_full;
478 0         0 $self->_clear_authorization_token;
479 0         0 $self->_clear_current_session;
480 0         0 $self->_clear_query_uri;
481             }
482              
483             # ------------------------------------------------------------------------
484 4 50   4   9484 method _listify ($thing) { !defined $thing ? () : ( ( ref $thing eq 'ARRAY' ) ? @{$thing} : $thing ) }
  1 50   1   2  
  1 50       4  
  1 50       3  
  1         4  
  1         6  
  1         4  
485              
486             # ------------------------------------------------------------------------
487              
488             __PACKAGE__->meta->make_immutable;
489              
490             1;
491              
492             __END__
493              
494             =pod
495              
496             =encoding UTF-8
497              
498             =head1 NAME
499              
500             VMware::vCloudDirector2::API - Module to do stuff!
501              
502             =head1 VERSION
503              
504             version 0.107
505              
506             =head2 Attributes
507              
508             =head3 hostname
509              
510             Hostname of the vCloud server. Must have a vCloud instance listening for https
511             on port 443.
512              
513             =head3 username
514              
515             Username to use to login to vCloud server.
516              
517             =head3 password
518              
519             Password to use to login to vCloud server.
520              
521             =head3 orgname
522              
523             Org name to use to login to vCloud server - this defaults to C<System>.
524              
525             =head3 timeout
526              
527             Command timeout in seconds. Defaults to 120.
528              
529             =head3 default_accept_header
530              
531             The default MIME types to accept. This is automatically set based on the
532             information received back from the API versions.
533              
534             =head3 ssl_verify
535              
536             Whether to do standard SSL certificate verification. Defaults to set.
537              
538             =head3 ssl_ca_file
539              
540             The SSL CA set to trust packaged in a file. This defaults to those set in the
541             L<Mozilla::CA>
542              
543             =head2 debug
544              
545             Set debug level. The higher the debug level, the more chatter is exposed.
546              
547             Defaults to 0 (no output) unless the environment variable C<VCLOUD_API_DEBUG>
548             is set to something that is non-zero. Picked up at create time in C<BUILD()>.
549              
550             =head2 API SHORTHAND METHODS
551              
552             =head3 api_version
553              
554             The C<api_version> holds the version number of the highest discovered non-
555             deprecated API, it is initialised by connecting to the C</api/versions>
556             endpoint, and is called implicitly during the login setup. Once filled the
557             values are cached.
558              
559             =head3 authorization_token
560              
561             The C<authorization_token> holds the vCloud authentication token that has been
562             handed out. It is set by L<login>, and can be tested for by using the
563             predicate C<has_authorization_token>.
564              
565             =head3 current_session
566              
567             The current session object for this login. Attempting to access this forces a
568             login and creation of a current session.
569              
570             =head3 login
571              
572             Returns the L<current_session> which co-incidently forces a login.
573              
574             =head3 logout
575              
576             If there is a current session, DELETEs it, and clears the current session state
577             data.
578              
579             =head3 GET ($url)
580              
581             Forces a session establishment, and does a GET operation on the given URL,
582             returning the objects that were built.
583              
584             =head3 GET_hash ($url)
585              
586             Forces a session establishment, and does a GET operation on the given URL,
587             returning the JSON equivalent hash that was built.
588              
589             =head3 PUT ($url, $hash, $content_type)
590              
591             Forces a session establishment, and does a PUT operation on the given URL,
592             passing the JSON string or encoded hash, returning the objects that were built.
593              
594             =head3 POST ($url, $hash, $content_type)
595              
596             Forces a session establishment, and does a POST operation on the given URL,
597             passing the JSON string or encoded hash, returning the objects that were built.
598              
599             =head3 DELETE ($url)
600              
601             Forces a session establishment, and does a DELETE operation on the given URL,
602             returning the objects that were built.
603              
604             =head3 query_uri
605              
606             Returns the URI for query operations, as taken from the initial session object.
607              
608             =head2 _clear_api_data
609              
610             Clears out all the API state data, including the current login state. This is
611             not intended to be used from outside the module, and will completely trash the
612             current state requiring a new login. The basic information passed at object
613             construction time is not deleted, so a new session could be created.
614              
615             =head1 AUTHOR
616              
617             Nigel Metheringham <nigelm@cpan.org>
618              
619             =head1 COPYRIGHT AND LICENSE
620              
621             This software is copyright (c) 2019 by Nigel Metheringham.
622              
623             This is free software; you can redistribute it and/or modify it under
624             the same terms as the Perl 5 programming language system itself.
625              
626             =cut