File Coverage

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


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