Class: Bosh::Blobstore::S3BlobstoreClient

Inherits:
BaseClient show all
Defined in:
lib/blobstore_client/s3_blobstore_client.rb

Constant Summary collapse

ENDPOINT =
'https://s3.amazonaws.com'
DEFAULT_CIPHER_NAME =
'aes-128-cbc'

Constants inherited from Client

Client::PROVIDER_NAMES, Client::VERSION

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from BaseClient

#create, #delete, #exists?, #get

Methods inherited from Client

create, safe_create

Constructor Details

#initialize(options) ⇒ S3BlobstoreClient

Note:

If access_key_id and secret_access_key are not present, the blobstore client operates in read only mode as a simple_blobstore_client

Blobstore client for S3 with optional object encryption

Parameters:

  • options (Hash)

    S3connection options

Options Hash (options):

  • bucket_name (Symbol)
  • encryption_key (Symbol, optional)

    optional encryption key that is applied before the object is sent to S3

  • access_key_id (Symbol, optional)
  • secret_access_key (Symbol, optional)


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
# File 'lib/blobstore_client/s3_blobstore_client.rb', line 26

def initialize(options)
  super(options)
  @bucket_name    = @options[:bucket_name]
  @encryption_key = @options[:encryption_key]

  aws_options = {
    access_key_id: @options[:access_key_id],
    secret_access_key: @options[:secret_access_key],
    use_ssl: @options.fetch(:use_ssl, true),
    s3_port: @options.fetch(:port, 443),
    s3_endpoint: @options.fetch(:host, URI.parse(S3BlobstoreClient::ENDPOINT).host),
    s3_force_path_style: @options.fetch(:s3_force_path_style, false),
    ssl_verify_peer: @options.fetch(:ssl_verify_peer, true),
    s3_multipart_threshold: @options.fetch(:s3_multipart_threshold, 16_777_216),
  }

  # using S3 without credentials is a special case:
  # it is really the simple blobstore client with a bucket name
  if read_only?
    if @encryption_key
      raise BlobstoreError, "can't use read-only with an encryption key"
    end

    unless @options[:bucket_name] || @options[:bucket]
      raise BlobstoreError, 'bucket name required'
    end

    @options[:bucket] ||= @options[:bucket_name]
    @options[:endpoint] ||= S3BlobstoreClient::ENDPOINT
    @simple = SimpleBlobstoreClient.new(@options)
  else
    @s3 = AWS::S3.new(aws_options)
  end

rescue AWS::Errors::Base => e
  raise BlobstoreError, "Failed to initialize S3 blobstore: #{e.message}"
end

Instance Attribute Details

#bucket_nameObject (readonly)

Returns the value of attribute bucket_name.



14
15
16
# File 'lib/blobstore_client/s3_blobstore_client.rb', line 14

def bucket_name
  @bucket_name
end

#encryption_keyObject (readonly)

Returns the value of attribute encryption_key.



14
15
16
# File 'lib/blobstore_client/s3_blobstore_client.rb', line 14

def encryption_key
  @encryption_key
end

#simpleObject (readonly)

Returns the value of attribute simple.



14
15
16
# File 'lib/blobstore_client/s3_blobstore_client.rb', line 14

def simple
  @simple
end

Instance Method Details

#create_file(object_id, file) ⇒ Object

Parameters:

  • file (File)

    file to store in S3



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/blobstore_client/s3_blobstore_client.rb', line 65

def create_file(object_id, file)
  raise BlobstoreError, 'unsupported action' if @simple

  object_id ||= generate_object_id

  file = encrypt_file(file) if @encryption_key

  # in Ruby 1.8 File doesn't respond to :path
  path = file.respond_to?(:path) ? file.path : file
  store_in_s3(path, full_oid_path(object_id))

  object_id
rescue AWS::Errors::Base => e
  raise BlobstoreError, "Failed to create object, S3 response error: #{e.message}"
ensure
  FileUtils.rm(file) if @encryption_key
end

#delete_object(object_id) ⇒ Object

Parameters:

  • object_id (String)

    object id to delete



113
114
115
116
117
118
119
120
121
122
123
# File 'lib/blobstore_client/s3_blobstore_client.rb', line 113

def delete_object(object_id)
  raise BlobstoreError, 'unsupported action' if @simple
  object_id = full_oid_path(object_id)
  object = get_object_from_s3(object_id)
  unless object.exists?
    raise BlobstoreError, "no such object: #{object_id}"
  end
  object.delete
rescue AWS::Errors::Base => e
  raise BlobstoreError, "Failed to delete object '#{object_id}', S3 response error: #{e.message}"
end

#get_file(object_id, file) ⇒ Object

Parameters:

  • object_id (String)

    object id to retrieve

  • file (File)

    file to store the retrived object in



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
# File 'lib/blobstore_client/s3_blobstore_client.rb', line 85

def get_file(object_id, file)
  object_id = full_oid_path(object_id)
  return @simple.get_file(object_id, file) if @simple

  if @encryption_key
    cipher = OpenSSL::Cipher::Cipher.new(DEFAULT_CIPHER_NAME)
    cipher.decrypt
    cipher.key = Digest::SHA1.digest(encryption_key)[0..(cipher.key_len - 1)]
  end

  object = get_object_from_s3(object_id)
  object.read do |chunk|
    if @encryption_key
      file.write(cipher.update(chunk))
    else
      file.write(chunk)
    end
  end

  file.write(cipher.final) if @encryption_key

rescue AWS::S3::Errors::NoSuchKey => e
  raise NotFound, "S3 object '#{object_id}' not found"
rescue AWS::Errors::Base => e
  raise BlobstoreError, "Failed to find object '#{object_id}', S3 response error: #{e.message}"
end

#object_exists?(object_id) ⇒ Boolean

Returns:

  • (Boolean)


125
126
127
128
129
130
# File 'lib/blobstore_client/s3_blobstore_client.rb', line 125

def object_exists?(object_id)
  object_id = full_oid_path(object_id)
  return simple.exists?(object_id) if simple

  get_object_from_s3(object_id).exists?
end