File Coverage

blib/lib/Net/Silverpeak/Orchestrator.pm
Criterion Covered Total %
statement 23 283 8.1
branch 0 78 0.0
condition 0 18 0.0
subroutine 8 43 18.6
pod 31 32 96.8
total 62 454 13.6


line stmt bran cond sub pod time code
1             package Net::Silverpeak::Orchestrator;
2             $Net::Silverpeak::Orchestrator::VERSION = '0.006000';
3             # ABSTRACT: Silverpeak Orchestrator REST API client library
4              
5 2     2   495197 use 5.024;
  2         21  
6 2     2   1590 use Moo;
  2         15698  
  2         9  
7 2     2   3019 use feature 'signatures';
  2         5  
  2         327  
8 2     2   1393 use Types::Standard qw( Bool Str );
  2         240925  
  2         32  
9 2     2   7638 use Carp qw( croak );
  2         8  
  2         189  
10 2     2   1752 use HTTP::CookieJar;
  2         66197  
  2         130  
11 2     2   22 use List::Util qw( any );
  2         5  
  2         175  
12             # use Data::Dumper::Concise;
13              
14 2     2   14 no warnings "experimental::signatures";
  2         6  
  2         5555  
15              
16              
17             has 'user' => (
18             isa => Str,
19             is => 'rw',
20             predicate => 1,
21             );
22             has 'passwd' => (
23             isa => Str,
24             is => 'rw',
25             predicate => 1,
26             );
27             has 'api_key' => (
28             isa => Str,
29             is => 'rw',
30             predicate => 1,
31             );
32              
33              
34             has 'is_logged_in' => (
35             isa => Bool,
36             is => 'rwp',
37             default => sub { 0 },
38             );
39              
40             with 'Role::REST::Client';
41              
42             has '+persistent_headers' => (
43             default => sub {
44             my $self = shift;
45             my %headers;
46             $headers{'X-Auth-Token'} = $self->api_key
47             if $self->has_api_key;
48             return \%headers;
49             },
50             );
51              
52             around 'do_request' => sub($orig, $self, $method, $uri, $opts) {
53             # $uri .= '?apiKey=' . $self->api_key
54             # if $self->has_api_key;
55             # warn 'request: ' . Dumper([$method, $uri, $opts]);
56             my $response = $orig->($self, $method, $uri, $opts);
57             # warn 'response: ' . Dumper($response);
58             return $response;
59             };
60              
61 0     0     sub _build_user_agent ($self) {
  0            
  0            
62 0           require HTTP::Thin;
63              
64 0           my %params = $self->clientattrs->%*;
65 0 0 0       if ($self->has_user && $self->has_passwd) {
66 0           $params{cookie_jar} = HTTP::CookieJar->new;
67             }
68              
69 0           return HTTP::Thin->new(%params);
70             }
71              
72 0     0     sub _error_handler ($self, $res) {
  0            
  0            
  0            
73             my $error_message = ref $res->data eq 'HASH' && exists $res->data->{error}
74             ? $res->data->{error}
75 0 0 0       : $res->response->decoded_content;
76              
77 0           croak('error (' . $res->code . '): ' . $error_message);
78             }
79              
80              
81 0     0 1   sub login($self) {
  0            
  0            
82 0 0 0       die "user and password required\n"
83             unless $self->has_user && $self->has_passwd;
84              
85 0           my $res = $self->post('/gms/rest/authentication/login', {
86             user => $self->user,
87             password => $self->passwd,
88             });
89              
90 0 0         $self->_error_handler($res)
91             unless $res->code == 200;
92              
93 0           my @cookies = $self->user_agent->cookie_jar->cookies_for($self->server);
94 0 0         if (my ($csrf_cookie) = grep { $_->{name} eq 'orchCsrfToken' } @cookies ) {
  0            
95 0           $self->set_persistent_header('X-XSRF-TOKEN' => $csrf_cookie->{value});
96             }
97              
98 0           $self->_set_is_logged_in(1);
99              
100 0           return 1;
101             }
102              
103              
104 0     0 1   sub logout($self) {
  0            
  0            
105 0 0 0       die "user and password required\n"
106             unless $self->has_user && $self->has_passwd;
107              
108 0           my $res = $self->get('/gms/rest/authentication/logout');
109 0 0         $self->_error_handler($res)
110             unless $res->code == 200;
111              
112 0           delete $self->persistent_headers->{'X-XSRF-TOKEN'};
113              
114 0           $self->_set_is_logged_in(0);
115              
116 0           return 1;
117             }
118              
119              
120 0     0 1   sub get_version($self) {
  0            
  0            
121 0           my $res = $self->get('/gms/rest/gms/versions');
122 0 0         $self->_error_handler($res)
123             unless $res->code == 200;
124              
125 0           return $res->data->{current};
126             }
127              
128              
129 0     0 1   sub list_templategroups($self) {
  0            
  0            
130 0           my $res = $self->get('/gms/rest/template/templateGroups');
131 0 0         $self->_error_handler($res)
132             unless $res->code == 200;
133 0           return $res->data;
134             }
135              
136              
137 0     0 1   sub get_templategroup($self, $name) {
  0            
  0            
  0            
138 0           my $res = $self->get('/gms/rest/template/templateGroups/' . $name);
139 0 0         $self->_error_handler($res)
140             unless $res->code == 200;
141 0           return $res->data;
142             }
143              
144              
145 0     0 1   sub create_templategroup($self, $name, $data = {}) {
  0            
  0            
  0            
  0            
146 0           $data->{name} = $name;
147 0           my $res = $self->post('/gms/rest/template/templateCreate',
148             $data);
149 0 0         $self->_error_handler($res)
150             unless $res->code == 204;
151 0           return 1;
152             }
153              
154              
155 0     0 1   sub update_templates_of_templategroup($self, $name, $templatenames) {
  0            
  0            
  0            
  0            
156 0 0         croak('templates names must be passed as an arrayref')
157             unless ref $templatenames eq 'ARRAY';
158              
159 0           my $res = $self->post('/gms/rest/template/templateSelection/' . $name,
160             $templatenames);
161 0 0         $self->_error_handler($res)
162             unless $res->code == 200;
163 0           return $res->data;
164             }
165              
166              
167 0     0 1   sub update_templategroup($self, $name, $data) {
  0            
  0            
  0            
  0            
168 0           my $res = $self->post('/gms/rest/template/templateGroups/' . $name,
169             $data);
170 0 0         $self->_error_handler($res)
171             unless $res->code == 200;
172 0           return $res->data;
173             }
174              
175              
176 0     0 1   sub delete_templategroup($self, $name) {
  0            
  0            
  0            
177 0           my $res = $self->delete('/gms/rest/template/templateGroups/' . $name);
178 0 0         $self->_error_handler($res)
179             unless $res->code == 204;
180 0           return 1;
181             }
182              
183              
184 0     0 1   sub list_appliances($self) {
  0            
  0            
185 0           my $res = $self->get('/gms/rest/appliance');
186 0 0         $self->_error_handler($res)
187             unless $res->code == 200;
188 0           return $res->data;
189             }
190              
191              
192 0     0 1   sub get_appliance($self, $id) {
  0            
  0            
  0            
193 0           my $res = $self->get('/gms/rest/appliance/' . $id);
194 0 0         $self->_error_handler($res)
195             unless $res->code == 200;
196 0           return $res->data;
197             }
198              
199              
200 0     0 1   sub list_template_applianceassociations($self) {
  0            
  0            
201 0           my $res = $self->get('/gms/rest/template/applianceAssociation');
202 0 0         $self->_error_handler($res)
203             unless $res->code == 200;
204 0           return $res->data;
205             }
206              
207              
208 0     0 1   sub list_applianceids_by_templategroupname($self, $name) {
  0            
  0            
  0            
209 0           my $associations = $self->list_template_applianceassociations;
210 0           my @appliance_ids;
211 0           for my $appliance_id (keys %$associations) {
212             push @appliance_ids, $appliance_id
213 0 0   0     if any { $_ eq $name } $associations->{$appliance_id}->@*;
  0            
214             }
215 0           return \@appliance_ids;
216             }
217              
218              
219 0     0 1   sub list_addressgroups($self) {
  0            
  0            
220 0           my $res = $self->get('/gms/rest/ipObjects/addressGroup');
221 0 0         $self->_error_handler($res)
222             unless $res->code == 200;
223 0           return $res->data;
224             }
225              
226              
227 0     0 1   sub list_addressgroup_names($self) {
  0            
  0            
228 0           my $res = $self->get('/gms/rest/ipObjects/addressGroupNames');
229 0 0         $self->_error_handler($res)
230             unless $res->code == 200;
231 0           return $res->data;
232             }
233              
234              
235 0     0 1   sub get_addressgroup($self, $name) {
  0            
  0            
  0            
236 0           my $res = $self->get('/gms/rest/ipObjects/addressGroup/' . $name);
237 0 0         $self->_error_handler($res)
238             unless $res->code == 200;
239 0           return $res->data;
240             }
241              
242              
243 0     0 1   sub create_or_update_addressgroup($self, $name, $data) {
  0            
  0            
  0            
  0            
244 0           $data->{name} = $name;
245 0           $data->{type} = 'AG';
246 0           my $res = $self->post('/gms/rest/ipObjects/addressGroup', $data);
247 0 0         $self->_error_handler($res)
248             unless $res->code == 204;
249 0           return 1;
250             }
251              
252              
253 0     0 1   sub update_addressgroup($self, $name, $data) {
  0            
  0            
  0            
  0            
254 0           $data->{name} = $name;
255 0           $data->{type} = 'AG';
256 0           my $res = $self->put('/gms/rest/ipObjects/addressGroup', $data);
257 0 0         $self->_error_handler($res)
258             unless $res->code == 204;
259 0           return 1;
260             }
261              
262              
263 0     0 1   sub delete_addressgroup($self, $name) {
  0            
  0            
  0            
264 0           my $res = $self->delete('/gms/rest/ipObjects/addressGroup/' . $name);
265 0 0         $self->_error_handler($res)
266             unless $res->code == 204;
267 0           return 1;
268             }
269              
270              
271 0     0 1   sub list_servicegroups($self) {
  0            
  0            
272 0           my $res = $self->get('/gms/rest/ipObjects/serviceGroup');
273 0 0         $self->_error_handler($res)
274             unless $res->code == 200;
275 0           return $res->data;
276             }
277              
278              
279 0     0 1   sub list_servicegroup_names($self) {
  0            
  0            
280 0           my $res = $self->get('/gms/rest/ipObjects/serviceGroupNames');
281 0 0         $self->_error_handler($res)
282             unless $res->code == 200;
283 0           return $res->data;
284             }
285              
286              
287 0     0 1   sub get_servicegroup($self, $name) {
  0            
  0            
  0            
288 0           my $res = $self->get('/gms/rest/ipObjects/serviceGroup/' . $name);
289 0 0         $self->_error_handler($res)
290             unless $res->code == 200;
291 0           return $res->data;
292             }
293              
294              
295 0     0 1   sub create_or_update_servicegroup($self, $name, $data) {
  0            
  0            
  0            
  0            
296 0           $data->{name} = $name;
297 0           $data->{type} = 'SG';
298 0           my $res = $self->post('/gms/rest/ipObjects/serviceGroup', $data);
299 0 0         $self->_error_handler($res)
300             unless $res->code == 204;
301 0           return 1;
302             }
303              
304              
305 0     0 1   sub update_servicegroup($self, $name, $data) {
  0            
  0            
  0            
  0            
306 0           $data->{name} = $name;
307 0           $data->{type} = 'SG';
308 0           my $res = $self->put('/gms/rest/ipObjects/serviceGroup', $data);
309 0 0         $self->_error_handler($res)
310             unless $res->code == 204;
311 0           return 1;
312             }
313              
314              
315 0     0 1   sub delete_servicegroup($self, $name) {
  0            
  0            
  0            
316 0           my $res = $self->delete('/gms/rest/ipObjects/serviceGroup/' . $name);
317 0 0         $self->_error_handler($res)
318             unless $res->code == 204;
319 0           return 1;
320             }
321              
322              
323 0     0 1   sub list_domain_applications($self, $resource_key='userDefined') {
  0            
  0            
  0            
324 0           my $res = $self->get('/gms/rest/applicationDefinition/dnsClassification',
325             { resourceKey => $resource_key });
326 0 0         $self->_error_handler($res)
327             unless $res->code == 200;
328 0           return $res->data;
329             }
330              
331              
332 0     0 1   sub create_or_update_domain_application($self, $domain, $data) {
  0            
  0            
  0            
  0            
333 0           $data->{domain} = $domain;
334 0           my $res = $self->post('/gms/rest/applicationDefinition/dnsClassification2/domain', $data);
335 0 0         $self->_error_handler($res)
336             unless $res->code == 200;
337 0           return 1;
338             }
339              
340              
341 0     0 1   sub delete_domain_application($self, $domain) {
  0            
  0            
  0            
342 0           my $res = $self->delete('/gms/rest/applicationDefinition/dnsClassification/' . $domain);
343 0 0         $self->_error_handler($res)
344             unless $res->code == 200;
345 0           return 1;
346             }
347              
348              
349 0     0 1   sub list_application_groups($self, $resource_key='userDefined') {
  0            
  0            
  0            
350 0           my $res = $self->get('/gms/rest/applicationDefinition/applicationTags',
351             { resourceKey => $resource_key });
352 0 0         $self->_error_handler($res)
353             unless $res->code == 200;
354 0           return $res->data;
355             }
356              
357              
358 0     0 1   sub create_or_update_application_group($self, $name, $data) {
  0            
  0            
  0            
  0            
359 0           my $application_groups = $self->list_application_groups;
360             # set or overwrite existing application group
361 0           $application_groups->{$name} = $data;
362 0           my $res = $self->post('/gms/rest/applicationDefinition/applicationTags',
363             $application_groups);
364 0 0         $self->_error_handler($res)
365             unless $res->code == 200;
366 0           return 1;
367             }
368              
369              
370 0     0 1   sub delete_application_group($self, $name) {
  0            
  0            
  0            
371 0           my $application_groups = $self->list_application_groups;
372             # set or overwrite existing application group
373             croak("application '$name' doesn't exist")
374 0 0         unless exists $application_groups->{$name};
375 0           delete $application_groups->{$name};
376 0           my $res = $self->post('/gms/rest/applicationDefinition/applicationTags',
377             $application_groups);
378 0 0         $self->_error_handler($res)
379             unless $res->code == 200;
380 0           return 1;
381             }
382              
383              
384             sub DEMOLISH {
385 0     0 0   my $self = shift;
386              
387 0 0 0       $self->logout
      0        
388             if $self->has_user
389             && $self->has_passwd
390             && $self->is_logged_in;
391             }
392              
393             1;
394              
395             __END__
396              
397             =pod
398              
399             =encoding UTF-8
400              
401             =head1 NAME
402              
403             Net::Silverpeak::Orchestrator - Silverpeak Orchestrator REST API client library
404              
405             =head1 VERSION
406              
407             version 0.006000
408              
409             =head1 SYNOPSIS
410              
411             use strict;
412             use warnings;
413             use Net::Silverpeak::Orchestrator;
414              
415             my $orchestrator = Net::Silverpeak::Orchestrator->new(
416             server => 'https://orchestrator.example.com',
417             user => 'username',
418             passwd => '$password',
419             clientattrs => { timeout => 30 },
420             );
421              
422             $orchestrator->login;
423              
424             # OR
425              
426             $orchestrator = Net::Silverpeak::Orchestrator->new(
427             server => 'https://orchestrator.example.com',
428             api_key => '$api-key',
429             clientattrs => { timeout => 30 },
430             );
431              
432             =head1 DESCRIPTION
433              
434             This module is a client library for the Silverpeak Orchestrator REST API.
435             Currently it is developed and tested against version 9.0.2.
436              
437             =head1 ATTRIBUTES
438              
439             =head2 is_logged_in
440              
441             Returns true if successfully logged in.
442              
443             =head1 METHODS
444              
445             =head2 login
446              
447             Logs into the Silverpeak Orchestrator.
448             Only required when using username and password, not for api key.
449              
450             =head2 logout
451              
452             Logs out of the Silverpeak Orchestrator.
453             Only possible when using username and password, not for api key.
454              
455             =head2 get_version
456              
457             Returns the Silverpeak Orchestrator version.
458              
459             =head2 list_templategroups
460              
461             Returns an arrayref of template groups.
462              
463             =head2 get_templategroup
464              
465             Returns a template group by name.
466              
467             =head2 create_templategroup
468              
469             Takes a template group name and a hashref with its config.
470              
471             Returns true on success.
472              
473             Throws an exception on error.
474              
475             =head2 update_templates_of_templategroup
476              
477             Takes a template group name and an arrayref of template names.
478              
479             Returns true on success.
480              
481             Throws an exception on error.
482              
483             =head2 update_templategroup
484              
485             Takes a template group name and a hashref of template configs.
486              
487             Returns true on success.
488              
489             Throws an exception on error.
490              
491             =head2 delete_templategroup
492              
493             Takes a template group name.
494              
495             Returns true on success.
496              
497             Throws an exception on error.
498              
499             =head2 list_appliances
500              
501             Returns an arrayref of appliances.
502              
503             =head2 get_appliance
504              
505             Returns an appliance by id.
506              
507             =head2 list_template_applianceassociations
508              
509             Returns a hashref of template to appliances associations.
510              
511             =head2 list_applianceids_by_templategroupname
512              
513             Returns an arrayref of appliance IDs a templategroup is assigned to.
514              
515             =head2 list_addressgroups
516              
517             Returns an arrayref of address groups.
518              
519             =head2 list_addressgroup_names
520              
521             Returns an arrayref of address group names.
522              
523             =head2 get_addressgroup
524              
525             Returns an address group by name.
526              
527             =head2 create_or_update_addressgroup
528              
529             Takes an address group name and a hashref of address group config.
530              
531             Returns true on success.
532              
533             Throws an exception on error.
534              
535             =head2 update_addressgroup
536              
537             Takes an address group name and a hashref of address group config.
538              
539             Returns true on success.
540              
541             Throws an exception on error.
542              
543             =head2 delete_addressgroup
544              
545             Takes an address group name.
546              
547             Returns true on success.
548              
549             Throws an exception on error.
550              
551             =head2 list_servicegroups
552              
553             Returns an arrayref of service groups.
554              
555             =head2 list_servicegroup_names
556              
557             Returns an arrayref of service group names.
558              
559             =head2 get_servicegroup
560              
561             Returns a service group by name.
562              
563             =head2 create_or_update_servicegroup
564              
565             Takes a service group name and a hashref of service group config.
566              
567             Returns true on success.
568              
569             Throws an exception on error.
570              
571             =head2 update_servicegroup
572              
573             Takes a service group name and a hashref of service group config.
574              
575             Returns true on success.
576              
577             Throws an exception on error.
578              
579             =head2 delete_servicegroup
580              
581             Takes a service group name.
582              
583             Returns true on success.
584              
585             Throws an exception on error.
586              
587             =head2 list_domain_applications
588              
589             Returns an arrayref of domain name applications for a resource key which
590             defaults to 'userDefined'.
591              
592             =head2 create_or_update_domain_application
593              
594             Takes a domain name application domain, not name, and a hashref of its config.
595              
596             Returns true on success.
597              
598             Throws an exception on error.
599              
600             =head2 delete_domain_application
601              
602             Takes a domain name, not application name.
603              
604             Returns true on success.
605              
606             Throws an exception on error.
607              
608             =head2 list_application_groups
609              
610             Returns a hashref of application groups indexed by their name for a resource
611             key which defaults to 'userDefined'.
612              
613             =head2 create_or_update_application_group
614              
615             Takes a application group name, and a hashref of its config.
616              
617             Returns true on success.
618              
619             Throws an exception on error.
620              
621             Because there is no API endpoint for creating or editing a single application
622             group, this method has to load all application groups using
623             L<list_application_groups>, modify and then save them.
624              
625             =head2 delete_application_group
626              
627             Takes an application group name.
628              
629             Returns true on success.
630              
631             Throws an exception on error.
632              
633             Because there is no API endpoint for deleting a single application group,
634             this method has to load all application groups using
635             L<list_application_groups>, remove the requested application group and then
636             save them.
637              
638             =head1 KNOWN SILVERPEAK ORCHESTRATOR BUGS
639              
640             =over
641              
642             =item http 500 response on api key authentication
643              
644             Orchestrator versions before version 9.0.4 respond with a http 500 error on
645             every request using an api key that has no expriation date set.
646             The only workaround is to set an expiration date for it.
647              
648             =back
649              
650             =for Pod::Coverage has_user has_passwd has_api_key
651              
652             =for Pod::Coverage DEMOLISH
653              
654             =head1 AUTHOR
655              
656             Alexander Hartmaier <abraxxa@cpan.org>
657              
658             =head1 COPYRIGHT AND LICENSE
659              
660             This software is copyright (c) 2023 by Alexander Hartmaier.
661              
662             This is free software; you can redistribute it and/or modify it under
663             the same terms as the Perl 5 programming language system itself.
664              
665             =cut