1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
|
# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
module AWS
class S3
# Represents a bucket in S3.
#
# # Creating Buckets
#
# You create a bucket by name. Bucket names must be globally unique
# and must be DNS compatible.
#
# s3 = AWS::S3.new
# bucket = s3.buckets.create('dns-compat-bucket-name')
#
# # Getting a Bucket
#
# You can create a reference to a bucket, given its name.
#
# bucket = s3.buckets['bucket-name'] # makes no request
# bucket.exists? #=> returns true/false
#
# # Enumerating Buckets
#
# The {BucketCollection} class is enumerable.
#
# s3.buckets.each do |bucket|
# puts bucket.name
# end
#
# # Deleting a Bucket
#
# You can delete an empty bucket you own.
#
# bucket = s3.buckets.create('my-temp-bucket')
# bucket.objects['abc'].write('xyz')
#
# bucket.clear! # deletes all object versions in batches
# bucket.delete
#
# You can alternatively call {#delete!} which will clear
# the bucket for your first.
#
# bucket.delete!
#
# # Objects
#
# Given a bucket you can access its objects, either by key or by
# enumeration.
#
# bucket.objects['key'] #=> makes no request, returns an S3Object
#
# bucket.objects.each do |obj|
# puts obj.key
# end
#
# See {ObjectCollection} and {S3Object} for more information on working
# with objects.
#
# # Website Configuration
#
# It is easy to enable website hosting for a bucket.
#
# bucket.configure_website
#
# You can specify the index and error documents by passing a block.
# If your bucket is already configured as a website, the current
# configuration will be yielded. If you bucket it not currently
# configured as a website, a new configuration will be yielded
# with default values.
#
# bucket.configure_website do |cfg|
# cfg.index_document_suffix = 'index.html'
# cfg.error_document_key = 'error.html'
# end
#
# You can disable website hosting two ways:
#
# bucket.remove_website_configuration
# bucket.website_configuration = nil
#
# You can use {#website_configuration=} to copy a website configuration
# from one bucket to another.
#
# bucket.website_configuration = other_bucket.website_configuration
#
# # Bucket Policies and ACLs
#
# You can control access to your bucket and its contents a number
# of ways. You can specify a bucket ACL (access control list)
# or a bucket policy.
#
# ## ACLs
#
# ACLs control access to your bucket and its contents via a list of
# grants and grantees.
#
# ### Canned ACLs
#
# The simplest way to specify an ACL is to use one of Amazon's "canned"
# ACLs. Amazon accepts the following canned ACLs:
#
# * `:private`
# * `:public_read`
# * `:public_read_write`
# * `:authenticated_read`
# * `:bucket_owner_read`
# * `:bucket_owner_full_control`
#
# You can specify a the ACL at bucket creation or later update a bucket.
#
# # at create time, defaults to :private when not specified
# bucket = s3.buckets.create('name', :acl => :public_read)
#
# # replacing an existing bucket ACL
# bucket.acl = :private
#
# ### Grants
#
# Alternatively you can specify a hash of grants. Each entry in the
# `:grant` hash has a grant (key) and a list of grantees (values).
# Valid grant keys are:
#
# * `:grant_read`
# * `:grant_write`
# * `:grant_read_acp`
# * `:grant_write_acp`
# * `:grant_full_control`
#
# Each grantee can be a String, Hash or array of strings or hashes.
# The following example uses grants to provide public read
# to everyone while providing full control to a user by email address
# and to another by their account id (cannonical user id).
#
# bucket = s3.buckets.create('name', :grants => {
# :grant_read => [
# { :uri => "http://acs.amazonaws.com/groups/global/AllUsers" },
# ],
# :grant_full_control => [
# { :id => 'abc...mno' } # cannonical user id
# { :email_address => 'foo@bar.com' }, # email address
# ]
# })
#
# ### ACL Object
#
# Lastly, you can build an ACL object and use a Ruby DSL to specify grants
# and grantees. See {ACLObject} for more information.
#
# # updating an existing bucket acl using ACLObject
# bucket.acl.change do |acl|
# acl.grants.reject! do |g|
# g.grantee.canonical_user_id != bucket.owner.id
# end
# end
#
# ## Policies
#
# You can also work with bucket policies.
#
# policy = AWS::S3::Policy.new
# policy.allow(
# :actions => [:put_object, :get_object]
# :resources => [bucket]
# :principals => :any)
#
# bucket.policy = policy
#
# See {Core::Policy} and {S3::Policy} for more information on build
# policy objects.
#
# # Versioned Buckets
#
# You can enable versioning on a bucket you control. When versioning
# is enabled, S3 will keep track of each version of each object you
# write to the bucket (even deletions).
#
# bucket.versioning_enabled? #=> false
# bucket.enable_versioning
# # there is also a #suspend_versioning method
#
# obj = bucket.objects['my-obj']
# obj.write('a')
# obj.write('b')
# obj.delete
# obj.write('c')
#
# obj.versions.each do |obj_version|
# if obj_version.delete_marker?
# puts obj_version.read
# else
# puts "- DELETE MARKER"
# end
# end
#
# Alternatively you can enumerate all versions of all objects in your
# bucket.
#
# bucket.versions.each do |obj_version|
# puts obj_version.key ` " : " ` obj_version.version_id
# end
#
# See {BucketVersionCollection}, {ObjectVersionCollection} and
# {ObjectVersion} for more information on working with objects in
# a versioned bucket. Also see the S3 documentation for information
# on object versioning.
#
class Bucket
include Core::Model
include ACLOptions
# @param [String] name
# @param [Hash] options
# @option options [String] :owner (nil) The owner id of this bucket.
def initialize(name, options = {})
# the S3 docs disagree with what the service allows,
# so it's not safe to toss out invalid bucket names
# S3::Client.validate_bucket_name!(name)
@name = name
@owner = options[:owner]
super
end
# @return [String] The bucket name
attr_reader :name
# Returns the url for this bucket.
# @return [String] url to the bucket
def url(options = {})
protocol = options.fetch(:secure, false) ? "https://" : "http://"
if client.dns_compatible_bucket_name?(name)
"#{protocol}#{name}.s3.amazonaws.com/"
else
"#{protocol}s3.amazonaws.com/#{name}/"
end
end
# @return [Boolean] Returns true if the bucket has no objects
# (this includes versioned objects that are delete markers).
def empty?
versions.first ? false : true
end
# @return [String,nil] Returns the location constraint for a bucket
# (if it has one), nil otherwise.
def location_constraint
client.get_bucket_location(:bucket_name => name).location_constraint
end
# Configure the current bucket as a website.
#
# bucket.configure_website
#
# If you pass a block, the website configuration object
# will be yielded. You can modify it before it is saved.
#
# bucket.configure_website do |cfg|
# cfg.index_document_suffix = 'index.html'
# cfg.error_document_key = 'error.html'
# end
#
# If the bucket already has a website configuration, it will be loaded
# and yielded. This makes it possible to modify an existing
# configuration.
#
# # only rename the error document
# bucket.configure_website do |cfg|
# cfg.error_document_key = 'oops.html'
# end
#
# @yieldparam [WebsiteConfiguration] website_config
# @return [WebsiteConfiguration]
# @see #website_configuration
# @see #website_configuration=
# @see #remove_website_configuration
# @see #website?
def configure_website &block
website_config = self.website_configuration
website_config ||= WebsiteConfiguration.new
yield(website_config) if block_given?
self.website_configuration = website_config
end
# Returns the bucket website configuration. Returns `nil` if the bucket
# is not configured as a website.
# @return [WebsiteConfiguration,nil]
# @see #configure_website
# @see #website_configuration=
# @see #remove_website_configuration
# @see #website?
def website_configuration
resp = client.get_bucket_website(:bucket_name => name)
WebsiteConfiguration.new(resp.data)
rescue Errors::NoSuchWebsiteConfiguration
nil
end
# Sets the website configuration. Deletes the configuration if
# `nil` is passed.
# @param [WebsiteConfiguration,nil] website_configuration
# @see #configure_website
# @see #website_configuration
# @see #remove_website_configuration
# @see #website?
def website_configuration= website_configuration
if website_configuration
client_opts = website_configuration.to_hash
client_opts[:bucket_name] = name
client.put_bucket_website(client_opts)
else
remove_website_configuration
end
end
# @return [nil] Deletes the bucket website configuration.
# @see #configure_website
# @see #website_configuration
# @see #website_configuration=
# @see #website?
def remove_website_configuration
client.delete_bucket_website(:bucket_name => name)
@website_configuration = false
nil
end
# @return [Boolean] Returns `true` if this bucket is configured as
# a website.
# @see #configure_website
# @see #website_configuration
# @see #website_configuration=
# @see #remove_website_configuration
def website?
!!website_configuration
end
# Returns the tags for this bucket.
#
# tags = bucket.tags
# #=> <AWS::S3::BucketTagCollection>
#
# # adds a tag to the bucket
# tags['foo'] = 'abc'
#
# # replaces all tags
# tags.set('new' => 'tags')
#
# # removes all tags from the bucket
# tags.clear
#
# # returns tags as a hash
# tags.to_h
#
# @return [BucketTagCollection] Returns a collection that represents
# the tags for this bucket.
#
def tags
BucketTagCollection.new(self)
end
# Sets the tags for this bucket.
#
# bucket.tags = { 'contents' => 'photots' }
#
# You can remove all tags for the bucket by passing an empty
# hash or `nil`.
#
# bucket.tags = nil # {} also deletes all tags
# bucket.tags
# #=> {}
#
# @param [Hash,nil] tags The tags to set on this bucket.
#
def tags= tags
self.tags.set(tags)
end
# @return [CORSRuleCollection] Returns a collection that can be
# used to manage (add, edit and delete) CORS rules for this bucket.
def cors
CORSRuleCollection.new(self)
end
# Sets the bucket CORS rules.
# @param (see CORSRuleCollection#set)
# @see CORSRuleCollection#set
def cors= *rules
self.cors.set(*rules)
end
# Enables versioning on this bucket.
#
# @option opts [String] :mfa_delete Set to 'Enabled' or 'Disabled'
# to control the state of MFA delete on the bucket versioning.
# Setting this option requires the :mfa option to also be set.
#
# @option opts [String] :mfa The serial number and current token code of
# the Multi-Factor Authentication (MFA) device for the user. Format
# is "SERIAL TOKEN" - with a space between the serial and token.
#
# @return [nil]
def enable_versioning(opts = {})
client.set_bucket_versioning(
:bucket_name => @name,
:state => :enabled,
:mfa_delete => opts[:mfa_delete],
:mfa => opts[:mfa])
nil
end
# Suspends versioning on this bucket.
#
# @option opts [String] :mfa_delete Set to 'Enabled' or 'Disabled'
# to control the state of MFA delete on the bucket versioning.
# Setting this option requires the :mfa option to also be set.
#
# @option opts [String] :mfa The serial number and current token code of
# the Multi-Factor Authentication (MFA) device for the user. Format
# is "SERIAL TOKEN" - with a space between the serial and token.
#
# @return [nil]
def suspend_versioning(opts = {})
client.set_bucket_versioning(
:bucket_name => @name,
:state => :suspended,
:mfa_delete => opts[:mfa_delete],
:mfa => opts[:mfa])
nil
end
# @return [Boolean] returns `true` if version is enabled on this bucket.
def versioning_enabled?
versioning_state == :enabled
end
alias_method :versioned?, :versioning_enabled?
# Returns the versioning status for this bucket. States include:
#
# * `:enabled` - currently enabled
# * `:suspended` - currently suspended
# * `:unversioned` - versioning has never been enabled
#
# @return [Symbol] the versioning state
def versioning_state
client.get_bucket_versioning(:bucket_name => @name).status
end
# Deletes all objects from this bucket.
# @return [nil]
def clear!
versions.each_batch do |versions|
objects.delete(versions)
end
end
# Deletes the current bucket. An error will be raised if the
# bucket is not empty.
# @return [nil]
def delete
client.delete_bucket(:bucket_name => @name)
nil
end
# Deletes all objects in a bucket and then deletes the bucket.
# @return [nil]
def delete!
clear!
delete
end
# @return [String] bucket owner id
def owner
@owner || client.list_buckets.owner
end
# @api private
def inspect
"#<AWS::S3::Bucket:#{name}>"
end
# @return [Boolean] Returns true if the two buckets have the same name.
def ==(other)
other.kind_of?(Bucket) && other.name == name
end
# @return [Boolean] Returns true if the two buckets have the same name
def eql?(other_bucket)
self == other_bucket
end
# @note This method only indicates if there is a bucket in S3, not
# if you have permissions to work with the bucket or not.
# @return [Boolean] Returns true if the bucket exists in S3.
def exists?
begin
versioned? # makes a get bucket request without listing contents
# raises a client error if the bucket doesn't exist or
# if you don't have permission to get the bucket
# versioning status.
true
rescue Errors::NoSuchBucket => e
false # bucket does not exist
rescue Errors::AccessDenied => e
true # bucket exists
end
end
# @return [ObjectCollection] Represents all objects(keys) in
# this bucket.
def objects
ObjectCollection.new(self)
end
# @return [BucketVersionCollection] Represents all of the versioned
# objects stored in this bucket.
def versions
BucketVersionCollection.new(self)
end
# @return [MultipartUploadCollection] Represents all of the
# multipart uploads that are in progress for this bucket.
def multipart_uploads
MultipartUploadCollection.new(self)
end
# @api private
module ACLProxy
attr_accessor :bucket
def change
yield(self)
bucket.acl = self
end
end
# Returns the bucket's access control list. This will be an
# instance of AccessControlList, plus an additional `change`
# method:
#
# bucket.acl.change do |acl|
# acl.grants.reject! do |g|
# g.grantee.canonical_user_id != bucket.owner.id
# end
# end
#
# @return [AccessControlList]
def acl
resp = client.get_bucket_acl(:bucket_name => name)
acl = AccessControlList.new(resp.data)
acl.extend ACLProxy
acl.bucket = self
acl
end
# Sets the bucket's ACL (access control list). You can provide an ACL
# in a number of different formats.
# @param (see ACLOptions#acl_options)
# @return [nil]
def acl= acl
client.set_bucket_acl(acl_options(acl).merge(:bucket_name => name))
nil
end
# @api private
module PolicyProxy
attr_accessor :bucket
def change
yield(self)
bucket.policy = self
end
def delete
bucket.client.delete_bucket_policy(:bucket_name => bucket.name)
end
end
# Returns the bucket policy. This will be an instance of
# Policy. The returned policy will also have the methods of
# PolicyProxy mixed in, so you can use it to change the
# current policy or delete it, for example:
#
# if policy = bucket.policy
# # add a statement
# policy.change do |p|
# p.allow(...)
# end
#
# # delete the policy
# policy.delete
# end
#
# Note that changing the policy is not an atomic operation; it
# fetches the current policy, yields it to the block, and then
# sets it again. Therefore, it's possible that you may
# overwrite a concurrent update to the policy using this
# method.
#
# @return [Policy,nil] Returns the bucket policy (if it has one),
# or it returns `nil` otherwise.
def policy
resp = client.get_bucket_policy(:bucket_name => name)
policy = Policy.from_json(resp.data[:policy])
policy.extend(PolicyProxy)
policy.bucket = self
policy
rescue Errors::NoSuchBucketPolicy => e
nil
end
# Sets the bucket's policy.
#
# @param policy The new policy. This can be a string (which
# is assumed to contain a valid policy expressed in JSON), a
# Policy object or any object that responds to `to_json`.
# @see Policy
# @return [nil]
def policy=(policy)
client.set_bucket_policy(:bucket_name => name, :policy => policy)
nil
end
# The primary interface for editing the lifecycle configuration.
# See {BucketLifecycleConfiguration} for more information.
#
# @example Adding rules to a bucket's lifecycle configuration
#
# bucket.lifecycle_configuration.update do
# add_rule 'cache-1/', 30
# add_rule 'cache-2/', 30
# end
#
# @example Deleting the lifecycle configuration
#
# bucket.lifecycle_configuration.clear
#
# @return [BucketLifecycleConfiguration]
#
def lifecycle_configuration
@lifecycle_cfg ||= BucketLifecycleConfiguration.new(self)
end
# You can call this method if you prefer to build your own
# lifecycle configuration.
#
# bucket.lifecycle_configuration = <<-XML
# <LifecycleConfiguration>
# ...
# </LifecycleConfiguration>
# XML
#
# You can also use this method to copy a lifecycle configuration
# from another bucket.
#
# bucket.lifecycle_configuration = other_bucket.lifecycle_configuration
#
# If you call this method, passing nil, the lifecycle configuration
# for this bucket will be deleted.
#
# @param [String,Object] config You can pass an xml string or any
# other object that responds to #to_xml (e.g.
# BucketLifecycleConfiguration).
#
# @return [nil]
#
def lifecycle_configuration= config
if config.nil?
client_opts = {}
client_opts[:bucket_name] = name
client.delete_bucket_lifecycle_configuration(client_opts)
@lifecycle_cfg = BucketLifecycleConfiguration.new(self, :empty => true)
else
xml = config.is_a?(String) ? config : config.to_xml
client_opts = {}
client_opts[:bucket_name] = name
client_opts[:lifecycle_configuration] = xml
client.set_bucket_lifecycle_configuration(client_opts)
@lifecycle_cfg = BucketLifecycleConfiguration.new(self, :xml => xml)
end
nil
end
# Returns a tree that allows you to expose the bucket contents
# like a directory structure.
#
# @see Tree
# @param [Hash] options
# @option options [String] :prefix (nil) Set prefix to choose where
# the top of the tree will be. A value of `nil` means
# that the tree will include all objects in the collection.
#
# @option options [String] :delimiter ('/') The string that separates
# each level of the tree. This is usually a directory separator.
#
# @option options [Boolean] :append (true) If true, the delimiter is
# appended to the prefix when the prefix does not already end
# with the delimiter.
#
# @return [Tree]
def as_tree options = {}
objects.as_tree(options)
end
# Generates fields for a presigned POST to this object. All
# options are sent to the PresignedPost constructor.
#
# @see PresignedPost
def presigned_post(options = {})
PresignedPost.new(self, options)
end
end
end
end
|