blobstore_client 1.3149.0 → 1.3153.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/blobstore_client/s3_blobstore_client.rb +84 -86
- data/lib/blobstore_client/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10c163e30bf30a08b4909ba6e12c7081efb1f991
|
4
|
+
data.tar.gz: ec59e8b8134b97a754eed2ec8bf6d94b5e07c123
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e0c59b863d0ec822b33133035ac5a9734963bfd11e3b1a55f08f66ad85f5fd3bdd7bbeec58203500fe4a6ef7c2aba9f801290cdd01c3428a0329f36787badd3e
|
7
|
+
data.tar.gz: b1c4581c5cc14372db13fae84d410788eb24226441c8162f48df2120020ad1f8c4d07eba2ab57a5cf16a4f671971c5afc78ac7e2f76c10efe4d862e720a547c7
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'openssl'
|
2
2
|
require 'digest/sha1'
|
3
3
|
require 'base64'
|
4
|
-
require 'aws'
|
4
|
+
require 'aws-sdk-resources'
|
5
5
|
require 'securerandom'
|
6
6
|
|
7
7
|
module Bosh
|
@@ -9,14 +9,15 @@ module Bosh
|
|
9
9
|
class S3BlobstoreClient < BaseClient
|
10
10
|
|
11
11
|
ENDPOINT = 'https://s3.amazonaws.com'
|
12
|
-
|
12
|
+
DEFAULT_REGION = 'us-east-1'
|
13
|
+
# hack to get the v2 AWS SDK to behave with S3-compatible blobstores
|
14
|
+
BLANK_REGION = ' '
|
13
15
|
|
14
|
-
attr_reader :
|
16
|
+
attr_reader :simple
|
15
17
|
|
16
|
-
# Blobstore client for S3
|
18
|
+
# Blobstore client for S3
|
17
19
|
# @param [Hash] options S3connection options
|
18
20
|
# @option options [Symbol] bucket_name
|
19
|
-
# @option options [Symbol, optional] encryption_key optional encryption
|
20
21
|
# key that is applied before the object is sent to S3
|
21
22
|
# @option options [Symbol, optional] access_key_id
|
22
23
|
# @option options [Symbol, optional] secret_access_key
|
@@ -25,27 +26,23 @@ module Bosh
|
|
25
26
|
# simple_blobstore_client
|
26
27
|
def initialize(options)
|
27
28
|
super(options)
|
28
|
-
@bucket_name = @options[:bucket_name]
|
29
|
-
@encryption_key = @options[:encryption_key]
|
30
29
|
|
31
|
-
aws_options = {
|
30
|
+
@aws_options = build_aws_options({
|
31
|
+
bucket_name: @options[:bucket_name],
|
32
32
|
use_ssl: @options.fetch(:use_ssl, true),
|
33
|
-
|
34
|
-
|
33
|
+
host: @options[:host],
|
34
|
+
port: @options[:port],
|
35
|
+
region: @options.fetch(:region, DEFAULT_REGION),
|
35
36
|
s3_force_path_style: @options.fetch(:s3_force_path_style, false),
|
36
|
-
ssl_verify_peer:
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
ssl_verify_peer: @options.fetch(:ssl_verify_peer, true),
|
38
|
+
credentials_source: @options.fetch(:credentials_source, 'static'),
|
39
|
+
access_key_id: @options[:access_key_id],
|
40
|
+
secret_access_key: @options[:secret_access_key]
|
41
|
+
})
|
41
42
|
|
42
43
|
# using S3 without credentials is a special case:
|
43
44
|
# it is really the simple blobstore client with a bucket name
|
44
45
|
if read_only?
|
45
|
-
if @encryption_key
|
46
|
-
raise BlobstoreError, "can't use read-only with an encryption key"
|
47
|
-
end
|
48
|
-
|
49
46
|
unless @options[:bucket_name] || @options[:bucket]
|
50
47
|
raise BlobstoreError, 'bucket name required'
|
51
48
|
end
|
@@ -53,12 +50,10 @@ module Bosh
|
|
53
50
|
@options[:bucket] ||= @options[:bucket_name]
|
54
51
|
@options[:endpoint] ||= S3BlobstoreClient::ENDPOINT
|
55
52
|
@simple = SimpleBlobstoreClient.new(@options)
|
56
|
-
else
|
57
|
-
@s3 = AWS::S3.new(aws_options)
|
58
53
|
end
|
59
54
|
|
60
|
-
rescue
|
61
|
-
raise BlobstoreError, "Failed to initialize S3 blobstore: #{e.message}"
|
55
|
+
rescue Aws::S3::Errors::ServiceError => e
|
56
|
+
raise BlobstoreError, "Failed to initialize S3 blobstore: #{e.code} : #{e.message}"
|
62
57
|
end
|
63
58
|
|
64
59
|
# @param [File] file file to store in S3
|
@@ -67,17 +62,13 @@ module Bosh
|
|
67
62
|
|
68
63
|
object_id ||= generate_object_id
|
69
64
|
|
70
|
-
file = encrypt_file(file) if @encryption_key
|
71
|
-
|
72
65
|
# in Ruby 1.8 File doesn't respond to :path
|
73
66
|
path = file.respond_to?(:path) ? file.path : file
|
74
67
|
store_in_s3(path, full_oid_path(object_id))
|
75
68
|
|
76
69
|
object_id
|
77
|
-
rescue
|
78
|
-
raise BlobstoreError, "Failed to create object, S3 response error: #{e.message}"
|
79
|
-
ensure
|
80
|
-
FileUtils.rm(file) if @encryption_key
|
70
|
+
rescue Aws::S3::Errors::ServiceError => e
|
71
|
+
raise BlobstoreError, "Failed to create object, S3 response error code #{e.code}: #{e.message}"
|
81
72
|
end
|
82
73
|
|
83
74
|
# @param [String] object_id object id to retrieve
|
@@ -86,81 +77,59 @@ module Bosh
|
|
86
77
|
object_id = full_oid_path(object_id)
|
87
78
|
return @simple.get_file(object_id, file) if @simple
|
88
79
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
cipher.key = Digest::SHA1.digest(encryption_key)[0..(cipher.key_len - 1)]
|
80
|
+
s3_object = Aws::S3::Object.new({:key => object_id}.merge(@aws_options))
|
81
|
+
s3_object.get do |chunk|
|
82
|
+
file.write(chunk)
|
93
83
|
end
|
94
84
|
|
95
|
-
|
96
|
-
object.read do |chunk|
|
97
|
-
if @encryption_key
|
98
|
-
file.write(cipher.update(chunk))
|
99
|
-
else
|
100
|
-
file.write(chunk)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
file.write(cipher.final) if @encryption_key
|
105
|
-
|
106
|
-
rescue AWS::S3::Errors::NoSuchKey => e
|
85
|
+
rescue Aws::S3::Errors::NoSuchKey => e
|
107
86
|
raise NotFound, "S3 object '#{object_id}' not found"
|
108
|
-
rescue
|
109
|
-
raise BlobstoreError, "Failed to find object '#{object_id}', S3 response error: #{e.message}"
|
87
|
+
rescue Aws::S3::Errors::ServiceError => e
|
88
|
+
raise BlobstoreError, "Failed to find object '#{object_id}', S3 response error code #{e.code}: #{e.message}"
|
110
89
|
end
|
111
90
|
|
112
91
|
# @param [String] object_id object id to delete
|
113
92
|
def delete_object(object_id)
|
114
93
|
raise BlobstoreError, 'unsupported action' if @simple
|
115
94
|
object_id = full_oid_path(object_id)
|
116
|
-
object = get_object_from_s3(object_id)
|
117
|
-
raise NotFound, "Object '#{object_id}' is not found" unless object.exists?
|
118
95
|
|
119
|
-
|
120
|
-
|
121
|
-
|
96
|
+
s3_object = Aws::S3::Object.new({:key => object_id}.merge(@aws_options))
|
97
|
+
# TODO: don't blow up if we are cannot find an object we are trying to
|
98
|
+
# delete anyway
|
99
|
+
raise NotFound, "Object '#{object_id}' is not found" unless s3_object.exists?
|
100
|
+
|
101
|
+
s3_object.delete
|
102
|
+
rescue Aws::S3::Errors::ServiceError => e
|
103
|
+
raise BlobstoreError, "Failed to delete object '#{object_id}', S3 response error code #{e.code}: #{e.message}"
|
122
104
|
end
|
123
105
|
|
124
106
|
def object_exists?(object_id)
|
125
107
|
object_id = full_oid_path(object_id)
|
126
108
|
return simple.exists?(object_id) if simple
|
127
109
|
|
128
|
-
|
110
|
+
# Hack to get the Aws SDK to redirect to the correct region on
|
111
|
+
# subsequent requests
|
112
|
+
unless @region_configured
|
113
|
+
s3 = Aws::S3::Client.new(@aws_options.reject{|k| k == :bucket_name})
|
114
|
+
s3.list_objects({bucket: @aws_options[:bucket_name]})
|
115
|
+
@region_configured = true
|
116
|
+
end
|
117
|
+
|
118
|
+
Aws::S3::Object.new({:key => object_id}.merge(@aws_options)).exists?
|
129
119
|
end
|
130
120
|
|
131
121
|
protected
|
132
122
|
|
133
|
-
# @param [String] oid object id
|
134
|
-
# @return [AWS::S3::S3Object] S3 object
|
135
|
-
def get_object_from_s3(oid)
|
136
|
-
@s3.buckets[bucket_name].objects[oid]
|
137
|
-
end
|
138
|
-
|
139
123
|
# @param [String] path path to file which will be stored in S3
|
140
124
|
# @param [String] oid object id
|
141
125
|
# @return [void]
|
142
126
|
def store_in_s3(path, oid)
|
143
|
-
|
144
|
-
raise BlobstoreError, "object id #{oid} is already in use" if s3_object.exists?
|
145
|
-
File.open(path, 'r') do |temp_file|
|
146
|
-
s3_object.write(temp_file, content_type: "application/octet-stream")
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def encrypt_file(file)
|
151
|
-
cipher = OpenSSL::Cipher::Cipher.new(DEFAULT_CIPHER_NAME)
|
152
|
-
cipher.encrypt
|
153
|
-
cipher.key = Digest::SHA1.digest(encryption_key)[0..(cipher.key_len - 1)]
|
127
|
+
raise BlobstoreError, "object id #{oid} is already in use" if object_exists?(oid)
|
154
128
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
end
|
160
|
-
temp_file.write(cipher.final)
|
161
|
-
end
|
162
|
-
|
163
|
-
path
|
129
|
+
s3_object = Aws::S3::Object.new({:key => oid}.merge(@aws_options))
|
130
|
+
multipart_threshold = @options.fetch(:s3_multipart_threshold, 16_777_216)
|
131
|
+
s3_object.upload_file(path, {content_type: "application/octet-stream", multipart_threshold: multipart_threshold})
|
132
|
+
nil
|
164
133
|
end
|
165
134
|
|
166
135
|
def read_only?
|
@@ -174,19 +143,24 @@ module Bosh
|
|
174
143
|
@options[:folder] ? @options[:folder] + '/' + object_id : object_id
|
175
144
|
end
|
176
145
|
|
177
|
-
def
|
146
|
+
def use_v4_signing?(region)
|
147
|
+
(region == 'eu-central-1' ||
|
148
|
+
region == 'cn-north-1')
|
149
|
+
end
|
150
|
+
|
151
|
+
def aws_credentials(credentials_source, access_key_id, secret_access_key)
|
178
152
|
creds = {}
|
179
153
|
# credentials_source could be static (default) or env_or_profile
|
180
154
|
# static credentials must be included in aws_properties
|
181
|
-
# env_or_profile credentials will use the
|
182
|
-
# to find
|
183
|
-
case
|
155
|
+
# env_or_profile credentials will use the Aws DefaultCredentialsProvider
|
156
|
+
# to find Aws credentials in environment variables or EC2 instance profiles
|
157
|
+
case credentials_source
|
184
158
|
when 'static'
|
185
|
-
creds[:access_key_id] =
|
186
|
-
creds[:secret_access_key] =
|
159
|
+
creds[:access_key_id] = access_key_id
|
160
|
+
creds[:secret_access_key] = secret_access_key
|
187
161
|
|
188
162
|
when 'env_or_profile'
|
189
|
-
if
|
163
|
+
if !access_key_id.nil? || !secret_access_key.nil?
|
190
164
|
raise BlobstoreError, "can't use access_key_id or secret_access_key with env_or_profile credentials_source"
|
191
165
|
end
|
192
166
|
else
|
@@ -194,6 +168,30 @@ module Bosh
|
|
194
168
|
end
|
195
169
|
return creds
|
196
170
|
end
|
171
|
+
|
172
|
+
def build_aws_options(options)
|
173
|
+
aws_options = {
|
174
|
+
bucket_name: options[:bucket_name],
|
175
|
+
region: options[:region],
|
176
|
+
force_path_style: options[:s3_force_path_style],
|
177
|
+
ssl_verify_peer: options[:ssl_verify_peer],
|
178
|
+
}
|
179
|
+
|
180
|
+
unless options[:host].nil?
|
181
|
+
host = options[:host]
|
182
|
+
protocol = options[:use_ssl] ? 'https' : 'http'
|
183
|
+
uri = options[:port].nil? ? host : "#{host}:#{options[:port]}"
|
184
|
+
aws_options[:endpoint] = "#{protocol}://#{uri}"
|
185
|
+
aws_options[:region] = BLANK_REGION
|
186
|
+
end
|
187
|
+
|
188
|
+
aws_options[:signature_version] = 's3' unless use_v4_signing?(options[:region])
|
189
|
+
|
190
|
+
creds = aws_credentials(options[:credentials_source], options[:access_key_id], options[:secret_access_key])
|
191
|
+
aws_options.merge!(creds)
|
192
|
+
|
193
|
+
aws_options
|
194
|
+
end
|
197
195
|
end
|
198
196
|
end
|
199
197
|
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blobstore_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3153.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- VMware
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-12-
|
11
|
+
date: 2015-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: aws-sdk
|
14
|
+
name: aws-sdk-resources
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.2.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 2.2.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: httpclient
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.
|
61
|
+
version: 1.3153.0
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.
|
68
|
+
version: 1.3153.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|