| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package PawsX::FakeImplementation::Instance { |
|
2
|
4
|
|
|
4
|
|
639553
|
use Moose; |
|
|
4
|
|
|
|
|
36
|
|
|
3
|
4
|
|
|
4
|
|
33608
|
use Paws; |
|
|
4
|
|
|
|
|
754082
|
|
|
|
4
|
|
|
|
|
133
|
|
|
4
|
4
|
|
|
4
|
|
4602
|
use UUID qw/uuid/; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = '0.02'; |
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
with 'Paws::Net::CallerRole'; |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
sub caller_to_response {} |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
has api_class => ( |
|
13
|
|
|
|
|
|
|
is => 'ro', |
|
14
|
|
|
|
|
|
|
isa => 'Str', |
|
15
|
|
|
|
|
|
|
required => 1, |
|
16
|
|
|
|
|
|
|
); |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
has params => ( |
|
19
|
|
|
|
|
|
|
is => 'ro', |
|
20
|
|
|
|
|
|
|
isa => 'HashRef', |
|
21
|
|
|
|
|
|
|
default => sub { {} }, |
|
22
|
|
|
|
|
|
|
); |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
has instance => ( |
|
25
|
|
|
|
|
|
|
is => 'ro', |
|
26
|
|
|
|
|
|
|
lazy => 1, |
|
27
|
|
|
|
|
|
|
default => sub { |
|
28
|
|
|
|
|
|
|
my $self = shift; |
|
29
|
|
|
|
|
|
|
Paws->load_class($self->api_class); |
|
30
|
|
|
|
|
|
|
return $self->api_class->new(%{ $self->params }); |
|
31
|
|
|
|
|
|
|
} |
|
32
|
|
|
|
|
|
|
); |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
sub do_call { |
|
35
|
|
|
|
|
|
|
my ($self, $service, $call_obj) = @_; |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
my $uuid = uuid; |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
my ($method_name) = ($call_obj->meta->name =~ m/^Paws::.*?::(.*)$/); |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
my $return = eval { $self->instance->$method_name($call_obj) }; |
|
42
|
|
|
|
|
|
|
if ($@) { |
|
43
|
|
|
|
|
|
|
if (ref($@)) { |
|
44
|
|
|
|
|
|
|
if ($@->isa('Paws::Exception')){ |
|
45
|
|
|
|
|
|
|
$@->throw; |
|
46
|
|
|
|
|
|
|
} else { |
|
47
|
|
|
|
|
|
|
Paws::Exception->throw(message => "$@", code => 'InternalError', request_id => $uuid); |
|
48
|
|
|
|
|
|
|
} |
|
49
|
|
|
|
|
|
|
} else { |
|
50
|
|
|
|
|
|
|
Paws::Exception->throw(message => "$@", code => 'InternalError', request_id => $uuid); |
|
51
|
|
|
|
|
|
|
} |
|
52
|
|
|
|
|
|
|
} else { |
|
53
|
|
|
|
|
|
|
if (not defined $call_obj->_returns or $call_obj->_returns eq 'Paws::API::Response') { |
|
54
|
|
|
|
|
|
|
$return = Paws::API::Response->new(request_id => $uuid); |
|
55
|
|
|
|
|
|
|
} else { |
|
56
|
|
|
|
|
|
|
$return = $self->new_with_coercions($call_obj->_returns, %$return); |
|
57
|
|
|
|
|
|
|
} |
|
58
|
|
|
|
|
|
|
} |
|
59
|
|
|
|
|
|
|
return $return; |
|
60
|
|
|
|
|
|
|
} |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub new_with_coercions { |
|
63
|
|
|
|
|
|
|
my ($self, $class, %params) = @_; |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
Paws->load_class($class); |
|
66
|
|
|
|
|
|
|
my %p; |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
if ($class->does('Paws::API::StrToObjMapParser')) { |
|
69
|
|
|
|
|
|
|
my ($subtype) = ($class->meta->find_attribute_by_name('Map')->type_constraint =~ m/^HashRef\[(.*?)\]$/); |
|
70
|
|
|
|
|
|
|
if (my ($array_of) = ($subtype =~ m/^ArrayRef\[(.*?)\]$/)){ |
|
71
|
|
|
|
|
|
|
$p{ Map } = { map { $_ => [ map { $self->new_with_coercions("$array_of", %$_) } @{ $params{ $_ } } ] } keys %params }; |
|
72
|
|
|
|
|
|
|
} else { |
|
73
|
|
|
|
|
|
|
$p{ Map } = { map { $_ => $self->new_with_coercions("$subtype", %{ $params{ $_ } }) } keys %params }; |
|
74
|
|
|
|
|
|
|
} |
|
75
|
|
|
|
|
|
|
} elsif ($class->does('Paws::API::StrToNativeMapParser')) { |
|
76
|
|
|
|
|
|
|
$p{ Map } = { %params }; |
|
77
|
|
|
|
|
|
|
} else { |
|
78
|
|
|
|
|
|
|
foreach my $att (keys %params){ |
|
79
|
|
|
|
|
|
|
my $att_meta = $class->meta->find_attribute_by_name($att); |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
Moose->throw_error("$class doesn't have an $att") if (not defined $att_meta); |
|
82
|
|
|
|
|
|
|
my $type = $att_meta->type_constraint; |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
if ($type eq 'Bool') { |
|
85
|
|
|
|
|
|
|
$p{ $att } = ($params{ $att } == 1)?1:0; |
|
86
|
|
|
|
|
|
|
} elsif ($type eq 'Str' or $type eq 'Num' or $type eq 'Int') { |
|
87
|
|
|
|
|
|
|
$p{ $att } = $params{ $att }; |
|
88
|
|
|
|
|
|
|
} elsif ($type =~ m/^ArrayRef\[(.*?)\]$/){ |
|
89
|
|
|
|
|
|
|
my $subtype = "$1"; |
|
90
|
|
|
|
|
|
|
if ($subtype eq 'Str' or $subtype eq 'Str|Undef' or $subtype eq 'Num' or $subtype eq 'Int' or $subtype eq 'Bool') { |
|
91
|
|
|
|
|
|
|
$p{ $att } = $params{ $att }; |
|
92
|
|
|
|
|
|
|
} else { |
|
93
|
|
|
|
|
|
|
$p{ $att } = [ map { $self->new_with_coercions("$subtype", %{ $_ }) } @{ $params{ $att } } ]; |
|
94
|
|
|
|
|
|
|
} |
|
95
|
|
|
|
|
|
|
} elsif ($type->isa('Moose::Meta::TypeConstraint::Enum')){ |
|
96
|
|
|
|
|
|
|
$p{ $att } = $params{ $att }; |
|
97
|
|
|
|
|
|
|
} else { |
|
98
|
|
|
|
|
|
|
$p{ $att } = $self->new_with_coercions("$type", %{ $params{ $att } }); |
|
99
|
|
|
|
|
|
|
} |
|
100
|
|
|
|
|
|
|
} |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
return $class->new(%p); |
|
103
|
|
|
|
|
|
|
} |
|
104
|
|
|
|
|
|
|
} |
|
105
|
|
|
|
|
|
|
1; |
|
106
|
|
|
|
|
|
|
### main pod documentation begin ### |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=encoding UTF-8 |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=head1 NAME |
|
111
|
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance - A Paws extension to help you write fake AWS services |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
use Paws; |
|
117
|
|
|
|
|
|
|
use Paws::Net::MultiplexCaller; |
|
118
|
|
|
|
|
|
|
use PawsX::FakeImplementation::Instance; |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
my $paws = Paws->new( |
|
121
|
|
|
|
|
|
|
config => { |
|
122
|
|
|
|
|
|
|
caller => Paws::Net::MultiplexCaller->new( |
|
123
|
|
|
|
|
|
|
caller_for => { |
|
124
|
|
|
|
|
|
|
SQS => PawsX::FakeImplementation::Instance->new( |
|
125
|
|
|
|
|
|
|
api_class => 'FakeSQS', |
|
126
|
|
|
|
|
|
|
), |
|
127
|
|
|
|
|
|
|
} |
|
128
|
|
|
|
|
|
|
), |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
); |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
my $sqs = $paws->service('SQS', region => 'test'); |
|
133
|
|
|
|
|
|
|
my $new_queue = $sqs->CreateQueue(QueueName => 'MyQueue'); |
|
134
|
|
|
|
|
|
|
# the FakeSQS implementation has returned a $new_queue object just |
|
135
|
|
|
|
|
|
|
# like SQS would: |
|
136
|
|
|
|
|
|
|
# $new_queue->QueueUrl eq 'http://sqs.fake.amazonaws.com/123456789012/MyQueue' |
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
my $qurl = $result->QueueUrl; |
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
my $sent_mess = $sqs->SendMessage(MessageBody => 'Message 1', QueueUrl => $new_queue->QueueUrl); |
|
141
|
|
|
|
|
|
|
# $sent_mess->MessageId has a unique id |
|
142
|
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
my $rec_mess = $sqs->ReceiveMessage(QueueUrl => $qurl); |
|
144
|
|
|
|
|
|
|
# $rec_mess->Messages->[0]->Body eq 'Message 1' |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
When working heavily with AWS services you will sometimes have special needs: |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=over |
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=item Working on a plane (or in situations with limited connectivity) |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=item Testing your application |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=item Generating faults |
|
157
|
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=back |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance will help you create fake implementations for any service you |
|
161
|
|
|
|
|
|
|
want. You will be able to emulate any service of your choice, and implement the behaviour you need to |
|
162
|
|
|
|
|
|
|
be tested. |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance teams up with Paws::Net::MultiplexCaller to route the appropiate |
|
165
|
|
|
|
|
|
|
service calls to the appropiate fake implementation. See L<Paws::Net::MultiplexCaller> for more info |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=head1 Creating a fake implementation |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance defines an interface between the fake implementations and Paws so |
|
170
|
|
|
|
|
|
|
that it's easy to write these fake implementations. Here's a guide by example to do it: |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=head2 Create your fake implementation class |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
We start out creating a new class for our fake service: |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
package My::Fake::SQS; |
|
177
|
|
|
|
|
|
|
use Moose; |
|
178
|
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
1; |
|
180
|
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
Be careful with namespacing: take into account that your fake implementation could be partial |
|
182
|
|
|
|
|
|
|
(not a full emulation of the service), or your fake could have specialized behaviour like: |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=over |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
=item Only implementing a subset of calls |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=item Only implementing partial behaviour for some calls (feature incomplete) |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=item Implements some type of failure mode (fails one of every 10 calls to the service) |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=back |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
So please try to name your fakes accordingly: C<My::Fake::BasicSQS>, C<My::Fake::SQS::OutOfOrder>, |
|
195
|
|
|
|
|
|
|
C<My::FakeSQS::OnlyAdministrativeCalls>, C<My::FakeSQS::FailSomeCalls> |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
If you are going to write a generic fake, trying to closely emulate the AWS service, you can use |
|
198
|
|
|
|
|
|
|
the C<PawsX::FakeService::SERVICE_NAME> namespace. |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
Please have the behaviour of these generic fakes well tested and be willing to accept contributions |
|
201
|
|
|
|
|
|
|
from third parties to these fakes, as people will probably turn to those implementations by default |
|
202
|
|
|
|
|
|
|
to test services. Please document any already known differences between the real service and the |
|
203
|
|
|
|
|
|
|
fake service. |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=head2 Write a fake method |
|
206
|
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Just create a sub named like the method you want to fake in your fake service class. It will receive |
|
208
|
|
|
|
|
|
|
an object with the parameters that were passed to the service: |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub CreateQueue { |
|
211
|
|
|
|
|
|
|
my ($self, $params) = @_; |
|
212
|
|
|
|
|
|
|
# $params->QueueName holds what the user passed to |
|
213
|
|
|
|
|
|
|
# $sqs->CreateQueue(QueueName => '...'); |
|
214
|
|
|
|
|
|
|
return { QueueUrl => 'http://myqueue' }; |
|
215
|
|
|
|
|
|
|
} |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
The $params object in this case is a C<Paws::SQS::CreateQueue> object (that represents the parameters |
|
218
|
|
|
|
|
|
|
to the CreateQueue call. |
|
219
|
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
=head2 Return values |
|
221
|
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
The return of CreateQueue is a hashref that contains the attributes for inflating a C<Paws::SQS::CreateQueueResult>. |
|
223
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance will convert the hashref to the appropiate return object that the calling |
|
224
|
|
|
|
|
|
|
code is expecting. |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
sub CreateQueue { |
|
227
|
|
|
|
|
|
|
return { QueueUrl => 'http://myqueue' }; |
|
228
|
|
|
|
|
|
|
} |
|
229
|
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
from the fake implementation will be received in the calling side as always: |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
my $return = $sqs->CreateQueue(QueueName => 'x'); |
|
233
|
|
|
|
|
|
|
print $return->QueueUrl; # http://myqueue |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=head2 Controlled exceptions |
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
If the code inside a fake implementation throws or returns a Paws::Exception, the "user code" will recieve the |
|
238
|
|
|
|
|
|
|
Paws::Exception just like if Paws had generated it. |
|
239
|
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
sub CreateQueue { |
|
241
|
|
|
|
|
|
|
Paws::Exception->throw(message => 'The name is duplicate', code => 'DuplicateName'); |
|
242
|
|
|
|
|
|
|
} |
|
243
|
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
This helps emulate error conditions just like Paws/AWS returns them |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=head2 Uncontrolled exceptions |
|
247
|
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
If the code inside a fake implementation dies, PawsX::FakeImplementation::Instance will wrap it inside a generic |
|
249
|
|
|
|
|
|
|
Paws::Exception object with code 'InternalError' and the exception as the message. Paws' contract with the |
|
250
|
|
|
|
|
|
|
outside world is to throw Paws::Exception objects in case of problems, so PawsX::FakeImplementation::Instance |
|
251
|
|
|
|
|
|
|
tries to not bubble non-Paws-compliant exceptions. |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=head2 Instance Storage |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
PawsX::FakeImplementation::Instance instances your object one time only, and after that routes method calls |
|
256
|
|
|
|
|
|
|
to your object. This lets you use an attribute to store data for the lifetime of your object, and use it |
|
257
|
|
|
|
|
|
|
as "storage". A queue service, for example could use |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
has _queue => (is => 'ro', isa => 'ArrayRef'); |
|
260
|
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
as storage for the messages it enqueues. Every call to the fake services methods will see $self->_queue, and |
|
262
|
|
|
|
|
|
|
be able to manipulate it: |
|
263
|
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
sub ReceiveMessage { |
|
265
|
|
|
|
|
|
|
my ($self, $params) = @_; |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
my $message = pop @{ $self->_queue }; |
|
268
|
|
|
|
|
|
|
return { Messages => $message }; |
|
269
|
|
|
|
|
|
|
} |
|
270
|
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=head2 Externally configurable attributes |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
If you want your fake service to be configurable in some way, you can specify an attribute in your |
|
274
|
|
|
|
|
|
|
fake service class. |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
package My::Fake::FailingSQS; |
|
277
|
|
|
|
|
|
|
use Moose; |
|
278
|
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
has failing_call_ratio => (is => 'ro', isa => 'Num', default => 0.5); |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
sub CreateQueue { |
|
282
|
|
|
|
|
|
|
my ($self, $params) = @_; |
|
283
|
|
|
|
|
|
|
die "Strange error" if (rand() < $self->failing_call_ratio); |
|
284
|
|
|
|
|
|
|
} |
|
285
|
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
1; |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
The user of the fake service can then initialize it in the following way: |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
my $paws = Paws->new( |
|
291
|
|
|
|
|
|
|
config => { |
|
292
|
|
|
|
|
|
|
caller => Paws::Net::MultiplexCaller->new( |
|
293
|
|
|
|
|
|
|
caller_for => { |
|
294
|
|
|
|
|
|
|
SQS => PawsX::FakeImplementation::Instance->new( |
|
295
|
|
|
|
|
|
|
api_class => 'My::Fake::FailingSQS', |
|
296
|
|
|
|
|
|
|
params => { |
|
297
|
|
|
|
|
|
|
failing_call_ratio => 1 # all calls fail |
|
298
|
|
|
|
|
|
|
} |
|
299
|
|
|
|
|
|
|
), |
|
300
|
|
|
|
|
|
|
} |
|
301
|
|
|
|
|
|
|
), |
|
302
|
|
|
|
|
|
|
} |
|
303
|
|
|
|
|
|
|
); |
|
304
|
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
It's recommended that your attribute either has a default (for easy usage), or declares itself as required |
|
306
|
|
|
|
|
|
|
as to guide the consumer of the fake service what parameters need to be passed. |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=head1 AUTHOR |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
Jose Luis Martinez |
|
311
|
|
|
|
|
|
|
CPAN ID: JLMARTIN |
|
312
|
|
|
|
|
|
|
CAPSiDE |
|
313
|
|
|
|
|
|
|
jlmartinez@capside.com |
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
316
|
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
L<Paws> |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
L<Paws::Net::MultiplexCaller> |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
L<https://github.com/pplu/aws-sdk-perl> |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=head1 BUGS and SOURCE |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
The source code is located here: L<https://github.com/pplu/pawsx-fakeimplementation-instance> |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
Please report bugs to: L<https://github.com/pplu/pawsx-fakeimplementation-instance/issues> |
|
328
|
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
=head1 COPYRIGHT and LICENSE |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
Copyright (c) 2017 by CAPSiDE |
|
332
|
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
This code is distributed under the Apache 2 License. The full text of the license can be found in the LICENSE file included with this module. |