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   728 use strict;
  4         9  
  4         116  
6 4     4   30 use warnings;
  4         8  
  4         130  
7 4     4   42 use v5.10; # needed for state variable
  4         13  
8              
9             our $VERSION = '0.106'; # VERSION
10             our $AUTHORITY = 'cpan:NIGELM'; # AUTHORITY
11              
12 4     4   485 use Moose;
  4         389368  
  4         28  
13 4     4   25248 use Method::Signatures;
  4         54164  
  4         33  
14 4     4   2275 use MooseX::Types::Path::Tiny qw(Path);
  4         441811  
  4         37  
15 4     4   11254 use MooseX::Types::URI qw(Uri);
  4         540428  
  4         24  
16 4     4   9656 use Cpanel::JSON::XS;
  4         9543  
  4         241  
17 4     4   1765 use LWP::UserAgent::Determined;
  4         120017  
  4         122  
18 4     4   1764 use MIME::Base64;
  4         2240  
  4         285  
19 4     4   1550 use Mozilla::CA;
  4         989  
  4         155  
20 4     4   27 use Path::Tiny;
  4         6  
  4         175  
21 4     4   1648 use Ref::Util qw(is_plain_hashref is_plain_arrayref);
  4         5710  
  4         263  
22 4     4   27 use Scalar::Util qw(looks_like_number);
  4         19  
  4         222  
23 4     4   1680 use Syntax::Keyword::Try 0.04; # Earlier versions throw errors
  4         2449  
  4         19  
24 4     4   1798 use VMware::vCloudDirector2::Error;
  4         15  
  4         169  
25 4     4   2203 use VMware::vCloudDirector2::Object;
  4         15  
  4         185  
26 4     4   2133 use XML::Fast qw(); # just for the versions document
  4         41400  
  4         116  
27 4     4   1737 use Data::Dump qw(pp);
  4         17989  
  4         811  
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   3338 method _build_ssl_ca_file () { return path( Mozilla::CA::SSL_ca_file() ); }
  0     0   0  
  0         0  
  0         0  
68 4 50   4   3047 method _build_base_url () { return URI->new( sprintf( 'https://%s/', $self->hostname ) ); }
  1     1   2  
  1         3  
  1         38  
69 4 50   4   2560 method _build_default_accept_header () { return ( 'application/*+json;version=' . $self->api_version ); }
  1     1   4  
  1         4  
  1         31  
70 4 0   4   9668 method _debug (@parameters) { warn join( '', '# ', @parameters, "\n" ) if ( $self->debug ); }
  0     0   0  
  0         0  
  0         0  
71              
72             # ------------------------------------------------------------------------
73              
74 4 50   4   8559 method BUILD ($args) {
  1 50   1   2  
  1         4  
  1         2  
  1         4  
75              
76             # deal with setting debug if needed
77 1         3 my $env_debug = $ENV{VCLOUD_API_DEBUG};
78 1 50       31 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   3196 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   2917 method _build_json () { return Cpanel::JSON::XS->new->utf8->allow_blessed->convert_blessed; }
  1     1   2  
  1         3  
  1         50  
117              
118             # ------------------------------------------------------------------------
119 4 50   4   8090 method _decode_xml_response ($response) {
  1 50   1   2  
  1         5  
  1         2  
  1         3  
120 1         7 my $content = $response->decoded_content;
121 1 50       1982 return if ( length($content) == 0 );
122              
123 1 50       5 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         30 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   8626 method _decode_json_response ($response) {
  3 50   3   6  
  3         10  
  3         6  
  3         8  
141 3         11 my $content = $response->decoded_content;
142 3 50       336 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         71 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   9073 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   19653 method _request ($method, $url, $content?, $headers?) {
  4 50   4   11  
  4 50       13  
  4         9  
  4         12  
  4         8  
  4         7  
  4         5  
  4         11  
170 4         112 my $uri = URI->new_abs( $url, $self->_base_url );
171 4 50       983 $self->_debug("API: _request [$method] $uri") if ( $self->debug );
172              
173 4         23 my $request = HTTP::Request->new( $method => $uri );
174              
175             # build headers
176 4 50 33     284 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         49 $request->header( 'Content-Length', 0 );
182             }
183              
184             # add any supplied headers
185 4         255 my $seen_accept;
186 4 100       12 if ( defined($headers) ) {
187 2         4 foreach my $h_name ( keys %{$headers} ) {
  2         16  
188 2         11 $request->header( $h_name, $headers->{$h_name} );
189 2 100       89 $seen_accept = 1 if ( lc($h_name) eq 'accept' );
190             }
191             }
192              
193             # set accept header
194 4 100       103 $request->header( 'Accept', $self->default_accept_header ) unless ($seen_accept);
195              
196             # set auth header
197 4 100       261 $request->header( 'x-vcloud-authorization', $self->authorization_token )
198             if ( $self->has_authorization_token );
199              
200             # do request
201 4         112 my $response;
202             try { $response = $self->_ua->request($request); }
203 4         10 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       4097 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       14 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         42 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   5567 method _build_api_version () { return $self->_raw_version->{Version}; }
  1     1   2  
  1         11  
  1         34  
281 4 50   4   2586 method _build_url_login () { return URI->new( $self->_raw_version->{LoginUrl} ); }
  1     1   3  
  1         4  
  1         40  
282              
283 4 50   4   2639 method _build_raw_version () {
  1     1   3  
  1         4  
284 1         27 my $hash = $self->_raw_version_full;
285 1         2 my $version = 0;
286 1         2 my $version_block;
287 1         2 for my $verblock ( @{ $hash->{SupportedVersions}{VersionInfo} } ) {
  1         5  
288 13 100 50     26 next if ( ( $verblock->{-deprecated} || '' ) eq 'true' );
289 4 50       12 if ( $verblock->{Version} > $version ) {
290 4         5 $version_block = $verblock;
291 4         10 $version = $verblock->{Version};
292             }
293             }
294              
295 1 50       28 $self->_debug("API: version used: $version") if ( $self->debug );
296 1 50       4 die "No valid version block seen" unless ($version_block);
297              
298 1         29 return $version_block;
299             }
300              
301 4 50   4   3390 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         7 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   2854 method _build_current_session () {
  1     1   2  
  1         4  
327 1         27 my $login_id = join( '@', $self->username, $self->orgname );
328 1         26 my $encoded_auth = 'Basic ' . MIME::Base64::encode( join( ':', $login_id, $self->password ) );
329 1 50       26 $self->_debug("API: attempting login as: $login_id") if ( $self->debug );
330 1         27 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         5 my $token = $response->header('x-vcloud-authorization');
335 1         74 $self->_set_authorization_token($token);
336 1 50       27 $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         6 my ($session) = $self->_build_returned_objects($response);
342 1         31 return $session;
343             }
344              
345 4 50   4   3270 method login () { return $self->current_session; }
  1     1   3  
  1         4  
  1         28  
346              
347 4 0   4   2791 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   9982 method _build_returned_objects ($response) {
  2 50   2   5  
  2         7  
  2         3  
  2         5  
359              
360 2 50       7 if ( $response->is_success ) {
361 2 50       65 $self->_debug("API: building objects") if ( $self->debug );
362              
363 2         6 my $hash = $self->_decode_json_response($response);
364 2 50       8 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       5 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       18 my $type = ( $mime_type =~ m|^application/vnd\..*\.(\w+)\+json$| ) ? $1 : $mime_type;
378 2 100       9 my $thing_type = ( substr( $type, -4, 4 ) eq 'List' ) ? substr( $type, 0, -4 ) : $type;
379              
380 2 100 66     20 if ( ( $type ne $thing_type )
      33        
381             and ( exists( $hash->{$thing_type} ) )
382             and is_plain_arrayref( $hash->{$thing_type} ) ) {
383 1         2 my @thing_objects;
384 1 50       40 $self->_debug("API: building a set of [$thing_type] objects") if ( $self->debug );
385 1         7 foreach my $thing ( $self->_listify( $hash->{$thing_type} ) ) {
386 5         137 my $object = VMware::vCloudDirector2::Object->new(
387             hash => $thing,
388             api => $self,
389             _partial_object => 1
390             );
391 5         13 push @thing_objects, $object;
392             }
393 1         40 return @thing_objects;
394             }
395              
396             # was not a list of things, so just objectify the one thing here
397             else {
398 1 50       32 $self->_debug("API: building a single [$thing_type] object") if ( $self->debug );
399 1         35 return VMware::vCloudDirector2::Object->new( hash => $hash, api => $self );
400             }
401             }
402              
403             # there was an error here - so bomb out
404             else {
405 0         0 VMware::vCloudDirector2::Error->throw(
406             { message => 'Error reponse passed to object builder', response => $response } );
407             }
408             }
409              
410             # ------------------------------------------------------------------------
411              
412              
413 4 50   4   10662 method GET ($url) {
  1 50   1   2  
  1         4  
  1         3  
  1         3  
414 1         27 $self->current_session; # ensure/force valid session in place
415 1         3 my $response = $self->_request( 'GET', $url );
416 1         3 return $self->_build_returned_objects($response);
417             }
418              
419 4 50   4   8356 method GET_hash ($url) {
  1 50   1   3  
  1         4  
  1         3  
  1         3  
420 1         42 $self->current_session; # ensure/force valid session in place
421 1         6 my $response = $self->_request( 'GET', $url );
422 1         5 return $self->_decode_json_response($response);
423             }
424              
425 4 0   4   13548 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  
426 0         0 $self->current_session; # ensure/force valid session in place
427 0 0       0 my $content = is_plain_hashref($hash) ? $self->_encode_json_content($hash) : $hash;
428 0         0 my $response = $self->_request( 'PUT', $url, $content, { 'Content-Type' => $content_type } );
429 0         0 return $self->_build_returned_objects($response);
430             }
431              
432 4 0   4   14045 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  
433 0         0 $self->current_session; # ensure/force valid session in place
434 0 0       0 my $content = is_plain_hashref($hash) ? $self->_encode_json_content($hash) : $hash;
435 0         0 my $response = $self->_request( 'POST', $url, $content, { 'Content-Type' => $content_type } );
436 0         0 return $self->_build_returned_objects($response);
437             }
438              
439 4 0   4   8321 method DELETE ($url) {
  0 0   0   0  
  0         0  
  0         0  
  0         0  
440 0         0 $self->current_session; # ensure/force valid session in place
441 0         0 my $response = $self->_request( 'DELETE', $url );
442 0         0 return $self->_build_returned_objects($response);
443             }
444              
445             # ------------------------------------------------------------------------
446              
447              
448             has query_uri => (
449             is => 'ro',
450             isa => Uri,
451             lazy => 1,
452             builder => '_build_query_uri',
453             clearer => '_clear_query_uri',
454             );
455              
456 4 0   4   3057 method _build_query_uri () {
  0     0   0  
  0         0  
457 0         0 my @links = $self->current_session->find_links( rel => 'down', type => 'queryList' );
458 0 0       0 VMware::vCloudDirector2::Error->throw('Cannot find single query URL')
459             unless ( scalar(@links) == 1 );
460 0         0 return $links[0]->href;
461             }
462              
463             # ------------------------------------------------------------------------
464              
465              
466 4 0   4   3725 method _clear_api_data () {
  0     0   0  
  0         0  
467 0         0 $self->_clear_default_accept_header;
468 0         0 $self->_clear_base_url;
469 0         0 $self->_clear_ua;
470 0         0 $self->_clear_api_version;
471 0         0 $self->_clear_url_login;
472 0         0 $self->_clear_raw_version;
473 0         0 $self->_clear_raw_version_full;
474 0         0 $self->_clear_authorization_token;
475 0         0 $self->_clear_current_session;
476 0         0 $self->_clear_query_uri;
477             }
478              
479             # ------------------------------------------------------------------------
480 4 50   4   8006 method _listify ($thing) { !defined $thing ? () : ( ( ref $thing eq 'ARRAY' ) ? @{$thing} : $thing ) }
  1 50   1   2  
  1 50       4  
  1 50       2  
  1         3  
  1         5  
  1         3  
481              
482             # ------------------------------------------------------------------------
483              
484             __PACKAGE__->meta->make_immutable;
485              
486             1;
487              
488             __END__
489              
490             =pod
491              
492             =encoding UTF-8
493              
494             =head1 NAME
495              
496             VMware::vCloudDirector2::API - Module to do stuff!
497              
498             =head1 VERSION
499              
500             version 0.106
501              
502             =head2 Attributes
503              
504             =head3 hostname
505              
506             Hostname of the vCloud server. Must have a vCloud instance listening for https
507             on port 443.
508              
509             =head3 username
510              
511             Username to use to login to vCloud server.
512              
513             =head3 password
514              
515             Password to use to login to vCloud server.
516              
517             =head3 orgname
518              
519             Org name to use to login to vCloud server - this defaults to C<System>.
520              
521             =head3 timeout
522              
523             Command timeout in seconds. Defaults to 120.
524              
525             =head3 default_accept_header
526              
527             The default MIME types to accept. This is automatically set based on the
528             information received back from the API versions.
529              
530             =head3 ssl_verify
531              
532             Whether to do standard SSL certificate verification. Defaults to set.
533              
534             =head3 ssl_ca_file
535              
536             The SSL CA set to trust packaged in a file. This defaults to those set in the
537             L<Mozilla::CA>
538              
539             =head2 debug
540              
541             Set debug level. The higher the debug level, the more chatter is exposed.
542              
543             Defaults to 0 (no output) unless the environment variable C<VCLOUD_API_DEBUG>
544             is set to something that is non-zero. Picked up at create time in C<BUILD()>.
545              
546             =head2 API SHORTHAND METHODS
547              
548             =head3 api_version
549              
550             The C<api_version> holds the version number of the highest discovered non-
551             deprecated API, it is initialised by connecting to the C</api/versions>
552             endpoint, and is called implicitly during the login setup. Once filled the
553             values are cached.
554              
555             =head3 authorization_token
556              
557             The C<authorization_token> holds the vCloud authentication token that has been
558             handed out. It is set by L<login>, and can be tested for by using the
559             predicate C<has_authorization_token>.
560              
561             =head3 current_session
562              
563             The current session object for this login. Attempting to access this forces a
564             login and creation of a current session.
565              
566             =head3 login
567              
568             Returns the L<current_session> which co-incidently forces a login.
569              
570             =head3 logout
571              
572             If there is a current session, DELETEs it, and clears the current session state
573             data.
574              
575             =head3 GET ($url)
576              
577             Forces a session establishment, and does a GET operation on the given URL,
578             returning the objects that were built.
579              
580             =head3 GET_hash ($url)
581              
582             Forces a session establishment, and does a GET operation on the given URL,
583             returning the JSON equivalent hash that was built.
584              
585             =head3 PUT ($url, $hash, $content_type)
586              
587             Forces a session establishment, and does a PUT operation on the given URL,
588             passing the JSON string or encoded hash, returning the objects that were built.
589              
590             =head3 POST ($url, $hash, $content_type)
591              
592             Forces a session establishment, and does a POST operation on the given URL,
593             passing the JSON string or encoded hash, returning the objects that were built.
594              
595             =head3 DELETE ($url)
596              
597             Forces a session establishment, and does a DELETE operation on the given URL,
598             returning the objects that were built.
599              
600             =head3 query_uri
601              
602             Returns the URI for query operations, as taken from the initial session object.
603              
604             =head2 _clear_api_data
605              
606             Clears out all the API state data, including the current login state. This is
607             not intended to be used from outside the module, and will completely trash the
608             current state requiring a new login. The basic information passed at object
609             construction time is not deleted, so a new session could be created.
610              
611             =head1 AUTHOR
612              
613             Nigel Metheringham <nigelm@cpan.org>
614              
615             =head1 COPYRIGHT AND LICENSE
616              
617             This software is copyright (c) 2019 by Nigel Metheringham.
618              
619             This is free software; you can redistribute it and/or modify it under
620             the same terms as the Perl 5 programming language system itself.
621              
622             =cut