File Coverage

blib/lib/Amazon/API.pm
Criterion Covered Total %
statement 191 601 31.7
branch 31 216 14.3
condition 26 166 15.6
subroutine 45 97 46.3
pod 7 21 33.3
total 300 1101 27.2


line stmt bran cond sub pod time code
1             package Amazon::API;
2              
3             # Generic interface to Amazon APIs
4              
5 3     3   106661 use strict;
  3         17  
  3         86  
6 3     3   14 use warnings;
  3         5  
  3         73  
7              
8 3     3   59 use 5.010;
  3         10  
9              
10             our %LOG4PERL_LOG_LEVELS;
11              
12             BEGIN {
13 3     3   10 %LOG4PERL_LOG_LEVELS = eval {
14 3         2236 require Log::Log4perl::Level;
15              
16 3         5663 Log::Log4perl::Level->import();
17              
18 3     3   19 use vars qw($ERROR $WARN $INFO $DEBUG $TRACE $FATAL);
  3         6  
  3         345  
19              
20             return (
21 3         410 error => $ERROR,
22             warn => $WARN,
23             info => $INFO,
24             debug => $DEBUG,
25             trace => $TRACE,
26             fatal => $FATAL,
27             );
28             };
29             }
30              
31 3     3   1393 use parent qw( Exporter Class::Accessor::Fast);
  3         854  
  3         19  
32              
33 3     3   12597 use Amazon::API::Error;
  3         8  
  3         26  
34 3     3   1519 use Amazon::API::Signature4;
  3         25  
  3         174  
35 3     3   1258 use Amazon::API::Constants qw( :all );
  3         8  
  3         636  
36 3     3   2722 use Amazon::Credentials;
  3         343788  
  3         178  
37 3     3   1760 use Amazon::API::Botocore qw(paginator);
  3         11  
  3         249  
38 3     3   1614 use Amazon::API::Botocore::Shape::Serializer;
  3         8  
  3         27  
39 3         169 use Amazon::API::Botocore::Shape::Utils qw(
40             require_class
41             require_shape
42             create_module_name
43             get_service_from_class
44 3     3   146 );
  3         7  
45              
46 3     3   30 use Carp;
  3         60  
  3         132  
47 3     3   18 use Carp::Always;
  3         6  
  3         20  
48 3     3   85 use Data::Dumper;
  3         6  
  3         98  
49 3     3   14 use Date::Format;
  3         15  
  3         170  
50 3     3   17 use English qw( -no_match_vars);
  3         88  
  3         22  
51 3     3   1065 use HTTP::Request;
  3         7  
  3         40  
52 3     3   130 use JSON qw( encode_json decode_json );
  3         5  
  3         15  
53 3     3   304 use LWP::UserAgent;
  3         15  
  3         36  
54 3     3   89 use List::Util qw(any all pairs none);
  3         6  
  3         183  
55 3     3   18 use ReadonlyX;
  3         4  
  3         122  
56 3     3   26 use Scalar::Util qw( reftype blessed );
  3         18  
  3         116  
57 3     3   15 use Time::Local;
  3         6  
  3         173  
58 3     3   25 use Time::HiRes qw( gettimeofday tv_interval );
  3         4  
  3         24  
59 3     3   318 use URL::Encode qw(url_encode);
  3         7  
  3         160  
60 3     3   19 use XML::Simple;
  3         6  
  3         33  
61              
62             Readonly::Scalar my $DEFAULT_REGION => 'us-east-1';
63             Readonly::Scalar my $REGIONAL_URL_FMT => '%s://%s.%s.amazonaws.com';
64             Readonly::Scalar my $GLOBAL_URL_FMT => '%s://%s.amazonaws.com';
65              
66             Readonly::Scalar my $DEFAULT_LAYOUT_PATTERN => '[%d] %p (%R/%r) %M:%L - %m%n';
67              
68             local $Data::Dumper::Pair = $COLON;
69             local $Data::Dumper::Useqq = $TRUE;
70             local $Data::Dumper::Terse = $TRUE;
71              
72             __PACKAGE__->follow_best_practice;
73              
74             __PACKAGE__->mk_accessors(
75             qw(
76             action
77             api
78             api_methods
79             botocore_metadata
80             botocore_operations
81             botocore_shapes
82             content_type
83             credentials
84             debug
85             decode_always
86             endpoint_prefix
87             error
88             force_array
89             http_method
90             last_action
91             _log_level
92             logger
93             log_file
94             log_layout
95             paginators
96             print_error
97             protocol
98             raise_error
99             raise_serialization_errors
100             region
101             request_uri
102             response
103             service
104             serializer
105             service_url_base
106             target
107             target_prefix
108             url
109             use_botocore
110             user_agent
111             use_paginator
112             version
113             )
114             );
115              
116             our $VERSION = '2.0.11'; ## no critic (RequireInterpolationOfMetachars)
117              
118             our @EXPORT_OK = qw(
119             create_urlencoded_content
120             get_api_service
121             param_n
122             paginator
123             );
124              
125             our $START_TIME = [gettimeofday];
126             our $LAST_LOG_TIME = [gettimeofday];
127              
128             =begin 'ignore'
129              
130             The service categories were extracted from Botocore project:
131              
132             grep -ri '"protocol":"' botocore/botocore/data/* | grep 'xml' | \
133             cut -f 8 -d '/' | sort -u > xml.services
134              
135             grep -ri '"protocol":"' botocore/botocore/data/* | grep 'json' | \
136             cut -f 8 -d '/' | sort -u > json.services
137              
138             grep -ri '"protocol":"' botocore/botocore/data/* | grep 'query' | \
139             cut -f 8 -d '/' | sort -u > query.services
140              
141             These service categories are used to deduce content type for
142             parameters sent to methods for these APIs when Botocore metadata was
143             not used to create an API class. Content-Type can however be
144             overridden when invoking APIs if we guess wrong.
145              
146             rest-json => application/x-amz-1.1
147             rest-xml => application/xml
148             query => application/x-www-form-urlencoded
149              
150             =end 'ignore'
151              
152             =cut
153              
154             our %API_TYPES = (
155             query => [
156             qw(
157             autoscaling
158             cloudformation
159             cloudsearch
160             cloudwatch
161             docdb
162             ec2
163             elasticache
164             elasticbeanstalk
165             elb
166             elbv2
167             forecastquery
168             iam
169             importexport
170             neptune
171             rds
172             redshift
173             sdb
174             ses
175             sns
176             sqs
177             sts
178             timestream-query
179             )
180             ],
181             xml => [
182             qw(
183             cloudfront
184             route53
185             s3
186             s3control
187             )
188             ],
189              
190             json => [
191             qw(
192             accessanalyzer
193             account
194             acm
195             acm-pca
196             alexaforbusiness
197             amp
198             amplify
199             amplifyuibuilder
200             apigateway
201             appconfig
202             appconfigdata
203             appflow
204             appintegrations
205             application-autoscaling
206             applicationcostprofiler
207             application-insights
208             appmesh
209             apprunner
210             appstream
211             appsync
212             athena
213             auditmanager
214             autoscaling-plans
215             backup
216             backup-gateway
217             batch
218             billingconductor
219             braket
220             budgets
221             ce
222             chime
223             chime-sdk-identity
224             chime-sdk-media-pipelines
225             chime-sdk-meetings
226             chime-sdk-messaging
227             cloud9
228             cloudcontrol
229             clouddirectory
230             cloudhsm
231             cloudhsmv2
232             cloudsearchdomain
233             cloudtrail
234             codeartifact
235             codebuild
236             codecommit
237             codedeploy
238             codeguruprofiler
239             codeguru-reviewer
240             codepipeline
241             codestar
242             codestar-connections
243             codestar-notifications
244             cognito-identity
245             cognito-idp
246             cognito-sync
247             comprehend
248             comprehendmedical
249             compute-optimizer
250             config
251             connect
252             connectcampaigns
253             connect-contact-lens
254             connectparticipant
255             cur
256             customer-profiles
257             databrew
258             datapipeline
259             datasync
260             dax
261             detective
262             devicefarm
263             devops-guru
264             directconnect
265             discovery
266             dlm
267             dms
268             drs
269             ds
270             dynamodb
271             dynamodbstreams
272             ebs
273             ec2-instance-connect
274             ecr
275             ecr-public
276             ecs
277             efs
278             eks
279             elastic-inference
280             elastictranscoder
281             emr
282             emr-containers
283             emr-serverless
284             es
285             events
286             evidently
287             finspace
288             finspace-data
289             firehose
290             fis
291             fms
292             forecast
293             forecastquery
294             frauddetector
295             fsx
296             gamelift
297             gamesparks
298             glacier
299             globalaccelerator
300             glue
301             grafana
302             greengrassv2
303             groundstation
304             guardduty
305             health
306             healthlake
307             honeycode
308             identitystore
309             imagebuilder
310             inspector
311             inspector2
312             iot
313             iot1click-projects
314             iotanalytics
315             iot-data
316             iotdeviceadvisor
317             iotevents
318             iotevents-data
319             iotfleethub
320             iot-jobs-data
321             iotsecuretunneling
322             iotsitewise
323             iotthingsgraph
324             iottwinmaker
325             iotwireless
326             ivs
327             ivschat
328             kafkaconnect
329             kendra
330             keyspaces
331             kinesis
332             kinesisanalytics
333             kinesisanalyticsv2
334             kinesisvideo
335             kinesis-video-archived-media
336             kinesis-video-media
337             kinesis-video-signaling
338             kms
339             lakeformation
340             lambda
341             lex-models
342             lex-runtime
343             lexv2-models
344             lexv2-runtime
345             license-manager
346             license-manager-user-subscriptions
347             lightsail
348             location
349             logs
350             lookoutequipment
351             lookoutmetrics
352             lookoutvision
353             m2
354             machinelearning
355             macie
356             managedblockchain
357             marketplace-catalog
358             marketplacecommerceanalytics
359             marketplace-entitlement
360             mediastore
361             mediastore-data
362             memorydb
363             meteringmarketplace
364             mgh
365             mgn
366             migrationhub-config
367             migration-hub-refactor-spaces
368             migrationhubstrategy
369             mobile
370             mturk
371             mwaa
372             network-firewall
373             networkmanager
374             nimble
375             opensearch
376             opsworks
377             opsworkscm
378             organizations
379             outposts
380             panorama
381             personalize
382             personalize-events
383             personalize-runtime
384             pi
385             pinpoint-email
386             pinpoint-sms-voice-v2
387             polly
388             pricing
389             proton
390             qldb
391             qldb-session
392             quicksight
393             ram
394             rbin
395             rds-data
396             redshift-data
397             redshift-serverless
398             rekognition
399             resiliencehub
400             resource-groups
401             resourcegroupstaggingapi
402             robomaker
403             rolesanywhere
404             route53domains
405             route53-recovery-cluster
406             route53resolver
407             rum
408             s3outposts
409             sagemaker
410             sagemaker-a2i-runtime
411             sagemaker-edge
412             sagemaker-featurestore-runtime
413             sagemaker-runtime
414             savingsplans
415             secretsmanager
416             securityhub
417             servicecatalog
418             servicecatalog-appregistry
419             servicediscovery
420             service-quotas
421             sesv2
422             shield
423             signer
424             sms
425             snowball
426             snow-device-management
427             ssm
428             ssm-contacts
429             ssm-incidents
430             sso
431             sso-admin
432             sso-oidc
433             stepfunctions
434             storagegateway
435             support
436             swf
437             synthetics
438             textract
439             timestream-query
440             timestream-write
441             transcribe
442             transfer
443             translate
444             voice-id
445             waf
446             waf-regional
447             wafv2
448             wellarchitected
449             wisdom
450             workdocs
451             worklink
452             workmail
453             workmailmessageflow
454             workspaces
455             workspaces-web
456             xray
457             )
458             ],
459             );
460              
461             our @GLOBAL_SERVICES = qw(
462             cloudfront
463             iam
464             importexport
465             route53
466             s3
467             savingsplans
468             sts
469             );
470              
471             our @REQUIRED_KEYS = qw( aws_access_key_id aws_secret_access_key );
472              
473             ########################################################################
474             sub new {
475             ########################################################################
476 1     1 1 128 my ( $class, @options ) = @_;
477              
478 1   33     9 $class = ref $class || $class;
479              
480 1 50       5 my %options = ref $options[0] ? %{ $options[0] } : @options;
  1         6  
481              
482 1         15 my $self = $class->SUPER::new( \%options );
483              
484 1 50       36 if ( $self->get_service_url_base ) {
485 1         35 $self->set_service( $self->get_service_url_base );
486             }
487              
488 1 50       51 croak 'service is required'
489             if !$self->get_service;
490              
491 1         13 $self->_set_default_logger;
492              
493 1         6 $self->_set_defaults(%options);
494              
495 1         9 $self->_create_methods;
496              
497 1 50       4 if ( $self->is_botocore_api ) {
498 0         0 $self->set_serializer(
499             Amazon::API::Botocore::Shape::Serializer->new(
500             service => get_service_from_class( ref $self )
501             )
502             );
503             }
504              
505 1         10 return $self;
506             }
507              
508             ########################################################################
509             sub set_log_level {
510             ########################################################################
511 1     1 0 4 my ( $self, $log_level ) = @_;
512              
513 1         23 $self->set__log_level($log_level);
514              
515 1 50       14 if ( Log::Log4perl->initialized() ) {
516 1         7 Log::Log4perl->get_logger->level( $LOG4PERL_LOG_LEVELS{$log_level} );
517             }
518             else {
519 0         0 my $logger = $self->get_logger;
520              
521 0 0       0 if ( $logger->can('level') ) {
522 0         0 $logger->level($log_level);
523             }
524             }
525              
526 1         1490 return $self;
527             }
528              
529             ########################################################################
530             sub get_log_level {
531             ########################################################################
532 0     0 0 0 my ($self) = @_;
533              
534 0         0 return $self->get__log_level();
535             }
536              
537             ########################################################################
538             sub get_api_service {
539             ########################################################################
540 0     0 1 0 my ( $service, %args ) = @_;
541              
542 0         0 $service = create_module_name lc $service;
543              
544 0         0 my $class = sprintf 'Amazon::API::%s', $service;
545              
546 0         0 require_class $class;
547              
548 0         0 return $class->new(%args);
549             }
550              
551             ########################################################################
552             sub decode_response {
553             ########################################################################
554 0     0 1 0 my ( $self, $response ) = @_;
555              
556 0   0     0 $response = $response || $self->get_response;
557              
558             # could be Furl or HTTP?
559 0 0 0     0 if ( !ref $response && $response->can('content') ) {
560 0         0 croak q{can't decode response - not a response object: } . ref $response;
561             }
562              
563 0         0 my $content = $response->content;
564 0         0 my $content_type = $response->content_type;
565              
566 0         0 my $decoded_content;
567              
568 0 0       0 if ($content) {
569              
570 0         0 $decoded_content = eval {
571 0 0       0 if ( $content_type =~ /json/xmsi ) {
    0          
572 0         0 decode_json($content);
573             }
574             elsif ( $content_type =~ /xml/xmsi ) {
575              
576 0         0 XMLin(
577             $content,
578             ForceContent => $self->is_botocore_api,
579             ForceArray => ['item'],
580             # $self->get_force_array ? ( ForceArray => ['item'] ) : ()
581             );
582             }
583             };
584              
585             # disregard content_type (it might be misleading)
586 0 0 0     0 if ( !$decoded_content || $EVAL_ERROR ) {
587 0         0 $decoded_content = eval { return decode_json($content); };
  0         0  
588              
589 0 0 0     0 if ( !$decoded_content || $EVAL_ERROR ) {
590 0         0 $decoded_content = eval {
591 0 0       0 return XMLin(
592             $content,
593             SuppressEmpty => undef,
594             $self->get_force_array ? ( ForceArray => ['item'] ) : ()
595             );
596             };
597             }
598             }
599             }
600              
601 0   0     0 $content = $decoded_content || $content;
602              
603 0     0   0 DEBUG( sub { return Dumper( [ 'content', $content ] ) } );
  0         0  
604              
605             # we'll only have a "serializer" if this is a Botocore generated API
606 0         0 my $serializer = $self->get_serializer;
607              
608 0 0 0     0 if ( ref $content && $serializer ) {
609              
610 0         0 my $botocore_action = $self->get_botocore_action;
611              
612 0         0 my $output = $botocore_action->{'output'};
613              
614 0         0 my $orig_content = $content;
615              
616 0 0       0 if ( $output->{resultWrapper} ) {
617 0         0 $content = $content->{ $output->{resultWrapper} };
618             }
619              
620 0     0   0 DEBUG( sub { return Dumper( [ 'content', $content, $output ] ) } );
  0         0  
621              
622 0         0 $serializer->set_logger( $self->get_logger );
623              
624 0         0 $content = eval {
625             $serializer->serialize(
626             service => get_service_from_class( ref $self ),
627             shape => $output->{shape},
628 0         0 data => $content
629             );
630             };
631              
632             # ...but this isn't necessarily where things STB
633 0 0 0     0 if ( !$content || $EVAL_ERROR ) {
634 0 0       0 if ( $self->get_raise_serialization_errors ) {
635 0         0 die $EVAL_ERROR;
636             }
637             else {
638 0         0 warn
639             "error serializing content: please report this error\n$EVAL_ERROR";
640 0         0 $content = $orig_content;
641             }
642             }
643             }
644              
645 0         0 return $content;
646             }
647              
648             ########################################################################
649             sub get_botocore_action {
650             ########################################################################
651 0     0 0 0 my ($self) = @_;
652              
653 0         0 return $self->get_botocore_operations->{ $self->get_action };
654             }
655              
656             ########################################################################
657             sub is_botocore_shape {
658             ########################################################################
659 0     0 0 0 my ($request) = @_;
660              
661 0         0 my $shape_name = ref $request;
662              
663 0 0       0 if ( $shape_name =~ /Botocore::Shape::([^:]+)::([^:]+)$/xsm ) {
664 0         0 $shape_name = $2;
665             }
666             else {
667 0         0 $shape_name = undef;
668             }
669              
670 0         0 return $shape_name;
671             }
672              
673             ########################################################################
674             sub is_param_type {
675             ########################################################################
676 0     0 0 0 my ( $self, $shape_name, $param, $type ) = @_;
677              
678 0         0 my $members = $self->get_botocore_shapes->{$shape_name}->{members};
679 0         0 my $member = $members->{$param};
680              
681 0         0 my $location = $member->{location};
682              
683             TRACE(
684             sub {
685 0     0   0 return Dumper( $members, $member, $location, $param, $type,
686             $shape_name );
687             }
688 0         0 );
689              
690             return ( $location && $location eq $type )
691             ? $member->{locationName}
692 0 0 0     0 : $EMPTY;
693             }
694              
695             ########################################################################
696             sub is_query_param {
697             ########################################################################
698 0     0 0 0 my ( $self, $shape_name, $param ) = @_;
699              
700 0         0 return $self->is_param_type( $shape_name, $param, 'querystring' );
701             }
702              
703             ########################################################################
704             sub is_uri_param {
705             ########################################################################
706 0     0 0 0 my ( $self, $shape_name, $param ) = @_;
707              
708 0         0 return $self->is_param_type( $shape_name, $param, 'uri' );
709             }
710              
711             ########################################################################
712             sub create_botocore_request {
713             ########################################################################
714 0     0 0 0 my ( $self, %args ) = @_;
715              
716 0         0 my ( $parameters, $action ) = @args{qw(parameters action)};
717              
718 0   0     0 $action //= $self->get_action;
719              
720 0 0       0 croak 'no action'
721             if !$action;
722              
723 0 0       0 croak 'no parameters'
724             if !$parameters;
725              
726 0 0       0 croak 'not a botocore API'
727             if !$self->is_botocore_api($action);
728              
729 0         0 my $botocore_operations = $self->get_botocore_operations->{$action};
730              
731 0         0 my $input = $botocore_operations->{'input'};
732 0         0 my $shape = $input->{'shape'};
733              
734 0         0 my $class = require_shape( $shape, get_service_from_class($self) );
735              
736 0 0       0 croak "could not create request shape: $shape\n$EVAL_ERROR"
737             if !$class;
738              
739 0         0 my $request = $class->new($parameters); # request class
740              
741 0         0 return $request;
742             }
743              
744             ########################################################################
745             # init_botocore_request( $self, $request)
746             ########################################################################
747              
748             # This function will accept either an object which is a sub-class of
749             # Amazon::API::Botocore::Shape, or a hash if the parameters have been
750             # constructed "by-hand",
751             #
752             # The parameters are used to populate both the URL if some parameters
753             # are passed in the URL and either a JSON or XML payload depending on
754             # the API type (rest-json, rest-xml).
755              
756             ########################################################################
757             sub init_botocore_request {
758             ########################################################################
759 0     0 0 0 my ( $self, $request ) = @_;
760              
761 0   0     0 $request //= {};
762              
763 0         0 my $action = $self->get_action;
764              
765 0         0 my $botocore_operations = $self->get_botocore_operations->{$action};
766              
767             TRACE(
768             sub {
769 0     0   0 return Dumper(
770             [ 'init_botocore_request:', $botocore_operations,
771             $request, $self->get_botocore_metadata
772             ]
773             );
774             }
775 0         0 );
776              
777 0         0 my $protocol = $self->get_botocore_metadata->{'protocol'};
778              
779 0         0 my $input = $botocore_operations->{'input'};
780              
781 0         0 my $shape = $input->{'shape'};
782              
783 0         0 my $request_shape_name = is_botocore_shape($request);
784              
785             # if a shape object is passed, it must be the correct type
786 0 0 0     0 croak "$action requires a $shape object, not a $request_shape_name object"
787             if $request_shape_name && $request_shape_name ne $shape;
788              
789             # try to create a Botocore request shape
790 0         0 my $boto_request;
791              
792 0 0 0     0 if ( !$request_shape_name && $self->is_botocore_api ) {
793 0         0 $boto_request = $self->create_botocore_request( parameters => $request );
794 0 0       0 if ( !$boto_request ) {
795 0         0 carp "could not create a botocore request object\n$EVAL_ERROR\n";
796             }
797             else {
798 0         0 $request_shape_name = is_botocore_shape($boto_request);
799 0         0 $request = $boto_request;
800             }
801             }
802              
803 0         0 my $http = $botocore_operations->{'http'};
804 0         0 my $method = $http->{'method'};
805              
806 0         0 my %parameters;
807              
808             # is the request a Botocore::Shape object? if so we can use metadata
809             # to create URI and payload, otherwise it's up to the caller to make
810             # sure the URI and the payload are correct...good luck!
811              
812 0 0       0 if ( !$request_shape_name ) {
813 0         0 %parameters = %{$request};
  0         0  
814             }
815             else {
816 0         0 my $finalized_request = $request->finalize;
817              
818 0 0       0 if ( $protocol =~ /rest\-(xml|json)/xsm ) {
819 0         0 $finalized_request = { $request_shape_name => $finalized_request };
820             }
821              
822             DEBUG(
823             sub {
824 0     0   0 return Dumper(
825             [ finalized_request => $finalized_request,
826             protocol => $protocol,
827             ]
828             );
829             }
830 0         0 );
831              
832 0         0 %parameters = %{$finalized_request};
  0         0  
833              
834 0 0       0 if ( defined $input->{'xmlNamespace'} ) {
835             $parameters{$request_shape_name}->{'xmlns'}
836 0         0 = $input->{'xmlNamespace'}->{uri};
837             }
838              
839 0     0   0 DEBUG( sub { return Dumper( [ 'parameters:', \%parameters ] ) } );
  0         0  
840             }
841              
842 0   0     0 my $json_version = $self->get_botocore_metadata->{'jsonVersion'} // '1.0';
843              
844 0         0 my %content_types = (
845             'ec2' => 'application/x-www-form-urlencoded',
846             'query' => 'application/x-www-form-urlencoded',
847             'rest-json' => 'application/json',
848             'json' => 'application/x-amz-json-' . $json_version,
849             'rest-xml' => 'application/xml',
850             );
851              
852 0         0 $self->set_http_method($method);
853 0         0 $self->set_content_type( $content_types{$protocol} );
854              
855 0         0 DEBUG("protocol: $protocol");
856 0         0 DEBUG("method: $method");
857 0         0 DEBUG( 'content-type: ' . $self->get_content_type );
858              
859 0         0 my $uri;
860              
861 0 0       0 if ( $protocol =~ /^rest\-(json|xml)/xsm ) {
862 0         0 my @args = @{ $http->{'parsed_request_uri'}->{'parameters'} };
  0         0  
863              
864 0         0 my $request_uri_tpl = $http->{'parsed_request_uri'}->{'request_uri_tpl'};
865              
866             # if the request is a shape, we've already checked for required parameters
867 0 0       0 if ( !$request_shape_name ) {
868 0         0 foreach my $p (@args) {
869             croak 'required parameter ' . $p . ' not found.'
870 0 0       0 if !exists $parameters{$p};
871             }
872              
873 0         0 $uri = sprintf $request_uri_tpl, @parameters{@args};
874 0         0 $self->set_request_uri($uri);
875              
876 0         0 delete @parameters{@args};
877             }
878             else {
879              
880 0 0       0 if ($request_shape_name) {
881              
882 0         0 $uri = $http->{requestUri}; # use the Botocore template
883              
884 0         0 my $shape_parameters = $parameters{$shape};
885              
886             DEBUG(
887             sub {
888 0     0   0 return sprintf "requestUri: %s\nshape parameter: %s", $uri,
889             Dumper($shape_parameters);
890             }
891              
892 0         0 );
893              
894 0         0 foreach my $p ( keys %{$shape_parameters} ) {
  0         0  
895 0         0 DEBUG("parameter: $p");
896              
897 0 0       0 if ( my $var = $self->is_uri_param( $request_shape_name, $p ) ) {
898              
899 0         0 my $val = $shape_parameters->{$p};
900              
901 0         0 DEBUG("var: $var val: $val");
902              
903 0         0 $uri =~ s/[{]$var[}]/$val/xsm;
904              
905 0         0 delete $shape_parameters->{$p};
906             }
907             }
908             }
909             else {
910 0         0 $uri = sprintf $request_uri_tpl, @{ $parameters{$shape} }{@args};
  0         0  
911 0         0 delete @{ $parameters{$shape} }{@args};
  0         0  
912             }
913              
914             # we're not done yet...just to make things interesting, some
915             # APIs embed request parameters in the uri, payload AND query
916             # string!
917              
918 0 0       0 if ($request_shape_name) {
919 0         0 my %query_parameters;
920              
921 0         0 my $shape_parameters = $parameters{$shape};
922              
923 0         0 foreach my $p ( keys %{$shape_parameters} ) {
  0         0  
924 0 0       0 if ( my $var = $self->is_query_param( $request_shape_name, $p ) ) {
925 0         0 $query_parameters{$var} = $shape_parameters->{$p};
926 0         0 delete $shape_parameters->{$p};
927             }
928             }
929              
930             DEBUG(
931             sub {
932 0     0   0 return Dumper( [ 'query parameters: ', \%query_parameters ] );
933             }
934 0         0 );
935              
936 0 0       0 if ( keys %query_parameters ) {
937             # $self->set_content_type(undef);
938              
939 0         0 $uri = sprintf '%s?%s', $uri,
940             create_urlencoded_content( \%query_parameters );
941             }
942              
943 0 0       0 if ( !keys %{$shape_parameters} ) {
  0         0  
944 0         0 %parameters = ();
945             }
946             }
947             }
948              
949 0         0 $self->set_request_uri($uri);
950              
951             TRACE(
952             sub {
953 0     0   0 return Dumper [ 'rest API', \%parameters, $self->get_request_uri ];
954             }
955 0         0 );
956             }
957              
958 0         0 my $content = \%parameters;
959              
960 0   0     0 my $content_type = $self->get_content_type // $EMPTY;
961              
962 0 0 0     0 if ( $method ne 'POST' && !keys %parameters ) {
963 0         0 $content = undef;
964             }
965              
966 0     0   0 DEBUG( sub { return Dumper( [ content => $content, uri => $uri ] ) } );
  0         0  
967              
968 0         0 return $content;
969             }
970              
971             ########################################################################
972             sub is_botocore_api {
973             ########################################################################
974 1     1 0 3 my ($self) = @_;
975              
976 1         19 return defined $self->get_botocore_metadata;
977             }
978              
979             ########################################################################
980             sub serialize_content {
981             ########################################################################
982 0     0 0 0 my ( $self, $parameters ) = @_;
983              
984 0         0 my $content = $parameters;
985 0         0 my $action = $self->get_action;
986 0         0 my $version = $self->get_version;
987 0         0 my $content_type = $self->get_content_type;
988              
989             DEBUG(
990             sub {
991 0     0   0 return Dumper(
992             [ 'serialize_content' => $parameters,
993             service => $self->get_service
994             ]
995             );
996             }
997 0         0 );
998              
999             # if the API is a query API, url encode parameters
1000 0 0 0 0   0 if ( any { $_ eq lc $self->get_service } @{ $API_TYPES{query} } ) {
  0 0       0  
  0         0  
1001 0         0 $content = create_urlencoded_content( $parameters, $action, $version );
1002             }
1003             elsif ( ref $parameters && reftype($parameters) eq 'HASH' ) {
1004 0 0       0 if ( $content_type =~ /json/xsm ) {
    0          
1005 0         0 $content = encode_json($parameters);
1006             }
1007             elsif ( $content_type =~ /xml/xms ) {
1008             return
1009 0 0 0     0 if !ref $content || !keys %{$content};
  0         0  
1010              
1011 0         0 $content = XMLout(
1012             $parameters,
1013             XMLDecl => $TRUE,
1014             KeepRoot => $TRUE,
1015             KeyAttr => ['xmlns'],
1016             );
1017             }
1018             }
1019              
1020 0         0 return $content;
1021             }
1022              
1023             ########################################################################
1024             # invoke_api( action, parameters, content-type, headers)
1025             ########################################################################
1026             sub invoke_api {
1027             ########################################################################
1028 0     0 1 0 my ( $self, @args ) = @_;
1029              
1030 0         0 my ( $action, $parameters, $content_type, $headers );
1031              
1032             # use_botocore overrides the creation of a Botocore request
1033             # object. In that case the arguments are expected to conform to the
1034             # format specified for this particular AWS API.
1035              
1036 0   0     0 my $use_botocore = $self->get_use_botocore // $self->is_botocore_api;
1037              
1038 0 0 0     0 if ( ref $args[0] && reftype( $args[0] ) eq 'HASH' ) {
1039             ( $action, $parameters, $content_type, $headers )
1040 0         0 = @{ $args[0] }{qw( action parameters content_type headers)};
  0         0  
1041             }
1042             else {
1043             # only HASH refs can be promoted to a Botocore request object
1044 0         0 $use_botocore = $FALSE;
1045 0         0 ( $action, $parameters, $content_type, $headers ) = @args;
1046             }
1047              
1048             DEBUG(
1049             sub {
1050 0     0   0 return Dumper(
1051             [ parameters => $parameters,
1052             'content-type' => $content_type,
1053             ]
1054             );
1055             }
1056 0         0 );
1057              
1058 0         0 $self->set_action($action);
1059 0         0 $self->set_last_action($action);
1060 0         0 $self->set_error(undef);
1061              
1062 0         0 my $decode_response = $self->get_decode_always;
1063              
1064             # someone is trying to send a blessed object, possibly a Botocore
1065             # request object using a class that does not have the Botocore
1066             # metadata
1067 0 0 0     0 croak sprintf qq{"%s" was not generated with Botocore support.\n}
1068             . qq{Parameters should be simple objects, not blessed.\n}, ref $self
1069             if blessed($parameters) && !$self->is_botocore_api;
1070              
1071 0         0 my @paged_results;
1072              
1073             # auto pagination is only available when using the Botocore metadata
1074 0         0 my ( $paginator, $use_paginator, $limit );
1075              
1076 0 0 0     0 if ( $use_botocore && $self->get_use_paginator ) {
1077 0   0     0 $paginator = $self->get_paginators && $self->get_paginators->{$action};
1078 0         0 $use_paginator = $self->get_use_paginator;
1079             }
1080              
1081             # if we're using the Botocore metadata, create a request object
1082 0 0       0 if ($use_botocore) {
    0          
1083 0 0       0 if ($paginator) {
1084              
1085 0   0     0 $paginator->{more_results} //= $paginator->{output_token};
1086              
1087 0         0 $limit = $parameters->{ $paginator->{limit_key} };
1088              
1089             DEBUG(
1090             sub {
1091 0     0   0 return Dumper(
1092             [ paginator => $paginator,
1093             limit => $limit,
1094             parameters => $parameters,
1095             ]
1096             );
1097             }
1098 0         0 );
1099             }
1100              
1101 0         0 $parameters = $self->init_botocore_request($parameters);
1102              
1103 0         0 $content_type = $self->get_content_type;
1104              
1105             DEBUG(
1106             sub {
1107 0     0   0 return Dumper(
1108             [ parameters => $parameters,
1109             content_type => $content_type
1110             ]
1111             );
1112             }
1113 0         0 );
1114              
1115             }
1116             elsif ( !$content_type ) {
1117 0         0 $content_type = $self->set_content_type( $self->_set_content_type );
1118             }
1119              
1120 0         0 my $serialized_content;
1121              
1122 0 0       0 if ($parameters) {
1123 0         0 $serialized_content = $self->serialize_content($parameters);
1124             }
1125              
1126             DEBUG(
1127             sub {
1128 0     0   0 Dumper [
1129             content_type => $content_type,
1130             parameters => $parameters,
1131             serialized_content => $serialized_content
1132             ];
1133             }
1134 0         0 );
1135              
1136 0         0 my $page_count = 0;
1137              
1138             PAGINATE:
1139              
1140 0     0   0 DEBUG( sub { return Dumper( [ 'page', ++$page_count ] ) } );
  0         0  
1141              
1142 0         0 my $rsp = $self->submit(
1143             content => $serialized_content,
1144             content_type => $content_type,
1145             headers => $headers,
1146             );
1147              
1148 0         0 $self->set_response($rsp);
1149              
1150 0 0       0 if ( !$rsp->is_success ) {
1151              
1152 0         0 $self->set_error(
1153             Amazon::API::Error->new(
1154             { error => $rsp->code,
1155             message_raw => $rsp->content,
1156             content_type => scalar $rsp->content_type,
1157             api => ref $self,
1158             response => $rsp,
1159             }
1160             )
1161             );
1162              
1163 0 0       0 if ( $self->get_print_error ) {
1164 0         0 print {*STDERR} $self->print_error;
  0         0  
1165             }
1166              
1167 0 0       0 if ( $self->get_raise_error ) {
1168 0         0 die $self->get_error; ## no critic (RequireCarping)
1169             }
1170             }
1171              
1172             DEBUG(
1173             sub {
1174 0     0   0 return Dumper(
1175             [ 'content', $rsp->content,
1176             'decode always', $self->get_decode_always
1177             ]
1178             );
1179             }
1180 0         0 );
1181              
1182 0 0       0 return $rsp->content
1183             if !$self->get_decode_always;
1184              
1185 0         0 eval {
1186 0 0       0 if ($use_paginator) {
1187 0         0 my $result = $self->decode_response;
1188              
1189 0         0 push @paged_results, @{ $result->{ $paginator->{result_key} } };
  0         0  
1190              
1191             DEBUG(
1192             sub {
1193             return Dumper(
1194             [ result => $result,
1195             paged_results => \@paged_results,
1196             more_results => $paginator->{more_results},
1197 0     0   0 ]
1198             );
1199             }
1200 0         0 );
1201              
1202 0 0       0 if ( !$result->{ $paginator->{more_results} } ) {
1203 0         0 delete $result->{ $paginator->{more_results} };
1204              
1205 0         0 return \@paged_results;
1206             }
1207              
1208 0   0     0 $limit = $limit // $result->{ $paginator->{limit_key} };
1209              
1210             my $parameters = $self->init_botocore_request(
1211             { $limit ? ( $paginator->{limit_key} => $limit ) : (),
1212             $paginator->{input_token} => $result->{ $paginator->{output_token} }
1213             }
1214 0 0       0 );
1215              
1216 0         0 $serialized_content = $self->serialize_content($parameters);
1217              
1218 0         0 goto PAGINATE;
1219             }
1220             };
1221              
1222 0 0       0 if ($EVAL_ERROR) {
1223 0 0       0 if ( $self->get_raise_serialization_errors ) {
1224 0         0 die $EVAL_ERROR;
1225             }
1226             else {
1227 0         0 warn "error serializing content: please report this error\n$EVAL_ERROR";
1228 0         0 return $rsp->content;
1229             }
1230             }
1231              
1232             return \@paged_results
1233 0 0       0 if $use_paginator;
1234              
1235 0         0 my $results = $self->decode_response;
1236              
1237 0 0 0     0 return $results
1238             if !$paginator || !$use_paginator;
1239              
1240             # you must have a paginator, but told me not to use it, delete
1241             # blank/null markers
1242              
1243             # TODO: figure out the comment above because it does not match the code
1244             # 'use_paginator' is tested above and if it is false (you told me not to use the
1245             # paginator) we return the results...
1246              
1247 0 0       0 if ( !$results->{ $paginator->{more_results} } ) {
1248 0         0 delete $results->{ $paginator->{more_results} };
1249 0         0 delete $results->{ $paginator->{input_token} };
1250             }
1251              
1252 0         0 return $results;
1253             }
1254              
1255             ########################################################################
1256             sub print_error {
1257             ########################################################################
1258 0     0 1 0 my ($self) = @_;
1259              
1260 0         0 my $error = $self->get_error;
1261              
1262 0         0 my $err_str = 'API ERROR (' . $self->get_last_action . '): ';
1263              
1264 0 0 0     0 if ( $error && ref($error) =~ /Amazon::API::Error/xms ) {
1265 0         0 my $response = $error->get_response;
1266              
1267 0         0 local $Data::Dumper::Terse = $TRUE;
1268 0         0 local $Data::Dumper::Pair = $COLON;
1269 0         0 local $Data::Dumper::Useqq = $TRUE;
1270              
1271 0 0       0 $err_str .= sprintf q{[%s], %s},
1272             $error->get_error, Dumper( ref $response ? $response : [$response] );
1273             }
1274             else {
1275 0         0 $err_str .= '[' . $error . ']';
1276             }
1277              
1278 0         0 return $err_str;
1279             }
1280              
1281             ########################################################################
1282             sub get_valid_token {
1283             ########################################################################
1284 0     0 0 0 my ($self) = @_;
1285              
1286 0         0 my $credentials = $self->get_credentials;
1287              
1288 0 0       0 if ( $credentials->can('is_token_expired') ) {
1289 0 0       0 if ( $credentials->is_token_expired ) {
1290              
1291 0 0 0     0 if ( !$self->can('refresh_token') || !$credentials->refresh_token ) {
1292 0         0 croak 'token expired';
1293             }
1294             }
1295             }
1296              
1297 0     0   0 TRACE( sub { return Dumper( [ 'valid token:', $credentials->get_token ] ) }
1298 0         0 );
1299              
1300 0         0 return $credentials->get_token;
1301             }
1302              
1303             ########################################################################
1304             sub submit {
1305             ########################################################################
1306 0     0 1 0 my ( $self, %options ) = @_;
1307              
1308 0     0   0 DEBUG( sub { return Dumper [ 'submit:', \%options ] } );
  0         0  
1309              
1310 0   0     0 my $method = $self->get_http_method || 'POST';
1311 0   0     0 my $headers = $options{'headers'} || [];
1312              
1313 0         0 my $url = $self->get_url;
1314              
1315             my $botocore_protocol
1316 0   0     0 = eval { $self->get_botocore_metadata->{'protocol'} } // $EMPTY;
  0         0  
1317              
1318 0 0       0 if ( $botocore_protocol =~ /^rest\-(json|xml)/xsm ) {
1319 0 0       0 croak 'no request URI provided for rest-json call'
1320             if !$self->get_request_uri;
1321              
1322 0         0 $url .= $self->get_request_uri;
1323             }
1324              
1325 0     0   0 DEBUG( sub { return Dumper [ $method, $url, $headers ] } );
  0         0  
1326              
1327 0         0 my $request = HTTP::Request->new( $method, $url, $headers );
1328              
1329             # 1. set the header
1330             # 2. set the content
1331             # 3. sign the request
1332             # 4. send the request & return result
1333              
1334             # see IMPLEMENTATION NOTES for an explanation
1335 0 0 0     0 if ( $self->get_api || $self->get_target_prefix ) {
1336 0         0 $request = $self->_set_x_amz_target($request);
1337             }
1338              
1339 0         0 $self->_set_request_content( request => $request, %options );
1340 0         0 my $credentials = $self->get_credentials;
1341              
1342 0 0       0 if ( my $token = $self->get_valid_token ) {
1343 0         0 $request->header( 'X-Amz-Security-Token', $token );
1344             }
1345              
1346             # TODO: global end-points
1347 0         0 my $region = $self->get_region;
1348              
1349             # sign the request
1350 0   0     0 Amazon::API::Signature4->new(
1351             -access_key => $credentials->get_aws_access_key_id,
1352             -secret_key => $credentials->get_aws_secret_access_key,
1353             -security_token => $credentials->get_token || undef,
1354             service => $self->get_service,
1355             region => $region,
1356             )->sign( $request, $self->get_region );
1357              
1358 0     0   0 DEBUG( sub { return Dumper( [$request] ) } );
  0         0  
1359              
1360             # make the request, return response object
1361 0         0 my $ua = $self->get_user_agent;
1362 0         0 my $rsp = $ua->request($request);
1363              
1364 0     0   0 DEBUG( sub { return Dumper [$rsp] } );
  0         0  
1365              
1366 0         0 return $rsp;
1367             }
1368              
1369             # +------------------+
1370             # | EXPORTED METHODS |
1371             # +------------------+
1372              
1373             ########################################################################
1374             sub param_n {
1375             ########################################################################
1376 0     0 1 0 my (@args) = @_;
1377              
1378 0         0 return Amazon::API::Botocore::Shape::Utils::param_n(@args);
1379             }
1380              
1381             ########################################################################
1382             # create_urlencoded_content(parameters, action, version)
1383             # input:
1384             # parameters:
1385             # SCALAR - query string to encode (x=y&w=z...)
1386             # ARRAY - either an array of hashes or...
1387             # key/value pairs of the form x=y or...
1388             # key/values pairs
1389             # HASH - key/value pairs, if the value is an array then
1390             # it is assumed to be a list of hashes
1391             # action: API method
1392             # version: wsdl version for API
1393             #
1394             # output:
1395             # URL encodode query string
1396             #
1397             ########################################################################
1398             sub create_urlencoded_content {
1399             ########################################################################
1400 5     5 0 3864 my ( $parameters, $action, $version ) = @_;
1401              
1402 5         7 my @args;
1403              
1404 5 100 66     55 if ( $parameters && !ref $parameters ) {
    100 66        
    50 33        
1405 1         6 @args = map { split /=/xsm } split /&/xsm, $parameters;
  4         9  
1406             }
1407             elsif ( $parameters && reftype($parameters) eq 'HASH' ) {
1408 1         2 foreach my $key ( keys %{$parameters} ) {
  1         6  
1409 2 50 33     7 if ( ref $parameters->{$key}
1410             && reftype( $parameters->{$key} ) eq 'ARRAY' ) {
1411 0         0 push @args, map { %{$_} } @{ $parameters->{$key} };
  0         0  
  0         0  
  0         0  
1412             }
1413             else {
1414 2         5 push @args, $key, $parameters->{$key};
1415             }
1416             }
1417             }
1418             elsif ( $parameters && reftype($parameters) eq 'ARRAY' ) {
1419              
1420             # if any are refs then they should be hashes...
1421 3 100   7   13 if ( any {ref} @{$parameters} ) {
  7 100       16  
  3         12  
1422              
1423 1         2 @args = map { %{$_} } @{$parameters}; # list of hashes
  2         5  
  2         7  
  1         3  
1424             }
1425 5     5   12 elsif ( any {/=/xsm} @{$parameters} ) {
  2         6  
1426 1         2 @args = map { split /=/xsm } @{$parameters}; # formatted list
  2         7  
  1         3  
1427             }
1428             else {
1429 1         3 @args = @{$parameters}; # simple list
  1         3  
1430             }
1431             }
1432              
1433 5         17 my $content;
1434              
1435 5 100 66 17   23 if ( $action && !any {/Action/xsm} @args ) {
  17         33  
1436 4         10 push @args, 'Action', $action;
1437             }
1438              
1439 5 100 66 31   23 if ( $version && !any {/Version/xsm} @args ) {
  31         47  
1440 4         8 push @args, 'Version', $version;
1441             }
1442              
1443             return join $AMPERSAND,
1444 5         37 map { sprintf '%s=%s', $_->[0], url_encode( $_->[1] ) } pairs @args;
  20         195  
1445             }
1446              
1447             ########################################################################
1448             sub has_keys {
1449             ########################################################################
1450 0     0 0 0 my ( $self, %options ) = @_;
1451              
1452 0 0       0 my %creds = keys %options ? %options : @{$self}{@REQUIRED_KEYS};
  0         0  
1453              
1454 0   0     0 return $creds{'aws_secret_access_key'} && $creds{'aws_access_key_id'};
1455             }
1456              
1457             # +-----------------+
1458             # | PRIVATE METHODS |
1459             # +-----------------+
1460              
1461             # should not be called if we have botocore definition
1462             ########################################################################
1463             sub _set_content_type {
1464             ########################################################################
1465 0     0   0 my ($self) = @_;
1466              
1467 0         0 my $service = $self->get_service;
1468              
1469             # default content-type
1470 0         0 my $content_type = $self->get_content_type;
1471              
1472             return 'application/x-www-form-urlencoded'
1473 0 0   0   0 if any { $_ eq $service } @{ $API_TYPES{query} };
  0         0  
  0         0  
1474              
1475             return 'application/x-amz-json-1.1'
1476 0 0   0   0 if any { $_ eq $service } @{ $API_TYPES{json} };
  0         0  
  0         0  
1477              
1478             return 'application/xml'
1479 0 0   0   0 if any { $_ eq $service } @{ $API_TYPES{xml} };
  0         0  
  0         0  
1480              
1481 0         0 return $content_type;
1482             }
1483              
1484             ########################################################################
1485             sub _create_methods {
1486             ########################################################################
1487 1     1   4 my ($self) = @_;
1488              
1489 1   33     18 my $class = ref $self || $self;
1490              
1491 1 50       28 if ( $self->get_api_methods ) {
1492 3     3   18900 no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict)
  3         6  
  3         114  
1493 3     3   35 no warnings 'redefine'; ## no critic (TestingAndDebugging::ProhibitNoWarnings)
  3         12  
  3         1030  
1494              
1495 0         0 my $stash = \%{ __PACKAGE__ . $DOUBLE_COLON };
  0         0  
1496              
1497 0         0 foreach my $api ( @{ $self->get_api_methods } ) {
  0         0  
1498              
1499 0         0 my $method = lcfirst $api;
1500              
1501 0         0 $method =~ s/([[:lower:]])([[:upper:]])/$1_$2/xmsg;
1502 0         0 $method = lc $method;
1503              
1504 0         0 my $snake_case_method = $class . $DOUBLE_COLON . $method;
1505 0         0 my $camel_case_method = $class . $DOUBLE_COLON . $api;
1506              
1507             # snake case rules the day
1508              
1509 0 0       0 if ( !$stash->{$method} ) {
1510 0         0 *{$snake_case_method} = sub {
1511 0     0   0 my $self = shift;
1512              
1513 0         0 $self->invoke_api( $api, @_ );
1514 0         0 };
1515             }
1516              
1517             # ...but some prefer camels
1518 0 0       0 if ( !$stash->{$api} ) {
1519 0         0 *{$camel_case_method} = sub {
1520 0     0   0 my $self = shift;
1521              
1522 0         0 $self->$method(@_);
1523 0         0 };
1524             }
1525             }
1526              
1527             }
1528              
1529 1         14 return $self;
1530             }
1531              
1532             ########################################################################
1533             sub _create_stealth_logger {
1534             ########################################################################
1535 0     0   0 my ( $self, $name, $level ) = @_;
1536              
1537 0         0 my $sub_name = sprintf '%s::%s', $name, uc $level;
1538              
1539 3     3   23 no strict 'refs'; ## no critic ProhibitNoStrict
  3         6  
  3         3637  
1540              
1541             return
1542 0 0       0 if defined *{$sub_name}{CODE};
  0         0  
1543              
1544 0         0 *{$sub_name} = sub {
1545 0 0 0 0   0 if ( $self->get_logger && $self->get_logger->can($level) ) {
1546 0         0 $self->get_logger->$level(@_);
1547             }
1548             else {
1549 0         0 return;
1550             }
1551 0         0 };
1552              
1553 0         0 return;
1554             }
1555              
1556             ########################################################################
1557             sub _set_default_logger {
1558             ########################################################################
1559 1     1   3 my ($self) = @_;
1560              
1561 1 50 33     21 if ( !$self->get_logger && !Log::Log4perl->initialized ) {
1562              
1563 1         24 eval {
1564 1         8 require Log::Log4perl;
1565              
1566 1         8 Log::Log4perl->import(':easy');
1567              
1568             Log::Log4perl->easy_init(
1569 1 50 33     859 { level => $LOG4PERL_LOG_LEVELS{'info'},
1570             layout => $self->get_log_layout // $DEFAULT_LAYOUT_PATTERN,
1571             ( $self->get_log_file ? ( file => $self->get_log_file ) : () ),
1572              
1573             }
1574             );
1575             };
1576             }
1577             else {
1578 0         0 for my $level (qw(debug trace info warn error )) {
1579 0         0 $self->_create_stealth_logger( __PACKAGE__, $level );
1580 0         0 $self->_create_stealth_logger( ref($self), $level );
1581             }
1582             }
1583              
1584 1         6087 return $self;
1585             }
1586              
1587             ########################################################################
1588             sub _set_defaults {
1589             ########################################################################
1590 1     1   6 my ( $self, %options ) = @_;
1591              
1592 1   33     32 $self->set_raise_error( $self->get_raise_error // $TRUE );
1593 1   33     59 $self->set_print_error( $self->get_print_error // $TRUE );
1594              
1595 1   33     54 $self->set_use_paginator( $self->get_use_paginator // $TRUE );
1596              
1597 1 50       53 if ( !$self->get_user_agent ) {
1598 1         17 $self->set_user_agent( LWP::UserAgent->new );
1599             }
1600              
1601 1 50       3708 if ( !defined $self->get_decode_always ) {
1602 1         28 $self->set_decode_always($TRUE);
1603              
1604 1 0 33     28 if ( !$self->get_decode_always && !defined $self->get_force_array ) {
1605 0         0 $self->set_force_array($FALSE);
1606             }
1607             }
1608              
1609             # most API services are POST, but using the Botocore metadata is best
1610 1   50     38 $self->set_http_method( $self->get_http_method // 'POST' );
1611              
1612 1   50     54 $self->set_protocol( $self->get_protocol() // 'https' );
1613              
1614             # note some APIs are global, hence an API may send '' to indicate global
1615 1 50       65 if ( !defined $self->get_region ) {
1616             $self->set_region( $self->get_region
1617             || $ENV{'AWS_REGION'}
1618 1   33     38 || $ENV{'AWS_DEFAULT_REGION'}
1619             || $DEFAULT_REGION );
1620             }
1621              
1622 1   33     54 my $debug //= ( $options{debug} || $ENV{DEBUG} );
      33        
1623              
1624 1 50 50     11 $options{log_level} = $debug ? 'debug' : $options{log_level} // 'info';
1625              
1626 1         8 $self->set_log_level( $options{log_level} );
1627              
1628 1 50       30 if ( !$self->get_credentials ) {
1629 0 0       0 if ( $self->has_keys(%options) ) {
1630             $self->set_credentials(
1631             Amazon::Credentials->new(
1632             { aws_secret_access_key => $options{'aws_secret_access_key'},
1633             aws_access_key_id => $options{'aws_access_key_id'},
1634 0         0 token => $options{'token'},
1635             region => $self->get_region,
1636             }
1637             )
1638             );
1639             }
1640             else {
1641             $self->set_credentials(
1642             Amazon::Credentials->new(
1643             order => $options{'order'},
1644 0         0 region => $options{'region'},
1645             )
1646             );
1647              
1648 0 0       0 if ( !defined $self->get_region ) {
1649 0         0 $self->set_region( $self->get_credentials->get_region );
1650             }
1651             }
1652             }
1653              
1654             # set URL last since it contains region
1655 1         18 $self->_set_url;
1656              
1657 1         2 return $self;
1658             }
1659              
1660             ########################################################################
1661             sub _create_service_url {
1662             ########################################################################
1663 1     1   3 my ($self) = @_;
1664              
1665 1         2 my $url;
1666              
1667 1         20 my $botocore_metadata = $self->get_botocore_metadata;
1668 1         23 my $service = $self->get_service;
1669              
1670 1 50 33     10 if ( $botocore_metadata && $botocore_metadata->{globalEndpoint} ) {
1671             $url = sprintf '%s://%s', $self->get_protocol,
1672 0         0 $botocore_metadata->{globalEndpoint};
1673             }
1674             else {
1675 1   33     20 my $endpoint = $self->get_endpoint_prefix || $service;
1676              
1677 1 50   7   28 if ( any { $_ eq $service } @GLOBAL_SERVICES ) {
  7 50       32  
1678 0         0 $url = sprintf $GLOBAL_URL_FMT, $self->get_protocol, $endpoint;
1679             }
1680             elsif ( $self->get_region ) {
1681 1         26 $url = sprintf $REGIONAL_URL_FMT, $self->get_protocol, $endpoint,
1682             $self->get_region;
1683             }
1684             else {
1685 0         0 $url = sprintf $REGIONAL_URL_FMT, $self->get_protocol, $endpoint,
1686             'us-east-1';
1687             }
1688             }
1689              
1690 1         34 return $url;
1691             }
1692              
1693             ########################################################################
1694             sub _set_url {
1695             ########################################################################
1696 1     1   4 my ($self) = @_;
1697              
1698 1         22 my $url = $self->get_url;
1699              
1700 1 50       9 if ( !$url ) {
1701 1         5 $url = $self->_create_service_url;
1702             }
1703             else {
1704 0 0       0 if ( $url !~ /^https?/xmsi ) {
1705 0         0 $url =~ s/^\///xms; # just remove leading slash...
1706 0         0 $url = $self->get_protocol . '://' . $url;
1707             }
1708              
1709             }
1710              
1711 1         23 $self->set_url($url);
1712              
1713 1         11 return $self;
1714             }
1715              
1716             ########################################################################
1717             sub _set_x_amz_target {
1718             ########################################################################
1719 0     0     my ( $self, $request ) = @_;
1720              
1721 0           my $target = $self->get_target_prefix;
1722 0           my $version = $self->get_version;
1723 0           my $api = $self->get_api;
1724 0           my $action = $self->get_action;
1725              
1726 0 0         if ( !$target ) {
1727 0 0         $target = $version ? $api . $UNDERSCORE . $version : $api;
1728             }
1729              
1730 0           $target = $target . $DOT . $action;
1731              
1732 0           $self->set_target($target);
1733              
1734 0           $request->header( 'X-Amz-Target', $target );
1735              
1736 0           return $request;
1737             }
1738              
1739             ########################################################################
1740             sub _set_request_content {
1741             ########################################################################
1742 0     0     my ( $self, %args ) = @_;
1743              
1744 0           my $request = $args{'request'};
1745 0           my $content = $args{'content'};
1746 0   0       my $content_type = $args{'content_type'} || $self->get_content_type;
1747              
1748             DEBUG(
1749             sub {
1750 0     0     return Dumper(
1751             [ '_set_request_content', $self->get_http_method, \%args ] );
1752             }
1753 0           );
1754              
1755 0 0 0       if ( $self->get_http_method ne 'GET' || !defined $content ) {
1756 0 0         if ($content_type) {
1757 0           $request->content_type( $content_type . '; charset=utf-8' );
1758             }
1759 0           $request->content($content);
1760             }
1761             else {
1762 0           $request->uri( $request->uri . $QUESTION_MARK . $content );
1763             }
1764              
1765 0           return $request;
1766             }
1767              
1768             1;
1769              
1770             __END__
1771              
1772             =pod
1773              
1774             =head1 NAME
1775              
1776             Amazon::API - A generic base class for AWS Services
1777              
1778             =head1 SYNOPSIS
1779              
1780             package Amazon::CloudWatchEvents;
1781              
1782             use parent qw( Amazon::API );
1783              
1784             # subset of methods I need
1785             our @API_METHODS = qw(
1786             ListRuleNamesByTarget
1787             ListRules
1788             ListTargetsByRule
1789             PutEvents
1790             PutRule
1791             );
1792              
1793             sub new {
1794             my $class = shift;
1795              
1796             $class->SUPER::new(
1797             service => 'events',
1798             api => 'AWSEvents',
1799             api_methods => \@API_METHODS,
1800             decode_always => 1
1801             );
1802             }
1803              
1804             1;
1805              
1806             Then...
1807              
1808             my $rules = Amazon::CloudWatchEvents->new->ListRules({});
1809              
1810             ...or
1811              
1812             my $rules = Amazon::API->new(
1813             { service => 'events',
1814             api => 'AWSEvents',
1815             }
1816             )->invoke_api( 'ListRules', {} );
1817              
1818             =head1 DESCRIPTION
1819              
1820             =begin markdown
1821              
1822             [![amazon-api](https://github.com/rlauer6/perl-Amazon-API/actions/workflows/build.yml/badge.svg)](https://github.com/rlauer6/perl-Amazon-API/actions/workflows/build.yml)
1823              
1824             =end markdown
1825              
1826             Generic class for constructing AWS API interfaces. Typically used as a
1827             parent class, but can be used directly. This package can also
1828             generates stubs for Amazon APIs using the Botocore project's
1829             metadata. (See L</BOTOCORE SUPPORT>).
1830              
1831             =over 5
1832              
1833             =item * See L</IMPLEMENTATION NOTES> for using C<Amazon::API>
1834             directly to call AWS services.
1835              
1836             =item * See
1837             L<Amazon::CloudWatchEvents|https://github.com/rlauer6/perl-Amazon-CloudWatchEvents/blob/master/src/main/perl/lib/Amazon/CloudWatchEvents.pm.in>
1838             for an example of how to use this module as a parent class.
1839              
1840             =item * See L<Amazon::API::Botocore::Pod> for information regarding
1841             how to automatically create Perl classes for AWS services using
1842             Botocore metadata.
1843              
1844             =back
1845              
1846             =head1 BACKGROUND AND MOTIVATION
1847              
1848             A comprehensive Perl interface to AWS services similar to the I<Botocore>
1849             library for Python has been a long time in coming. The PAWS project
1850             has been attempting to create an always up-to-date AWS interface with
1851             community support. Some however may find that project a little heavy
1852             in the dependency department. If you are looking for an extensible
1853             (albeit spartan) method of invoking a subset of services with a lower
1854             dependency count, you might want to consider C<Amazon::API>.
1855              
1856             Think of this class as a DIY kit for invoking B<only> the methods you
1857             need for your AWS project. Using the included C<amazon-api> utility
1858             however, you can also roll your own complete Amazon API classes that
1859             include support for serializing requests and responses based on
1860             metadata provided by the Botocore project. The classes you create with
1861             C<amazon-api> include full documentation as pod. (See L</BOTOCORE
1862             SUPPORT> for more details).
1863              
1864             =over 5
1865              
1866             I<NOTE:> The original L<Amazon::API> was written in 2017 as a I<very>
1867             lightweight way to call a handfull of APIs. The evolution of the
1868             module was based on discovering, without much documentation or help,
1869             the nature of Amazon APIs. In retrospect, even back then, it would
1870             have been easier to consult the Botocore project and decipher how that
1871             project managed to create a library from the metadata. Fast forward
1872             to 2022 and L<Amazon::API> is now capable of using the Botocore
1873             metadata in order to, in most cases, correctly call any AWS service.
1874             The L<Amazon::API> module can still be used without the assistance of
1875             Botocore metadata, but it works a heckuva lot better with it.
1876              
1877             =back
1878              
1879             You can use L<Amazon::API> in 3 different ways:
1880              
1881             =over 5
1882              
1883             =item Take the Luddite approach
1884              
1885             my $queues = Amazon::API->new(
1886             {
1887             service => 'sqs',
1888             http_method => 'GET'
1889             })->invoke_api('ListQueues');
1890              
1891             =item Build your own API classes with just what you need
1892              
1893             package Amazon::API::SQS;
1894            
1895             use strict;
1896             use warnings;
1897            
1898             use parent qw( Amazon::API );
1899            
1900             our @API_METHODS = qw(
1901             ListQueues
1902             PurgeQueue
1903             ReceiveMessage
1904             SendMessage
1905             );
1906            
1907             sub new {
1908             my ( $class, @options ) = @_;
1909             $class = ref($class) || $class;
1910            
1911             my %options = ref( $options[0] ) ? %{ $options[0] } : @options;
1912            
1913             return $class->SUPER::new(
1914             { service => 'sqs',
1915             http_method => 'GET',
1916             api_methods => \@API_METHODS,
1917             decode_always => 1,
1918             %options
1919             }
1920             );
1921             }
1922            
1923             1;
1924              
1925             use Amazon::API::SQS;
1926             use Data::Dumper;
1927              
1928             my $sqs = Amazon::API::SQS->new;
1929             print Dumper($sqs->ListQueues);
1930              
1931             =item Use the Botocore metadata to build classes for you
1932              
1933             amazon-api -s sqs create-stubs
1934             amazon-api -s sqs create-shapes
1935              
1936             perl -I . -MData::Dumper -MAmazon::API:SQS -e 'print Dumper(Amazon::API::SQS->new->ListQueues);'
1937              
1938             =over 5
1939              
1940             I<NOTE:> In order to use Botocore metadata you must clone the Botocore
1941             repository and point the utility to the repo.
1942              
1943             Clone the Botocore project from GitHub:
1944              
1945             mkdir ~/git
1946             cd git
1947             git clone https:://github.com/boto/botocore.git
1948              
1949             Generate stub classes for the API and shapes:
1950              
1951             amazon-api -b ~/git/botocore -s sqs -o ~/lib/perl5 create-stubs
1952             amazon-api -b ~/git/botocore -s sqs -o ~/lib/perl5 create-shapes
1953              
1954             perldoc Amazon::API::SQS
1955              
1956             See L<Amazon::API::Botocore::Pod> for more details regarding building
1957             stubs and shapes.
1958              
1959             =back
1960              
1961             =back
1962              
1963             =head1 THE APPROACH
1964              
1965             Essentially, most AWS APIs are RESTful services that adhere to a
1966             common protocol, but differences in services make a single solution
1967             difficult. All services more or less adhere to this framework:
1968              
1969             =over 5
1970              
1971             =item 1. Set HTTP headers (or query string) to indicate the API and
1972             method to be invoked
1973              
1974             =item 2. Set credentials in the header
1975              
1976             =item 3. Set API specific headers
1977              
1978             =item 4. Sign the request and set the signature in the header
1979              
1980             =item 5. Optionally send a payload of parameters for the method being invoked
1981              
1982             =back
1983              
1984             Specific details of the more recent AWS services are well documented,
1985             however early services were usually implemented as simple HTTP
1986             services that accepted a query string. This module attempts to account
1987             for most of the nuances involved in invoking AWS services and
1988             provide a fairly generic way of invoking these APIs in the most
1989             lightweight way possible.
1990              
1991             Using L<Amazon::API> as a generic, lightweight module, naturally does
1992             not provide nuanced support for individual AWS services. To use this
1993             class in that manner for invoking the AWS APIs, you need to be very
1994             familiar with the specific API requirements and responses and be
1995             willng to invest time reading the documentation on Amazon's website.
1996             The payoff is that you can probably use this class to call I<any> AWS
1997             API without installing a large number of dependencies.
1998              
1999             If you don't mind a few extra dependencies and overhead, you should
2000             generate the stub APIs and support classes using the C<amazon-api>
2001             utility. The stubs and shapes produced by the utility will serialize
2002             and deserialize requests and responses correctly by using the Botocore
2003             metadata. Botocore metadata provides the necessary information to
2004             create classes that can successfully invoke all of the Amazon APIs.
2005              
2006             A good example of creating a quick and dirty interface to CloudWatch
2007             Events can be found here:
2008              
2009             L<Amazon::CloudWatchEvents|https://github.com/rlauer6/perl-Amazon-CloudWatchEvents/blob/master/src/main/perl/lib/Amazon/CloudWatchEvents.pm.in>
2010              
2011             And invoking some of the APIs can be as easy as:
2012              
2013             Amazon::API->new(
2014             service => 'sqs',
2015             http_method => 'GET'
2016             }
2017             )->invoke_api('ListQueues');
2018              
2019             =head1 BOTOCORE SUPPORT
2020              
2021             A new experimental module ( L<Amazon::API::Botocore>) is now included
2022             in this project.
2023              
2024             B<!!CAUTION!!>
2025              
2026             I<Support for API calls using the Botocore metadata may be buggy and
2027             is subject to change. It may not be suitable for production
2028             environments at this time.>
2029              
2030             Using Botocore metadata and the utilities in this project, you can
2031             create Perl classes that simplify calling AWS services. After
2032             creating service classes and shape objects from the Botocore metadata
2033             calling AWS APIs will look something like this:
2034              
2035             use Amazon::API::SQS;
2036              
2037             my $sqs = Amazon::API::SQS->new;
2038             my $rsp = $sqs->ListQueues();
2039              
2040             The L<Amazon::API::Botocore> module augments L<Amazon::API> by using
2041             Botocore metadata for determining how to call individual services and
2042             serialize parameters passed to its API methods. A utility (C<amazon-api>)
2043             is provided that can generate Perl classes for all AWS services using
2044             the Botocore metadata.
2045              
2046             Perl classes that represent AWS data structures (aka shapes) that are
2047             passed to or returned from services can also be generated. These
2048             classes allow you to call all of the API methods for a given service
2049             using simple Perl objects that are serialized correctly for a specific
2050             method.
2051              
2052             Service classes are subclassed from C<Amazon::API> so their C<new()>
2053             constructor takes the same arguments as C<Amazon::API::new()>.
2054              
2055             my $credentials = Amazon::Credential->new();
2056              
2057             my $sqs = Amazon::API::SQS->new( credentials => $credentials );
2058              
2059             If you are going to use the Botocore support and automatically
2060             generate API classes you I<must also> create the data structure classes
2061             that are used by each service. The Botocore based APIs will use these
2062             classes to serialize requests and responses.
2063              
2064             For more information on generating API classes, see
2065             L<Amazon::API::Botocore::Pod>.
2066              
2067             =head2 Serialization Errors
2068              
2069             With little documentation to go on, Interpretting the Botocore
2070             metadata and deducing how to serialize shapes from Perl objects has
2071             been a difficult task. It's likely that there are still some edge
2072             cases and bugs lurking in the serialization methods. Accordingly,
2073             starting with version 1.4.5, serialization exceptions or exceptions
2074             that occur while attempting to decode a response, will result in the
2075             raw response being returned to the caller. The idea being that getting
2076             something back that allows you figure out what to do with the response
2077             might be better than receiving an error.
2078              
2079             OTOH, you might want to see the error, report it, or possibly
2080             contribute to its resolution. You can prevent errors from being
2081             surpressed by setting the C<raise_serializtion_errors> to a true
2082             value. The default is I<false>.
2083              
2084             I<Throughout the rest of this documentation a request made using one
2085             of the classes created by the Botocore support scripts will be
2086             referred to as a B<Botocore request> or B<Botocore API>.>
2087              
2088             =over 5
2089              
2090             =item *
2091              
2092             =back
2093              
2094             =head1 ERRORS
2095              
2096             When an error is returned from an API request, an exception class
2097             (C<Amazon::API::Error>) will be raised if C<raise_error> has been set
2098             to a true value. Additionally, a detailed error message will be
2099             displayed if C<print_error> is set to true.
2100              
2101             See L<Amazon::API::Error> for more details.
2102              
2103             =head1 METHODS AND SUBROUTINES
2104              
2105             I<Reminder: You can mostly ignore this part of the documentation when
2106             you are leveraging Botocore to generate your API classes.>
2107              
2108             =head2 new
2109              
2110             new(options)
2111              
2112             All options are described below. C<options> can be a list of
2113             key/values or hash reference.
2114              
2115             =over 5
2116              
2117             =item action
2118              
2119             The API method. Normally, you would not set C<action> when you
2120             construct your object. It is set when you call the C<invoke_api>
2121             method or automatically set when you call one of the API stubs created
2122             for you.
2123              
2124             Example: 'PutEvents'
2125              
2126             =item api
2127              
2128             The name of the AWS service. See L</IMPLEMENTATION NOTES> for a
2129             detailed explanation of when to set this value.
2130              
2131             Example: 'AWSEvents'
2132              
2133             =item api_methods
2134              
2135             A reference to an array of method names for the API. The new
2136             constructor will automatically create methods for each of the method
2137             names listed in the array.
2138              
2139             The methods that are created for you are nothing more than stubs that
2140             call C<invoke_api>. The stub is a convenience for calling the
2141             C<invoke_api> method as shown below.
2142              
2143            
2144             my $api = Amazon::CloudWatch->new;
2145              
2146             $api->PutEvents($events);
2147              
2148             ...is equivalent to:
2149              
2150             $api->invoke_api->('PutEvents', $events);
2151              
2152             Consult the Amazon API documentation for the service to determine what
2153             parameters each action requires.
2154              
2155             =item aws_access_key_id
2156              
2157             Your AWS access key. Both the access key and secret access key are
2158             required if either is passed. If no credentials are passed, an attempt
2159             will be made to find credentials using L<Amazon::Credentials>. Note
2160             that you may need to pass C<token> as well if you are using temporary
2161             credentials.
2162              
2163             =item aws_secret_access_key
2164              
2165             Your AWS secret access key.
2166              
2167             =item content_type
2168              
2169             Default content for parameters passed to the C<invoke_api()>
2170             method. If you do not provide this value, a default content type will
2171             be selected based on the service's protocol.
2172              
2173             query => application/x-www-form-urlencoded
2174             rest-json => application/x-amz-json-1.1
2175             json => application/json
2176             rest-xml => application/xml
2177              
2178             =item credentials (optional)
2179              
2180             Accessing AWS services requires credentials with sufficient privileges
2181             to make programmatic calls to the APIs that support a service. This
2182             module supports three ways that you can provide those credentials.
2183              
2184             =over 10
2185              
2186             =item 1. Pass the credentials directly.
2187              
2188             Pass the values for the credentials (C<aws_access_key_id>,
2189             C<aws_secaret_access_key>, C<token>) when you call the C<new> method.
2190             A session token is typically required when you have assumed
2191             a role, you are using the EC2's instance role or a container's role.
2192              
2193             =item 2. Pass a class that will provide the credential keys.
2194              
2195             Pass a reference to a class that has I<getters> for the credential
2196             keys. The class should supply I<getters> for all three credential keys.
2197              
2198             Pass the reference to the class as C<credentials> in the constructor
2199             as shown here:
2200              
2201             my $api = Amazon::API->new(credentials => $credentials_class, ... );
2202              
2203             =item 3. Use the default C<Amazon::Credentials> class.
2204              
2205             If you do not explicitly pass credentials or do not pass a class that
2206             will supply credentials, the module will use the
2207             C<Amazon::Credentials> class that attempts to find credentials in the
2208             I<environment>, your I<credentials file(s)>, or the I<container or
2209             instance role>. See L<Amazon::Credentials> for more details.
2210              
2211             I<NOTE: The latter method of obtaining credentials is probably the
2212             easiest to use and provides the most succinct and secure way of
2213             obtaining credentials.>
2214              
2215             =back
2216              
2217             =item debug
2218              
2219             Set debug to a true value to enable debug messages. Debug mode will
2220             dump the request and response from all API calls. You can also set the
2221             environment variable DEBUG to enable debugging output. Set the debug
2222             value to '2' to increase the logging level.
2223              
2224             default: false
2225              
2226             =item decode_always
2227              
2228             Set C<decode_always> to a true value to return Perl objects from API
2229             method calls. The default is to return the raw output from the call.
2230             Typically, API calls will return either XML or JSON encoded objects.
2231             Setting C<decode_always> will attempt to decode the content based on
2232             the returned content type.
2233              
2234             default: false
2235              
2236             =item error
2237              
2238             The most recent result of an API call. C<undef> indicates no error was
2239             encountered the last time C<invoke_api> was called.
2240              
2241             =item http_method
2242              
2243             Sets the HTTP method used to invoke the API. Consult the AWS
2244             documentation for each service to determine the method utilized. Most
2245             of the more recent services utilize the POST method, however older
2246             services like SQS or S3 utilize GET or a combination of methods
2247             depending on the specific method being invoked.
2248              
2249             default: POST
2250              
2251             =item last_action
2252              
2253             The last method call invoked.
2254              
2255             =item print_error
2256              
2257             Setting this value to true enables a detailed error message containing
2258             the error code and any messages returned by the API when errors occur.
2259              
2260             default: true
2261              
2262             =item protocol
2263              
2264             One of 'http' or 'https'. Some Amazon services do not support https
2265             (yet).
2266              
2267             default: https
2268              
2269             =item raise_error
2270              
2271             Setting this value to true will raise an exception when errors
2272             occur. If you set this value to false you can inspect the C<error>
2273             attribute to determine the success or failure of the last method call.
2274              
2275             $api->invoke_api('ListQueues');
2276              
2277             if ( $api->get_error ) {
2278             ...
2279             }
2280              
2281             default: true
2282              
2283             =item region
2284              
2285             The AWS region. Pass an empty string if the service is a global
2286             service that does not require or want a region.
2287              
2288             default: $ENV{'AWS_REGION'}, $ENV{'AWS_DEFAULT_REGION'}, 'us-east-1'
2289              
2290             =item response
2291              
2292             The HTTP response from the last API call.
2293              
2294             =item service
2295              
2296             The AWS service name. Example: C<sqs>. This value is used as a prefix
2297             when constructing the the service URL (if not C<url> attribute is set).
2298              
2299             =item service_url_base
2300              
2301             Deprecated, use C<service>
2302              
2303             =item token
2304              
2305             Session token for assumed roles.
2306              
2307             =item url
2308              
2309             The service url. Example: https://events.us-east-1.amazonaws.com
2310              
2311             Typically this will be constructed for you based on the region and the
2312             service being invoked. However, you may want to set this manually if
2313             for example you are using a service like
2314             <LocalStack|https://localstack.cloud/> that mocks AWS API calls.
2315              
2316             my $api = Amazon::API->new(service => 's3', url => 'http://localhost:4566/');
2317              
2318             =item user_agent
2319              
2320             Your own user agent object. Using
2321             C<Furl>, if you have it avaiable may result in faster response.
2322              
2323             default: C<LWP::UserAgent>
2324              
2325             =item version
2326              
2327             Sets the API version. Some APIs require a version. Consult the
2328             documentation for individual services.
2329              
2330             =back
2331              
2332             =head2 invoke_api
2333              
2334             invoke_api(action, [parameters], [content-type], [headers]);
2335              
2336             or using named parameters...
2337              
2338             invoke_api({ action => args, ... } )
2339              
2340             Invokes the API with the provided parameters.
2341              
2342             =over 5
2343              
2344             =item action
2345              
2346             API name.
2347              
2348             =item parameters
2349              
2350             Parameters to send to the API. C<parameters> can be a scalar, a hash
2351             reference or an array reference. See the discussion below regarding
2352             C<content-type> and how C<invoke_api()> formats parameters before
2353             sending them as a payload to the API.
2354              
2355             You can use the C<param_n()> method to format query string arguments
2356             that are required to be in the I<param.n> notation. This is about the
2357             best documentation I have seen for that format. From the AWS
2358             documentation...
2359              
2360             =over 10
2361              
2362             Some actions take lists of parameters. These lists are specified using
2363             the I<param.n> notation. Values of n are integers starting from 1. For
2364             example, a parameter list with two elements looks like this:
2365              
2366             &AttributeName.1=first
2367              
2368             &AttributeName.2=second
2369              
2370             =back
2371              
2372             An example of using this notation is to set queue attributes when
2373             creating an SQS queue.
2374              
2375             my $attributes = { Attributes => [ { Name => 'VisibilityTimeout', Value => '100' } ] };
2376             my @sqs_attributes= Amazon::API::param_n($attributes);
2377              
2378             eval {
2379             $sqs->CreateQueue([ 'QueueName=foo', @sqs_attributes ]);
2380             };
2381              
2382             See L</param_n> for more details.
2383              
2384             =item content-type
2385              
2386             If you pass the C<content-type> parameter, it is assumed that the parameters are
2387             the actual payload to be sent in the request (unless the parameter is a reference).
2388              
2389             The C<parameters> will be converted to a JSON string if the
2390             C<parameters> value is a hash reference. If the C<parameters> value
2391             is an array reference it will be converted to a query string (Name=Value&...).
2392              
2393             To pass a query string, you should send an array of key/value
2394             pairs, or an array of scalars of the form C<Name=Value>.
2395              
2396             [ { Action => 'DescribeInstances' } ]
2397             [ 'Action=DescribeInstances' ]
2398              
2399             =item headers
2400              
2401             Array reference of key/value pairs representing additional headers to
2402             send with the request.
2403              
2404             =back
2405              
2406             =head2 decode_response
2407              
2408             Boolean that indicates whether or not to deserialize the most recent
2409             response from an invoked API based on the I<Content-Type> header
2410             returned. If there is no I<Content-Type> header, then the method will
2411             try to decode it first as a JSON string and then as an XML string. If
2412             both of those fail, the raw content is returned.
2413              
2414             You can enable or disable deserializing responses globally by setting
2415             the C<decode_always> attribute when you call the C<new> constructor.
2416              
2417             default: true
2418              
2419             By default, `Amazon::API` will retrieve all results for Botocore based
2420             API calls that require pagination. To turn this behavior off, set
2421             C<use_paginator> to a false value when you instantiate the API
2422             service.
2423              
2424             my $ec2 = Amazon::API->new(use_paginator => 0);
2425              
2426             You can also use the L</paginator> method to retrieve all results from Botocore requests that implement pagination.
2427              
2428             =head2 submit
2429              
2430             submit(options)
2431              
2432             I<This method is used internally by C<invoke_api> and normally should
2433             not be called by your applications.>
2434              
2435             C<options> is a reference to a hash of options:
2436              
2437             =over 5
2438              
2439             =item content
2440              
2441             Payload to send.
2442              
2443             =item content_type
2444              
2445             Content types we have seen used to send values to AWS APIs:
2446              
2447             application/json
2448             application/x-amz-json-1.0
2449             application/x-amz-json-1.1
2450             application/x-www-form-urlencoded
2451              
2452             Check the documentation for the individual APIs for the correct
2453             content type.
2454              
2455             =item headers
2456              
2457             Array reference of key/value pairs that represent additional headers
2458             to send with the request.
2459              
2460             =back
2461              
2462             =head1 EXPORTED METHODS
2463              
2464             =head2 get_api_service
2465              
2466             get_api_service(api, options)
2467              
2468             Convenience routine that will return an API instance.
2469              
2470             my $sqs = get_api_service 'sqs';
2471              
2472             Equivalent to:
2473              
2474             require Amazon::API::SQS;
2475              
2476             my $sqs = Amazon::API::SQS->new(%options);
2477              
2478             =over 5
2479              
2480             =item api
2481              
2482             The service name. Example: route53, sqs, sns
2483              
2484             =item options
2485              
2486             list of key/value pairs passed to the new constructor as options
2487              
2488             =back
2489              
2490             =head2 create_url_encoded_content
2491              
2492             create_urlencoded_content(parameters, action, version)
2493              
2494             Returns a URL encoded query string. C<parameters> can be any of SCALAR, ARRAY, or HASH. See below.
2495              
2496             =over 5
2497              
2498             =item parameters
2499              
2500             =over 5
2501              
2502             =item SCALAR
2503              
2504             Query string to encode (x=y&w=z..)
2505              
2506             =item ARRAY
2507              
2508             Can be one of:
2509              
2510             =over 5
2511              
2512             =item * Array of hashes where the keys are the query string variable and the value is the value of that variable
2513              
2514             =item * Array of strings of the form "x=y"
2515              
2516             =item * An array of key/value pairs - qw( x y w z )
2517              
2518             =back
2519              
2520             =item HASH
2521              
2522             Key/value pairs. If value is an array it is assumed to be a list of hashes
2523              
2524             =back
2525              
2526             =item action
2527              
2528             The method being called. For some query type APIs an Action query variable is required.
2529              
2530             =item version
2531              
2532             The WSDL version for the API. Some query type APIs require a Version query variable.
2533              
2534             =back
2535              
2536             =head2 paginator
2537              
2538             paginator(service, api, request)
2539              
2540             Returns an array containing the results of an API call that requires
2541             pagination,
2542              
2543              
2544             my $result = paginator($ec2, 'DescribeInstances', { MaxResults => 10 });
2545              
2546             =head2 param_n
2547              
2548             param_n(parameters)
2549              
2550             Format parameters in the "param.n" notation.
2551              
2552             C<parameters> should be a hash or array reference.
2553              
2554             A good example of a service that uses this notation is the
2555             I<SendMessageBatch> SQS API call.
2556              
2557             The sample request can be found here:
2558              
2559             L<SendMessageBatch|https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessageBatch.html>
2560              
2561              
2562             https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue/
2563             ?Action=SendMessageBatch
2564             &SendMessageBatchRequestEntry.1.Id=test_msg_001
2565             &SendMessageBatchRequestEntry.1.MessageBody=test%20message%20body%201
2566             &SendMessageBatchRequestEntry.2.Id=test_msg_002
2567             &SendMessageBatchRequestEntry.2.MessageBody=test%20message%20body%202
2568             &SendMessageBatchRequestEntry.2.DelaySeconds=60
2569             &SendMessageBatchRequestEntry.2.MessageAttribute.1.Name=test_attribute_name_1
2570             &SendMessageBatchRequestEntry.2.MessageAttribute.1.Value.StringValue=test_attribute_value_1
2571             &SendMessageBatchRequestEntry.2.MessageAttribute.1.Value.DataType=String
2572             &Expires=2020-05-05T22%3A52%3A43PST
2573             &Version=2012-11-05
2574             &AUTHPARAMS
2575              
2576             To produce this message you would pass the Perl object below to C<param_n()>:
2577              
2578             my $message = {
2579             SendMessageBatchRequestEntry => [
2580             { Id => 'test_msg_001',
2581             MessageBody => 'test message body 1'
2582             },
2583             { Id => 'test_msg_002',
2584             MessageBody => 'test message body 2',
2585             DelaySeconds => 60,
2586             MessageAttribute => [
2587             { Name => 'test_attribute_name_1',
2588             Value =>
2589             { StringValue => 'test_attribute_value_1', DataType => 'String' }
2590             }
2591             ]
2592             }
2593             ]
2594             };
2595              
2596             =head1 CAVEATS
2597              
2598             =over 5
2599              
2600             =item If you are calling an API that does not expect parameters (or all of
2601             them are optional and you do not pass a parameter) the default is to
2602             pass an empty hash..
2603              
2604             $cwe->ListRules();
2605              
2606             would be equivalent to...
2607              
2608             $cwe->ListRules({});
2609              
2610             I<CAUTION! This may not be what the API expects! Always consult
2611             the AWS API for the service you are are calling.>
2612              
2613             =back
2614              
2615             =head1 GORY DETAILS
2616              
2617             If you using the Botocore APIs you can probably ignore this section.
2618              
2619             =head2 X-Amz-Target
2620              
2621             Most of the newer AWS APIs are invoked as HTTP POST operations and
2622             accept a header C<X-Amz-Target> in lieu of the CGI parameter C<Action>
2623             to specify the specific API action. Some APIs also want the version in
2624             the target, some don't. There is sparse documentation about the
2625             nuances of using the REST interface I<directly> to call AWS APIs.
2626              
2627             When invoking an API, the class uses the C<api> value to indicate
2628             that the action should be set in the C<X-Amz-Target> header. We also
2629             check to see if the version needs to be attached to the action value
2630             as required by some APIs.
2631              
2632             if ( $self->get_api ) {
2633             if ( $self->get_version) {
2634             $self->set_target(sprintf('%s_%s.%s', $self->get_api, $self->get_version, $self->get_action));
2635             }
2636             else {
2637             $self->set_target(sprintf('%s.%s', $self->get_api, $self->get_action));
2638             }
2639              
2640             $request->header('X-Amz-Target', $self->get_target());
2641             }
2642              
2643             DynamoDB and KMS seem to be able to use this in lieu of query
2644             variables C<Action> and C<Version>, although again, there seems to be
2645             a lot of inconsistency (and sometimes flexibility) in the APIs.
2646             DynamoDB uses DynamoDB_YYYYMMDD.Action while KMS does not require the
2647             version that way and prefers TrentService.Action (with no version).
2648             There is no explanation in any of the documentations I have been able
2649             to find as to what "TrentService" might actually mean. Again, your
2650             best approach is to read Amazon's documentation and look at their
2651             sample requests for guidance. You can also look to the L<Botocore
2652             project|https://github.com/boto/botocore> for information regarding
2653             the service. Checkout the F<service-2.json> file within the
2654             sub-directory F<botocore/botocore/data/{api-version}/{service-name}>
2655             which contains details for each service.
2656              
2657             In general, the AWS API ecosystem is very organic. Each service seems
2658             to have its own rules and protocol regarding what the content of the
2659             headers should be.
2660              
2661             As noted, this generic API interface tries to make it possible to use
2662             one class C<Amazon::API> as a sort of gateway to the APIs. The most
2663             generic interface is simply sending query variables and not much else
2664             in the header. Services like EC2 conform to that protocol and can be
2665             invoked with relatively little fanfare.
2666              
2667             use Amazon::API;
2668             use Data::Dumper;
2669              
2670             print Dumper(
2671             Amazon::API->new(
2672             service => 'ec2',
2673             version => '2016-11-15'
2674             )->invoke_api('DescribeInstances')
2675             );
2676              
2677             Note that invoking the API in this fashion, C<version> is
2678             required.
2679              
2680             For more hints regarding how to call a particular service, you can use
2681             the AWS CLI with the --debug option. Invoke the service using the CLI
2682             and examine the payloads sent by the botocore library.
2683              
2684             =head2 Rolling a New API
2685              
2686             The L<Amazon::API> class will stub out methods for the API if you pass
2687             an array of API method names. The stub is equivalent to:
2688              
2689             sub some_api {
2690             my $self = shift;
2691              
2692             $self->invoke_api('SomeApi', @_);
2693             }
2694              
2695             Some will also be happy to know that the class will create an
2696             equivalent I<CamelCase> version of the method.
2697              
2698             As an example, here is a possible implementation of
2699             C<Amazon::CloudWatchEvents> that implements one of the API calls.
2700              
2701             package Amazon::CloudWatchEvents;
2702              
2703             use parent qw/Amazon::API/;
2704              
2705             sub new {
2706             my ($class, $options) = @_;
2707              
2708             my $self = $class->SUPER::new(
2709             { %{$options},
2710             api => 'AWSEvents',
2711             service => 'events',
2712             api_methods => [qw( ListRules )],
2713             }
2714             );
2715              
2716             return $self;
2717             }
2718              
2719             Then...
2720              
2721             use Data::Dumper;
2722              
2723             print Dumper(Amazon::CloudWatchEvents->new->ListRules({}));
2724              
2725             Of course, creating a class for the service is optional. It may be
2726             desirable however to create higher level and more convenient methods
2727             that aid the developer in utilizing a particular API.
2728              
2729             =head2 Overriding Methods
2730              
2731             Because the class does some symbol table munging, you cannot easily
2732             override the methods in the usual way.
2733              
2734             sub ListRules {
2735             my $self = shift;
2736             ...
2737             $self->SUPER::ListRules(@_)
2738             }
2739              
2740             Instead, you should re-implement the method as implemented by this
2741             class.
2742              
2743             sub ListRules {
2744             my $self = shift;
2745             ...
2746             $self->invoke_api('ListRules', @_);
2747             }
2748              
2749             =head2 Content-Type
2750              
2751             Yet another piece of evidence that suggests the I<organic> nature of
2752             the Amazon API ecosystem is their use of different C<Content-Type>
2753             headers. Some of the variations include:
2754              
2755             application/json
2756             application/x-amz-json-1.0
2757             application/x-amz-json-1.1
2758             application/x-www-form-urlencoded
2759              
2760             Accordingly, the C<invoke_api()> method can be passed the
2761             C<Content-Type> or will try to make its I<best guess> based on the
2762             service protocol. It guesses using the following decision tree:
2763              
2764             You can also set the default content type used for the calling service
2765             by passing the C<content_type> option to the constructor.
2766              
2767             $class->SUPER::new(
2768             content_type => 'application/x-amz-json-1.1',
2769             api => 'AWSEvents',
2770             service => 'events'
2771             );
2772              
2773             =head2 ADDITIONAL HINTS
2774              
2775             =over 5
2776              
2777             =item * Bad Request
2778              
2779             If you send the wrong headers or payload you're liable to get a 400
2780             Bad Request. You may also get other errors that can be misleading when
2781             you send incorrect parameters. When in doubt compare your requests to
2782             requests from the AWS CLI using the C<--debug> option.
2783              
2784             =over 10
2785              
2786             =item 1. Set the C<debug> option to true to see the request object and
2787             the response object from C<Amazon::API>.
2788              
2789             =item 2. Excecute the AWS CLI with the --debug option and compare the
2790             request and response with that of your calls.
2791              
2792             =back
2793              
2794             =item * Payloads
2795              
2796             Pay attention to the payloads that are required by each service. B<Do
2797             not> assume that sending nothing when you have no parameters to pass
2798             is correct. For example, the C<ListSecrets> API of SecretsManager
2799             requires at least an empty JSON object.
2800              
2801             $api->invoke_api('ListSecrets', {});
2802              
2803             Failure to send at least an empty JSON object will result in a 400
2804             response.
2805              
2806             =back
2807              
2808             =head1 VERSION
2809              
2810             This documentation refers to version 2.0.11 of C<Amazon::API>.
2811              
2812             =head1 DIAGNOSTICS
2813              
2814             To enable diagnostic output set C<debug> to a true value when calling
2815             the constructor. You can also set the C<DEBUG> environment variable to a
2816             true value to enable diagnostics.
2817              
2818             =head2 Logging
2819              
2820             By default L<Amazon::API> uses L<Log::Log4perl>'s Stealth loggers to
2821             log at the DEBUG and TRACE levels. Setting the environment variable
2822             DEBUG to some value or passing a true value for C<debug> in the
2823             constructor will trigger verbose logging.
2824              
2825             If you pass a logger to the constructor, C<Amazon::API> will attempt
2826             to use that if it has the appropriate logging level methods (error,
2827             warn, info, debug, trace). If L<Log::Log4perl> is unavailable and you
2828             do not pass a logger, logging is essentially disabled at any level.
2829              
2830             If, for some reason you set the enviroment variable DEBUG to a true
2831             value but do not want C<Amazon::API> to log messages you can turn off
2832             logging as shown below:
2833              
2834             my $ec2 = Amazon::API::EC2->new();
2835              
2836             $ec2->set_log_level('fatal');
2837              
2838             =head1 DEPENDENCIES
2839              
2840             =over 5
2841              
2842             =item * L<Amazon::Signature4>
2843              
2844             =item * L<Amazon::Credentials>
2845              
2846             =item * L<Class::Accessor::Fast>
2847              
2848             =item * L<Date::Format>
2849              
2850             =item * L<HTTP::Request>
2851              
2852             =item * L<JSON>
2853              
2854             =item * L<LWP::UserAgent>
2855              
2856             =item * L<List::Util>
2857              
2858             =item * L<ReadonlyX>
2859              
2860             =item * L<Scalar::Util>
2861              
2862             =item * L<Time::Local>
2863              
2864             =item * L<XML::Simple>
2865              
2866             =back
2867              
2868             ...and possibly others.
2869              
2870             =head1 INCOMPATIBILITIES
2871              
2872             =head1 BUGS AND LIMITATIONS
2873              
2874             This module has not been tested on Windows OS. Please report any
2875             issues found by opening an issue here:
2876              
2877             L<https://github.com/rlauer6/perl-Amazon-API/issues>
2878              
2879             =head1 FAQs
2880              
2881             =head2 Why should I use this module instead of PAWS?
2882              
2883             Maybe you shouldn't. PAWS is a community supported project and
2884             may be a better choice for most people. The programmers who created
2885             PAWS are luminaries in the pantheon of Perl programming (alliteration
2886             intended). If you want to use something a little lighter in the
2887             dependency department however, and perhaps only need to invoke a
2888             single service, L<Amazon:API> may be the right choice.
2889              
2890             =head2 Does it perform better than PAWS?
2891              
2892             Probably. But individual API calls to Amazon services have their own
2893             performance characteristics and idiosyncracies. The overhead
2894             introduced by this module and PAWS may be insignificant compared to
2895             the API performance itself. YMMV.
2896              
2897             =head2 Does this work for all APIs?
2898              
2899             I don't know. Probably not. Would love to hear your
2900             feedback. L<Amazon::API> has been developed based on my needs.
2901             Although I have tested it on many APIs, there may still be some cases
2902             that are not handled properly and I am still deciphering the nuances
2903             of flattening, boxing and serializing objects to send to Amazon APIs. The newer versions of this module using Botocore metadata have become increasingly reliable.
2904              
2905             Amazon APIs are not created equal, homogenous or invoked in the the
2906             same manner. Some accept parameters as a query strings, some
2907             parameters are embedded in the URI, some are sent as JSON payloads and
2908             others as XML. Content types for payloads are all over the map.
2909             Likewise with return values.
2910              
2911             Luckily, the Botocore metadata describes the protocols, parameters and
2912             return values for all APIs. The Botocore metadata is quite amazing
2913             actually. It is used to provide information to the Botocore library
2914             for calling any of the AWS services and even for creating
2915             documentation!
2916              
2917             L<Amazon::API> can use that information for creating the Perl classes
2918             that invoke each API but may not interpret the metadata correctly in
2919             all circumstances. Bugs almost certainly exist. :-( Did I mention help
2920             is welcome?
2921              
2922             If you want to use this to invoke S3 APIs, don't. I
2923             haven't tried it and I'm pretty sure it would not work anyway. There are
2924             modules designed specifically for S3; L<Amazon::S3>,
2925             L<Net::Amazon::S3>. Use them.
2926              
2927             =head2 Do I have to create the shape classes when I generate stubs for
2928             a service?
2929              
2930             Possibly. If you create stubs manually, then you do not need the shape
2931             classes. If you use the scripts provide to create the API stubs using
2932             Botocore metadata, then yes, you must create the shapes so that the
2933             Botocore API methods know how to serialize requests. Note that you can
2934             create the shape stubs using the Botocore metadata while not creating
2935             the API services. You might want to do that if you want a
2936             lean stub but want the benefits of using the shape stubs for
2937             serialization of the parameters.
2938              
2939             If you produce your stubs manually and do not create the shape stubs,
2940             then you must pass parameters to your API methods that are ready to be
2941             serialized by L<Amazon::API>. Creating data structures that will be
2942             serialized correctly however is done for you if you use the shape
2943             classes. For example, to create an SQS queue using the shape stubs,
2944             you can call the C<CreateQueue> API method as describe in the Botocore
2945             documentation.
2946              
2947             $sqs->CreateQueue(
2948             { QueueName => $queue_name,
2949             Tag => [ { Name => 'my-new-queue' }, { Env => 'dev' } ],
2950             Attribute => [ { VisibilityTimeout => 40 }, { DelaySeconds => 60 } ]
2951             });
2952              
2953             If you do not use the shape classes, then you must pass the arguments
2954             in the form that will eventually be serialized in the correct manner
2955             as a query string.
2956              
2957             $sqs->CreateQueue([
2958             'QueueName=foo',
2959             'Attributes.1.Value=100',
2960             'Attributes.1.Name=VisibilityTimeout',
2961             'Tag.1.Key=Name',
2962             'Tag.1.Value=foo',
2963             'Tag.2.Key=Env',
2964             'Tag.2.Value=dev'
2965             ]);
2966              
2967             =head2 This code is wonky. Why?
2968              
2969             This code has evolved over the years from being I<ONLY> a way to make
2970             RESTful calls to Amazon APIs to incorporating the use of the Botocore
2971             metadata. It I<was> one person's effort to create a somewhat
2972             lightweight interface to a few AWS APIs. As my own needs have changed
2973             and my knowledge of AWS services has increased it became obvious that
2974             using the Botocore metadata would yield far superior results. Still,
2975             I'm grokking the Botocore metadata intuitively without any help. My
2976             interpretations may be off. Help? Pull requests welcomed.
2977              
2978             =head2 How do I pass AWS credentials to the API?
2979              
2980             There is a bit of magic here as L<Amazon::API> will use
2981             L<Amazon::Credentials> transparently if you do not explicitly pass the
2982             credentials object. I've taken great pains to try to make the
2983             aforementioned module somewhat useful and I<secure>.
2984              
2985             See L<Amazon::Credentials>.
2986              
2987             =head2 Can I use more than one set of credentials to invoke different APIs?
2988              
2989             Yes. See L<Amazon::Credentials>.
2990              
2991             =head2 OMG! You update this too frequently!
2992              
2993             Yeah, there are a lot of bugs. Should I stop fixing them?
2994              
2995             =head2 Why are you using XML::Simple when it clearly says "DO NOT"?
2996              
2997             It's simple. And it seems easier to build than other modules that
2998             almost do the same thing.
2999              
3000             =head2 I tried to use this with XYZ service and it barfed. What do I do?
3001              
3002             That's not too surprising. There are several reasons why your call
3003             might not have worked.
3004              
3005             Did you enable debugging? Tracing?
3006              
3007             =over 5
3008              
3009             =item * You passed bad data
3010              
3011             Take a look at the data you passed, how was it serialized and
3012             ultimately passed to the API?
3013              
3014             =item * You didn't read the docs and passed bad data
3015              
3016             amazon-api -s sqs CreateQueue
3017              
3018             =item * The serialization of Amazon::API::Botocore::Shape is busted
3019              
3020             I have not tested every class generated for every API. You may find
3021             that some API methods return C<Bad Request> or do not serialize the
3022             results in the manner expected. Requests are serialized based on the
3023             metadata found in the Botocore project. There lie the clues for each
3024             API (protocol, end points, etc) and the models (shapes) for requests
3025             and response elements.
3026              
3027             Some requests require a query string, some an XML or JSON payload. The
3028             Botocore based API classes use the metadata to determine how to send a
3029             request and how to interpret the results. This module uses
3030             L<XML::Simple> or L<JSON> to parse the results. It then uses the
3031             C<Amazon::API::Bottocore::Serializer> to turn the parsed results into
3032             a Perl object that respresents the response shape. It's likely that
3033             this module has bugs and the shapes returned might not exactly match the
3034             return response from the C<aws> command line interface version.
3035              
3036             What should be returned by an API request method is documented in
3037             these modules and Amazon's CLI.
3038              
3039             perldoc Amazon::API::Botocore::Shape::EC2:DescribeInstancesRequest
3040              
3041             If you find this project's serializer deficient, please log an issue
3042             and I will attempt to address it.
3043              
3044             =back
3045              
3046             =head1 LICENSE AND COPYRIGHT
3047              
3048             This module is free software. It may be used, redistributed and/or
3049             modified under the same terms as Perl itself.
3050              
3051             =head1 SEE OTHER
3052              
3053             L<Amazon::Credentials>, L<Amazon::API::Error>, L<AWS::Signature4>, L<Amazon::API::Botocore>
3054              
3055             =head1 AUTHOR
3056              
3057             Rob Lauer - <rlauer6@comcast.net>
3058              
3059             =cut