lib/backup/storage/s3.rb
require "backup/cloud_io/s3"
module Backup
module Storage
class S3 < Base
include Storage::Cycler
class Error < Backup::Error; end
##
# Amazon Simple Storage Service (S3) Credentials
attr_accessor :access_key_id, :secret_access_key, :use_iam_profile
##
# Amazon S3 bucket name
attr_accessor :bucket
##
# Region of the specified S3 bucket
attr_accessor :region
##
# Multipart chunk size, specified in MiB.
#
# Each package file larger than +chunk_size+
# will be uploaded using S3 Multipart Upload.
#
# Minimum: 5 (but may be disabled with 0)
# Maximum: 5120
# Default: 5
attr_accessor :chunk_size
##
# Number of times to retry failed operations.
#
# Default: 10
attr_accessor :max_retries
##
# Time in seconds to pause before each retry.
#
# Default: 30
attr_accessor :retry_waitsec
##
# Encryption algorithm to use for Amazon Server-Side Encryption
#
# Supported values:
#
# - :aes256
#
# Default: nil
attr_accessor :encryption
##
# Storage class to use for the S3 objects uploaded
#
# Supported values:
#
# - :standard (default)
# - :standard_ia
# - :reduced_redundancy
#
# Default: :standard
attr_accessor :storage_class
##
# Additional options to pass along to fog.
# e.g. Fog::Storage.new({ :provider => 'AWS' }.merge(fog_options))
attr_accessor :fog_options
def initialize(model, storage_id = nil)
super
@chunk_size ||= 5 # MiB
@max_retries ||= 10
@retry_waitsec ||= 30
@path ||= "backups"
@storage_class ||= :standard
@path = @path.sub(/^\//, "")
check_configuration
end
private
def cloud_io
@cloud_io ||= CloudIO::S3.new(
access_key_id: access_key_id,
secret_access_key: secret_access_key,
use_iam_profile: use_iam_profile,
region: region,
bucket: bucket,
encryption: encryption,
storage_class: storage_class,
max_retries: max_retries,
retry_waitsec: retry_waitsec,
chunk_size: chunk_size,
fog_options: fog_options
)
end
def transfer!
package.filenames.each do |filename|
src = File.join(Config.tmp_path, filename)
dest = File.join(remote_path, filename)
Logger.info "Storing '#{bucket}/#{dest}'..."
cloud_io.upload(src, dest)
end
end
# Called by the Cycler.
# Any error raised will be logged as a warning.
def remove!(package)
Logger.info "Removing backup package dated #{package.time}..."
remote_path = remote_path_for(package)
objects = cloud_io.objects(remote_path)
raise Error, "Package at '#{remote_path}' not found" if objects.empty?
cloud_io.delete(objects)
end
def check_configuration
required =
if use_iam_profile
%w[bucket]
else
%w[access_key_id secret_access_key bucket]
end
raise Error, <<-EOS if required.map { |name| send(name) }.any?(&:nil?)
Configuration Error
#{required.map { |name| "##{name}" }.join(", ")} are all required
EOS
raise Error, <<-EOS if chunk_size > 0 && !chunk_size.between?(5, 5120)
Configuration Error
#chunk_size must be between 5 and 5120 (or 0 to disable multipart)
EOS
raise Error, <<-EOS if encryption && encryption.to_s.upcase != "AES256"
Configuration Error
#encryption must be :aes256 or nil
EOS
classes = ["STANDARD", "STANDARD_IA", "REDUCED_REDUNDANCY"]
raise Error, <<-EOS unless classes.include?(storage_class.to_s.upcase)
Configuration Error
#storage_class must be :standard or :standard_ia or :reduced_redundancy
EOS
end
end
end
end