File Coverage

blib/lib/Net/Amazon/S3/Bucket.pm
Criterion Covered Total %
statement 168 187 89.8
branch 42 68 61.7
condition 10 28 35.7
subroutine 26 27 96.3
pod 17 18 94.4
total 263 328 80.1


line stmt bran cond sub pod time code
1             package Net::Amazon::S3::Bucket;
2             # ABSTRACT: convenience object for working with Amazon S3 buckets
3             $Net::Amazon::S3::Bucket::VERSION = '0.99';
4 99     99   797 use Moose 0.85;
  99         2168  
  99         1092  
5 99     99   663267 use MooseX::StrictConstructor 0.16;
  99         2002  
  99         632  
6 99     99   327541 use Carp;
  99         252  
  99         6697  
7 99     99   56860 use File::stat;
  99         697912  
  99         501  
8 99     99   58592 use IO::File 1.14;
  99         865306  
  99         268746  
9              
10             has 'account' => (
11             is => 'ro',
12             isa => 'Net::Amazon::S3',
13             required => 1,
14             handles => [qw[ err errstr ]],
15             );
16             has 'bucket' => ( is => 'ro', isa => 'Str', required => 1 );
17             has 'creation_date' => ( is => 'ro', isa => 'Maybe[Str]', required => 0 );
18              
19             has 'region' => (
20             is => 'ro',
21             lazy => 1,
22             predicate => 'has_region',
23             default => sub {
24             return $_[0]->account->vendor->guess_bucket_region ($_[0]);
25             },
26             );
27              
28             __PACKAGE__->meta->make_immutable;
29              
30             # returns bool
31             sub add_key {
32 17     17 1 44 my $self = shift;
33 17         174 my %args = Net::Amazon::S3::Utils->parse_arguments (\@_, ['key', 'value']);
34              
35 17         72 my $key = delete $args{key};
36 17         38 my $value = delete $args{value};
37              
38 17 100       68 if ( ref($value) eq 'SCALAR' ) {
39 3   33     121 $args{'Content-Length'} ||= -s $$value;
40 3         12 $value = _content_sub($$value);
41             } else {
42 14   33     79 $args{'Content-Length'} ||= length $value;
43             }
44              
45 17         36 my $acl;
46 17 100       56 $acl = delete $args{acl_short} if exists $args{acl_short};
47 17 100       63 $acl = delete $args{acl} if exists $args{acl};
48              
49 17         41 my $encryption = delete $args{encryption};
50 17         84 my %headers = %args;
51              
52             # we may get a 307 redirect; ask server to signal 100 Continue
53             # before reading the content from CODE reference (_content_sub)
54 17 100       56 $headers{expect} = '100-continue' if ref $value;
55              
56 17         120 my $response = $self->_perform_operation (
57             'Net::Amazon::S3::Operation::Object::Add',
58              
59             key => $key,
60             value => $value,
61             (acl => $acl) x!! defined $acl,
62             ((encryption => $encryption) x!! defined $encryption),
63             headers => \%headers,
64             );
65              
66 10         383 return $response->is_success;
67             }
68              
69             sub add_key_filename {
70 2     2 1 5 my $self = shift;
71 2         16 my %args = Net::Amazon::S3::Utils->parse_arguments (\@_, ['key', 'value']);
72 2         7 $args{value} = \ delete $args{value};
73              
74 2         10 return $self->add_key (%args);
75             }
76              
77             sub copy_key {
78 6     6 1 18 my $self = shift;
79 6         56 my %args = Net::Amazon::S3::Utils->parse_arguments (\@_, ['key', 'source' ]);
80              
81 6         25 my $key = delete $args{key};
82 6         15 my $source = delete $args{source};
83              
84 6         14 my $acl_short;
85 6 50       18 if (%args) {
86 6 50       21 if ( $args{acl_short} ) {
87 6         17 $acl_short = $args{acl_short};
88 6         11 delete $args{acl_short};
89             }
90 6   50     62 $args{Net::Amazon::S3::Constants->HEADER_METADATA_DIRECTIVE} ||= 'REPLACE';
91             }
92              
93 6         31 $args{Net::Amazon::S3::Constants->HEADER_COPY_SOURCE} = $source;
94              
95 6         18 my $encryption = delete $args{encryption};
96              
97 6         220 my $acct = $self->account;
98 6         44 my $response = $self->_perform_operation (
99             'Net::Amazon::S3::Operation::Object::Add',
100              
101             value => '',
102             key => $key,
103             acl_short => $acl_short,
104             (encryption => $encryption) x!! defined $encryption,
105             headers => \%args,
106             );
107              
108 0 0       0 return unless $response->is_success;
109             }
110              
111             sub edit_metadata {
112 2     2 1 7 my $self = shift;
113 2         18 my %args = Net::Amazon::S3::Utils->parse_arguments_with_object (\@_);
114              
115 2         12 my $key = delete $args{key};
116 2 50       8 croak "Need some metadata to change" unless %args;
117              
118 2         72 return $self->copy_key (
119             key => $key,
120             source => "/" . $self->bucket . "/" . $key,
121             %args,
122             );
123             }
124              
125             sub head_key {
126 9     9 1 24 my $self = shift;
127 9         107 my %args = Net::Amazon::S3::Utils->parse_arguments_with_object (\@_);
128              
129 9         62 return $self->get_key (%args, method => 'HEAD', filename => undef);
130             }
131              
132             sub query_string_authentication_uri {
133 4     4 1 10 my $self = shift;
134 4         39 my %args = Net::Amazon::S3::Utils->parse_arguments (\@_, ['key', 'expires_at']);
135              
136 4 100       18 $args{method} = 'GET' unless exists $args{method};
137              
138             my $request = Net::Amazon::S3::Operation::Object::Fetch::Request->new (
139             s3 => $self->account,
140             bucket => $self,
141             key => $args{key},
142             method => $args{method},
143 4         145 );
144              
145 4         24 return $request->query_string_authentication_uri ($args{expires_at});
146             }
147              
148             sub get_key {
149 23     23 1 63 my $self = shift;
150 23         209 my %args = Net::Amazon::S3::Utils->parse_arguments (\@_, ['key', 'method', 'filename']);
151              
152 3         14 $args{filename} = ${ delete $args{filename} }
153 23 100       123 if ref $args{filename};
154              
155 23   50     80 $args{method} ||= 'GET';
156              
157             my $response = $self->_perform_operation (
158             'Net::Amazon::S3::Operation::Object::Fetch',
159              
160             filename => $args{filename},
161             (range => $args{range}) x defined $args{range},
162              
163             key => $args{key},
164             method => $args{method},
165 23         184 );
166              
167 7 100       64 return unless $response->is_success;
168 3         92 my $etag = $response->etag;
169              
170 3         13 my $return;
171 3         81 foreach my $header ($response->headers->header_field_names) {
172 15         844 $return->{ lc $header } = $response->header ($header);
173             }
174 3   100     169 $return->{content_length} = $response->content_length || 0;
175 3         147 $return->{content_type} = $response->content_type;
176 3         95 $return->{etag} = $etag;
177 3         45 $return->{value} = $response->content;
178              
179 3         111 return $return;
180              
181             }
182              
183             sub get_key_filename {
184 1     1 1 3 my $self = shift;
185 1         10 my %args = Net::Amazon::S3::Utils->parse_arguments (\@_, ['key', 'method', 'filename']);
186              
187 1         6 $args{filename} = \ delete $args{filename};
188 1         6 return $self->get_key (%args);
189             }
190              
191             # returns bool
192             sub delete_multi_object {
193 5     5 0 19 my $self = shift;
194 5         19 my @objects = @_;
195 5 50       20 return unless( scalar(@objects) );
196              
197             # Since delete can handle up to 1000 requests, be a little bit nicer
198             # and slice up requests and also allow keys to be strings
199             # rather than only objects.
200 5         13 my $last_result;
201 5         20 while (scalar(@objects) > 0) {
202             my $response = $self->_perform_operation (
203             'Net::Amazon::S3::Operation::Objects::Delete',
204              
205             keys => [
206 5 50       28 map { ref ($_) ? $_->key : $_ }
  11 50       51  
207             splice @objects, 0, ((scalar(@objects) > 1000) ? 1000 : scalar(@objects))
208             ]
209             );
210              
211 4 100       45 return unless $response->is_success;
212             }
213              
214 1         239 return 1;
215             }
216              
217             sub delete_key {
218 9     9 1 25 my $self = shift;
219 9         99 my %args = Net::Amazon::S3::Utils->parse_arguments_with_object (\@_);
220              
221 9 50 33     64 croak 'must specify key' unless defined $args{key} && length $args{key};
222              
223 9         42 my $response = $self->_perform_operation (
224             'Net::Amazon::S3::Operation::Object::Delete',
225             %args,
226             );
227              
228 5         53 return $response->is_success;
229             }
230              
231             sub delete_bucket {
232 1     1 1 3 my $self = shift;
233 1         33 return $self->account->delete_bucket (bucket => $self, @_);
234             }
235              
236             sub list {
237 2     2 1 5 my $self = shift;
238 2         15 my %args = Net::Amazon::S3::Utils->parse_arguments (\@_);
239              
240 2         69 return $self->account->list_bucket ($self, %args);
241             }
242              
243             sub list_all {
244 2     2 1 5 my $self = shift;
245 2         11 my %args = Net::Amazon::S3::Utils->parse_arguments (\@_);
246              
247 2         65 return $self->account->list_bucket_all ($self, %args);
248             }
249              
250             sub get_acl {
251 14     14 1 40 my $self = shift;
252 14         123 my %args = Net::Amazon::S3::Utils->parse_arguments_with_object (\@_);
253              
254 14         47 my $response;
255 14 100       53 if (defined $args{key}) {
256 8         37 $response = $self->_perform_operation (
257             'Net::Amazon::S3::Operation::Object::Acl::Fetch',
258             %args,
259             );
260             } else {
261 6         16 delete $args{key};
262 6         28 $response = $self->_perform_operation (
263             'Net::Amazon::S3::Operation::Bucket::Acl::Fetch',
264             %args,
265             );
266             }
267              
268 5 100       50 return unless $response->is_success;
269 2         70 return $response->content;
270             }
271              
272             sub set_acl {
273 24     24 1 54 my $self = shift;
274 24         180 my %args = Net::Amazon::S3::Utils->parse_arguments_with_object (\@_);
275              
276 24         78 my $response;
277 24 100       79 if (defined $args{key}) {
278 13         52 $response = $self->_perform_operation (
279             'Net::Amazon::S3::Operation::Object::Acl::Set',
280             %args,
281             );
282             } else {
283 11         26 delete $args{key};
284 11         48 $response = $self->_perform_operation (
285             'Net::Amazon::S3::Operation::Bucket::Acl::Set',
286             %args,
287             );
288             }
289              
290 18 100       1414 return unless $response->is_success;
291 11         276 return 1;
292             }
293              
294             sub get_location_constraint {
295 2     2 1 4 my $self = shift;
296 2         18 my %args = Net::Amazon::S3::Utils->parse_arguments (\@_);
297              
298 2         8 my $response = $self->_perform_operation (
299             'Net::Amazon::S3::Operation::Bucket::Location',
300             %args,
301             );
302              
303 0 0       0 return unless $response->is_success;
304 0         0 return $response->location;
305             }
306              
307             sub add_tags {
308 14     14 1 42 my $self = shift;
309 14 100       55 my %args = @_ == 1 ? %{ $_[0] } : @_;
  11         43  
310              
311 14         37 my $response;
312 14 100       44 if (defined $args{key}) {
313 8         31 $response = $self->_perform_operation (
314             'Net::Amazon::S3::Operation::Object::Tags::Add',
315             %args,
316             );
317             } else {
318 6         12 delete $args{key};
319 6         30 $response = $self->_perform_operation (
320             'Net::Amazon::S3::Operation::Bucket::Tags::Add',
321             %args,
322             );
323             }
324              
325 8         64 return $response->is_success;
326             }
327              
328             sub delete_tags {
329 15     15 1 33 my $self = shift;
330 15         153 my %args = Net::Amazon::S3::Utils->parse_arguments_with_object (\@_);
331              
332 15         60 my $response;
333 15 100       57 if (defined $args{key}) {
334 9         131 $response = $self->_perform_operation (
335             'Net::Amazon::S3::Operation::Object::Tags::Delete',
336             %args,
337             );
338             } else {
339 6         17 delete $args{key};
340 6         26 $response = $self->_perform_operation (
341             'Net::Amazon::S3::Operation::Bucket::Tags::Delete',
342             %args,
343             );
344             }
345              
346 9         75 return $response->is_success;
347             }
348              
349             sub _content_sub {
350 3     3   8 my $filename = shift;
351 3         15 my $stat = stat($filename);
352 3         618 my $remaining = $stat->size;
353 3   50     60 my $blksize = $stat->blksize || 4096;
354              
355 3 50 33     91 croak "$filename not a readable file with fixed size"
      33        
356             unless -r $filename and ( -f _ || $remaining );
357 3 50       26 my $fh = IO::File->new( $filename, 'r' )
358             or croak "Could not open $filename: $!";
359 3         375 $fh->binmode;
360              
361             return sub {
362 0     0   0 my $buffer;
363              
364             # upon retries the file is closed and we must reopen it
365 0 0       0 unless ( $fh->opened ) {
366 0 0       0 $fh = IO::File->new( $filename, 'r' )
367             or croak "Could not open $filename: $!";
368 0         0 $fh->binmode;
369 0         0 $remaining = $stat->size;
370             }
371              
372             # warn "read remaining $remaining";
373 0 0       0 unless ( my $read = $fh->read( $buffer, $blksize ) ) {
374              
375             # warn "read $read buffer $buffer remaining $remaining";
376 0 0 0     0 croak
377             "Error while reading upload content $filename ($remaining remaining) $!"
378             if $! and $remaining;
379              
380             # otherwise, we found EOF
381 0 0       0 $fh->close
382             or croak "close of upload content $filename failed: $!";
383 0   0     0 $buffer ||= ''
384             ; # LWP expects an emptry string on finish, read returns 0
385             }
386 0         0 $remaining -= length($buffer);
387 0         0 return $buffer;
388 3         43 };
389             }
390              
391             sub _head_region {
392 1     1   3 my ($self) = @_;
393              
394 1 50       43 my $protocol = $self->account->secure ? 'https' : 'http';
395 1         28 my $host = $self->account->host;
396 1         31 my $path = $self->bucket;
397 1         8 my @retry = (1, 2, (4) x 8);
398              
399 1 50       27 if ($self->account->use_virtual_host) {
400 1         5 $host = "$path.$host";
401 1         4 $path = '';
402             }
403              
404 1         6 my $request_uri = "${protocol}://${host}/$path";
405 1         6 while (@retry) {
406 1         12 my $request = HTTP::Request->new (HEAD => $request_uri);
407              
408             # Disable redirects
409 1         8729 my $requests_redirectable = $self->account->ua->requests_redirectable;
410 1         47 $self->account->ua->requests_redirectable( [] );
411              
412 1         36 my $response = $self->account->ua->request ($request);
413              
414 1         1681 $self->account->ua->requests_redirectable( $requests_redirectable );
415              
416 1 50       73 return $response->header (Net::Amazon::S3::Constants->HEADER_BUCKET_REGION)
417             if $response->header (Net::Amazon::S3::Constants->HEADER_BUCKET_REGION);
418              
419 0         0 print STDERR "Invalid bucket head response; $request_uri\n";
420 0         0 print STDERR $response->as_string;
421              
422 0         0 sleep shift @retry;
423             }
424              
425 0         0 die "Cannot determine bucket region; bucket=${\ $self->bucket }";
  0         0  
426             }
427              
428             sub _perform_operation {
429 129     129   572 my ($self, $operation, %params) = @_;
430              
431 129         4623 $self->account->_perform_operation ($operation => (
432             bucket => $self,
433             %params,
434             ));
435             }
436              
437             1;
438              
439             __END__
440              
441             =pod
442              
443             =encoding UTF-8
444              
445             =head1 NAME
446              
447             Net::Amazon::S3::Bucket - convenience object for working with Amazon S3 buckets
448              
449             =head1 VERSION
450              
451             version 0.99
452              
453             =head1 SYNOPSIS
454              
455             use Net::Amazon::S3;
456              
457             my $bucket = $s3->bucket("foo");
458              
459             ok($bucket->add_key("key", "data"));
460             ok($bucket->add_key("key", "data", {
461             content_type => "text/html",
462             'x-amz-meta-colour' => 'orange',
463             }));
464              
465             # Enable server-side encryption
466             ok($bucket->add_key("key", "data", {
467             encryption => 'AES256',
468             }));
469              
470             # the err and errstr methods just proxy up to the Net::Amazon::S3's
471             # objects err/errstr methods.
472             $bucket->add_key("bar", "baz") or
473             die $bucket->err . $bucket->errstr;
474              
475             # fetch a key
476             $val = $bucket->get_key("key");
477             is( $val->{value}, 'data' );
478             is( $val->{content_type}, 'text/html' );
479             is( $val->{etag}, 'b9ece18c950afbfa6b0fdbfa4ff731d3' );
480             is( $val->{'x-amz-meta-colour'}, 'orange' );
481              
482             # fetch a part of the key
483             $val = $bucket->get_key("key", { range => "bytes=1024-10240" });
484              
485             # returns undef on missing or on error (check $bucket->err)
486             is(undef, $bucket->get_key("non-existing-key"));
487             die $bucket->errstr if $bucket->err;
488              
489             # fetch a key's metadata
490             $val = $bucket->head_key("key");
491             is( $val->{value}, '' );
492             is( $val->{content_type}, 'text/html' );
493             is( $val->{etag}, 'b9ece18c950afbfa6b0fdbfa4ff731d3' );
494             is( $val->{'x-amz-meta-colour'}, 'orange' );
495              
496             # delete a key
497             ok($bucket->delete_key($key_name));
498             ok(! $bucket->delete_key("non-exist-key"));
499              
500             # delete the entire bucket (Amazon requires it first be empty)
501             $bucket->delete_bucket;
502              
503             =head1 DESCRIPTION
504              
505             This module represents an S3 bucket. You get a bucket object
506             from the Net::Amazon::S3 object.
507              
508             =head1 METHODS
509              
510             =head2 new
511              
512             Create a new bucket object. Expects a hash containing these two arguments:
513              
514             =over
515              
516             =item bucket
517              
518             =item account
519              
520             =back
521              
522             =head2 add_key
523              
524             Takes three positional parameters:
525              
526             =over
527              
528             =item key
529              
530             =item value
531              
532             =item configuration
533              
534             A hash of configuration data for this key.
535              
536             =over
537              
538             =item acl
539              
540             =item encryption
541              
542             =item any additional HTTP header
543              
544             =back
545              
546             See L<Net::Amazon::S3::Operation::Object::Add::Request> for details
547              
548             =back
549              
550             Returns a boolean.
551              
552             =head2 add_key_filename
553              
554             Use this to upload a large file to S3. Takes three positional parameters:
555              
556             =over
557              
558             =item key
559              
560             =item filename
561              
562             =item configuration
563              
564             A hash of configuration data for this key. (See synopsis);
565              
566             =back
567              
568             Returns a boolean.
569              
570             =head2 copy_key
571              
572             Creates (or replaces) a key, copying its contents from another key elsewhere in S3.
573             Takes the following parameters:
574              
575             =over
576              
577             =item key
578              
579             The key to (over)write
580              
581             =item source
582              
583             Where to copy the key from. Should be in the form C</I<bucketname>/I<keyname>>/.
584              
585             =item conf
586              
587             Optional configuration hash. If present and defined, the configuration (ACL
588             and headers) there will be used for the new key; otherwise it will be copied
589             from the source key.
590              
591             =back
592              
593             =head2 edit_metadata
594              
595             Changes the metadata associated with an existing key. Arguments:
596              
597             =over
598              
599             =item key
600              
601             The key to edit
602              
603             =item conf
604              
605             The new configuration hash to use
606              
607             =back
608              
609             =head2 head_key KEY
610              
611             Takes the name of a key in this bucket and returns its configuration hash
612              
613             =head2 query_string_authentication_uri KEY, EXPIRES_AT
614              
615             my $uri = $bucket->query_string_authentication_uri (
616             key => 'foo',
617             expires_at => time + 3_600, # valid for one hour
618             );
619              
620             my $uri = $bucket->query_string_authentication_uri (
621             key => 'foo',
622             expires_at => time + 3_600,
623             method => 'PUT',
624             );
625              
626             Returns uri presigned with your credentials.
627              
628             When used with Signature V4 you have to specify also HTTP method this
629             presigned uri will be used for (default: C<GET>)
630              
631             Method provides authenticated uri only for direct object operations.
632              
633             Method follows API's L</"CALLING CONVENTION">.
634              
635             Recognized positional arguments (mandatory).
636              
637             =over
638              
639             =item key
640              
641             =item expires_at
642              
643             Expiration time (epoch time).
644              
645             =back
646              
647             Optional arguments
648              
649             =over
650              
651             =item method
652              
653             Default: C<GET>
654              
655             Intended HTTP method this uri will be presigned for.
656              
657             Signature V2 doesn't use it but Signature V4 does.
658              
659             See L<https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObject.html>
660              
661             =back
662              
663             =head2 get_key $key_name [$method]
664              
665             Takes a key name and an optional HTTP method (which defaults to C<GET>.
666             Fetches the key from AWS.
667              
668             On failure:
669              
670             Returns undef on missing content, throws an exception (dies) on server errors.
671              
672             On success:
673              
674             Returns a hashref of { content_type, etag, value, @meta } on success. Other
675             values from the server are there too, with the key being lowercased.
676              
677             =head2 get_key_filename $key_name $method $filename
678              
679             Use this to download large files from S3. Takes a key name and an optional
680             HTTP method (which defaults to C<GET>. Fetches the key from AWS and writes
681             it to the filename. THe value returned will be empty.
682              
683             On failure:
684              
685             Returns undef on missing content, throws an exception (dies) on server errors.
686              
687             On success:
688              
689             Returns a hashref of { content_type, etag, value, @meta } on success
690              
691             =head2 delete_key $key_name
692              
693             Removes C<$key> from the bucket. Forever. It's gone after this.
694              
695             Returns true on success and false on failure
696              
697             =head2 delete_bucket
698              
699             Delete the current bucket object from the server. Takes no arguments.
700              
701             Fails if the bucket has anything in it.
702              
703             This is an alias for C<< $s3->delete_bucket($bucket) >>
704              
705             =head2 list
706              
707             List all keys in this bucket.
708              
709             see L<Net::Amazon::S3/list_bucket> for documentation of this method.
710              
711             =head2 list_all
712              
713             List all keys in this bucket without having to worry about
714             'marker'. This may make multiple requests to S3 under the hood.
715              
716             see L<Net::Amazon::S3/list_bucket_all> for documentation of this method.
717              
718             =head2 get_acl
719              
720             Takes one optional positional parameter
721              
722             =over
723              
724             =item key (optional)
725              
726             If no key is specified, it returns the acl for the bucket.
727              
728             =back
729              
730             Returns an acl in XML format.
731              
732             =head2 set_acl
733              
734             Takes a configuration hash_ref containing:
735              
736             =over
737              
738             =item acl_xml (cannot be used in conjunction with acl_short)
739              
740             An XML string which contains access control information which matches
741             Amazon's published schema. There is an example of one of these XML strings
742             in the tests for this module.
743              
744             =item acl_short (cannot be used in conjunction with acl_xml)
745              
746             You can use the shorthand notation instead of specifying XML for
747             certain 'canned' types of acls.
748              
749             (from the Amazon API documentation)
750              
751             private: Owner gets FULL_CONTROL. No one else has any access rights.
752             This is the default.
753              
754             public-read:Owner gets FULL_CONTROL and the anonymous principal is granted
755             READ access. If this policy is used on an object, it can be read from a
756             browser with no authentication.
757              
758             public-read-write:Owner gets FULL_CONTROL, the anonymous principal is
759             granted READ and WRITE access. This is a useful policy to apply to a bucket,
760             if you intend for any anonymous user to PUT objects into the bucket.
761              
762             authenticated-read:Owner gets FULL_CONTROL, and any principal authenticated
763             as a registered Amazon S3 user is granted READ access.
764              
765             =item key (optional)
766              
767             If the key is not set, it will apply the acl to the bucket.
768              
769             =back
770              
771             Returns a boolean.
772              
773             =head2 get_location_constraint
774              
775             Retrieves the location constraint set when the bucket was created. Returns a
776             string (eg, 'EU'), or undef if no location constraint was set.
777              
778             =head2 err
779              
780             The S3 error code for the last error the object ran into
781              
782             =head2 errstr
783              
784             A human readable error string for the last error the object ran into
785              
786             =head2 add_tags
787              
788             # Add tags for a bucket
789             $s3->add_tags ({
790             bucket => 'bucket-name',
791             tags => { tag1 => 'value-1', tag2 => 'value-2' },
792             });
793              
794             # Add tags for an object
795             $s3->add_tags ({
796             bucket => 'bucket-name',
797             key => 'key',
798             tags => { tag1 => 'value-1', tag2 => 'value-2' },
799             });
800              
801             Takes configuration parameters
802              
803             =over
804              
805             =item key (optional, scalar)
806              
807             If key is specified, add tag(s) to object, otherwise on bucket.
808              
809             =item tags (mandatory, hashref)
810              
811             Set specified tags and their respective values.
812              
813             =item version_id (optional)
814              
815             Is specified (in conjunction with C<key>) add tag(s) to versioned object.
816              
817             =back
818              
819             Returns C<true> on success.
820              
821             Returns C<false> and sets C<err>/C<errstr> otherwise.
822              
823             =head2 delete_tags
824              
825             # Add tags for a bucket
826             $s3->delete_tags ({
827             bucket => 'bucket-name',
828             });
829              
830             # Add tags for an object
831             $s3->delete_tags ({
832             bucket => 'bucket-name',
833             key => 'key',
834             version_id => $version_id,
835             });
836              
837             Takes configuration parameters
838              
839             =over
840              
841             =item key (optional, scalar)
842              
843             If key is specified, add tag(s) to object, otherwise on bucket.
844              
845             =item version_id (optional)
846              
847             Is specified (in conjunction with C<key>) add tag(s) to versioned object.
848              
849             =back
850              
851             Returns C<true> on success.
852              
853             Returns C<false> and sets C<err>/C<errstr> otherwise.
854              
855             =head1 SEE ALSO
856              
857             L<Net::Amazon::S3>
858              
859             =head1 AUTHOR
860              
861             Branislav Zahradník <barney@cpan.org>
862              
863             =head1 COPYRIGHT AND LICENSE
864              
865             This software is copyright (c) 2021 by Amazon Digital Services, Leon Brocard, Brad Fitzpatrick, Pedro Figueiredo, Rusty Conover, Branislav Zahradník.
866              
867             This is free software; you can redistribute it and/or modify it under
868             the same terms as the Perl 5 programming language system itself.
869              
870             =cut