blobstore_client 0.3.13 → 0.4.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.
- data/Rakefile +0 -1
- data/lib/blobstore_client.rb +10 -6
- data/lib/blobstore_client/base.rb +5 -4
- data/lib/blobstore_client/s3_blobstore_client.rb +54 -6
- data/lib/blobstore_client/simple_blobstore_client.rb +2 -1
- data/lib/blobstore_client/version.rb +1 -1
- data/spec/unit/blobstore_client_spec.rb +5 -0
- data/spec/unit/s3_blobstore_client_spec.rb +52 -0
- metadata +56 -15
data/Rakefile
CHANGED
data/lib/blobstore_client.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module Bosh; module Blobstore; end; end
|
4
4
|
|
5
|
+
require "common/common"
|
5
6
|
require "blobstore_client/version"
|
6
7
|
require "blobstore_client/errors"
|
7
8
|
|
@@ -23,15 +24,18 @@ module Bosh
|
|
23
24
|
"local" => LocalClient
|
24
25
|
}
|
25
26
|
|
26
|
-
def self.create(
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
else
|
27
|
+
def self.create(blobstore_provider, options = {})
|
28
|
+
provider = PROVIDER_MAP[blobstore_provider]
|
29
|
+
|
30
|
+
unless provider
|
31
31
|
providers = PROVIDER_MAP.keys.sort.join(", ")
|
32
|
-
raise
|
32
|
+
raise BlobstoreError,
|
33
|
+
"Invalid client provider, available providers are: #{providers}"
|
33
34
|
end
|
35
|
+
|
36
|
+
provider.new(options)
|
34
37
|
end
|
38
|
+
|
35
39
|
end
|
36
40
|
end
|
37
41
|
end
|
@@ -7,7 +7,7 @@ module Bosh
|
|
7
7
|
class BaseClient < Client
|
8
8
|
|
9
9
|
def initialize(options)
|
10
|
-
@options = symbolize_keys(options)
|
10
|
+
@options = Bosh::Common.symbolize_keys(options)
|
11
11
|
end
|
12
12
|
|
13
13
|
def symbolize_keys(hash)
|
@@ -34,7 +34,7 @@ module Bosh
|
|
34
34
|
File.open(path, "w") do |file|
|
35
35
|
file.write(contents)
|
36
36
|
end
|
37
|
-
create_file(File.open(path, "r"))
|
37
|
+
return create_file(File.open(path, "r"))
|
38
38
|
rescue BlobstoreError => e
|
39
39
|
raise e
|
40
40
|
rescue Exception => e
|
@@ -72,9 +72,10 @@ module Bosh
|
|
72
72
|
def temp_path
|
73
73
|
path = File.join(Dir::tmpdir, "temp-path-#{UUIDTools::UUID.random_create}")
|
74
74
|
begin
|
75
|
-
yield path
|
75
|
+
yield path if block_given?
|
76
|
+
path
|
76
77
|
ensure
|
77
|
-
FileUtils.rm_f(path)
|
78
|
+
FileUtils.rm_f(path) if block_given?
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
@@ -11,10 +11,21 @@ module Bosh
|
|
11
11
|
|
12
12
|
class S3BlobstoreClient < BaseClient
|
13
13
|
|
14
|
+
ENDPOINT = "https://s3.amazonaws.com"
|
14
15
|
DEFAULT_CIPHER_NAME = "aes-128-cbc"
|
15
16
|
|
16
17
|
attr_reader :bucket_name, :encryption_key
|
17
18
|
|
19
|
+
# Blobstore client for S3 with optional object encryption
|
20
|
+
# @param [Hash] options S3connection options
|
21
|
+
# @option options [Symbol] bucket_name
|
22
|
+
# @option options [Symbol, optional] encryption_key optional encryption
|
23
|
+
# key that is applied before the object is sent to S3
|
24
|
+
# @option options [Symbol, optional] access_key_id
|
25
|
+
# @option options [Symbol, optional] secret_access_key
|
26
|
+
# @note If access_key_id and secret_access_key are not present, the
|
27
|
+
# blobstore client operates in read only mode as a
|
28
|
+
# simple_blobstore_client
|
18
29
|
def initialize(options)
|
19
30
|
super(options)
|
20
31
|
@bucket_name = @options[:bucket_name]
|
@@ -27,21 +38,45 @@ module Bosh
|
|
27
38
|
:port => 443
|
28
39
|
}
|
29
40
|
|
30
|
-
|
41
|
+
# using S3 without credentials is a special case:
|
42
|
+
# it is really the simple blobstore client with a bucket name
|
43
|
+
if read_only?
|
44
|
+
unless @options[:bucket_name] || @options[:bucket]
|
45
|
+
raise BlobstoreError, "bucket name required"
|
46
|
+
end
|
47
|
+
@options[:bucket] ||= @options[:bucket_name]
|
48
|
+
@options[:endpoint] ||= S3BlobstoreClient::ENDPOINT
|
49
|
+
@simple = SimpleBlobstoreClient.new(@options)
|
50
|
+
else
|
51
|
+
AWS::S3::Base.establish_connection!(aws_options)
|
52
|
+
end
|
53
|
+
|
31
54
|
rescue AWS::S3::S3Exception => e
|
32
55
|
raise BlobstoreError, "Failed to initialize S3 blobstore: #{e.message}"
|
33
56
|
end
|
34
57
|
|
35
58
|
def create_file(file)
|
59
|
+
raise BlobstoreError, "unsupported action" if @simple
|
60
|
+
|
36
61
|
object_id = generate_object_id
|
37
|
-
|
38
|
-
|
39
|
-
|
62
|
+
|
63
|
+
if @encryption_key
|
64
|
+
temp_path do |path|
|
65
|
+
File.open(path, "w") do |temp_file|
|
66
|
+
encrypt_stream(file, temp_file)
|
67
|
+
end
|
68
|
+
File.open(path, "r") do |temp_file|
|
69
|
+
AWS::S3::S3Object.store(object_id, temp_file, bucket_name)
|
70
|
+
end
|
40
71
|
end
|
41
|
-
|
72
|
+
elsif file.is_a?(String)
|
73
|
+
File.open(file, "r") do |temp_file|
|
42
74
|
AWS::S3::S3Object.store(object_id, temp_file, bucket_name)
|
43
75
|
end
|
76
|
+
else # Ruby 1.8 passes a File
|
77
|
+
AWS::S3::S3Object.store(object_id, file, bucket_name)
|
44
78
|
end
|
79
|
+
|
45
80
|
object_id
|
46
81
|
rescue AWS::S3::S3Exception => e
|
47
82
|
raise BlobstoreError,
|
@@ -49,6 +84,8 @@ module Bosh
|
|
49
84
|
end
|
50
85
|
|
51
86
|
def get_file(object_id, file)
|
87
|
+
return @simple.get_file(object_id, file) if @simple
|
88
|
+
|
52
89
|
object = AWS::S3::S3Object.find(object_id, bucket_name)
|
53
90
|
from = lambda { |callback|
|
54
91
|
object.value { |segment|
|
@@ -59,7 +96,12 @@ module Bosh
|
|
59
96
|
end
|
60
97
|
}
|
61
98
|
}
|
62
|
-
|
99
|
+
if @encryption_key
|
100
|
+
decrypt_stream(from, file)
|
101
|
+
else
|
102
|
+
to_stream = write_stream(file)
|
103
|
+
read_stream(from) { |segment| to_stream.call(segment) }
|
104
|
+
end
|
63
105
|
rescue AWS::S3::NoSuchKey => e
|
64
106
|
raise NotFound, "S3 object '#{object_id}' not found"
|
65
107
|
rescue AWS::S3::S3Exception => e
|
@@ -68,6 +110,8 @@ module Bosh
|
|
68
110
|
end
|
69
111
|
|
70
112
|
def delete(object_id)
|
113
|
+
raise BlobstoreError, "unsupported action" if @simple
|
114
|
+
|
71
115
|
AWS::S3::S3Object.delete(object_id, bucket_name)
|
72
116
|
rescue AWS::S3::S3Exception => e
|
73
117
|
raise BlobstoreError,
|
@@ -122,6 +166,10 @@ module Bosh
|
|
122
166
|
end
|
123
167
|
end
|
124
168
|
|
169
|
+
def read_only?
|
170
|
+
@options[:access_key_id].nil? && @options[:secret_access_key].nil?
|
171
|
+
end
|
172
|
+
|
125
173
|
end
|
126
174
|
end
|
127
175
|
end
|
@@ -10,6 +10,7 @@ module Bosh
|
|
10
10
|
super(options)
|
11
11
|
@client = HTTPClient.new
|
12
12
|
@endpoint = @options[:endpoint]
|
13
|
+
@bucket = @options[:bucket] || "resources"
|
13
14
|
@headers = {}
|
14
15
|
user = @options[:user]
|
15
16
|
password = @options[:password]
|
@@ -20,7 +21,7 @@ module Bosh
|
|
20
21
|
end
|
21
22
|
|
22
23
|
def url(id=nil)
|
23
|
-
["#{@endpoint}
|
24
|
+
["#{@endpoint}/#{@bucket}", id].compact.join("/")
|
24
25
|
end
|
25
26
|
|
26
27
|
def create_file(file)
|
@@ -25,6 +25,11 @@ describe Bosh::Blobstore::Client do
|
|
25
25
|
bs.should be_instance_of Bosh::Blobstore::S3BlobstoreClient
|
26
26
|
end
|
27
27
|
|
28
|
+
it "should pick S3 provider when S3 is used without credentials" do
|
29
|
+
bs = Bosh::Blobstore::Client.create('s3', {:bucket_name => "foo"})
|
30
|
+
bs.should be_instance_of Bosh::Blobstore::S3BlobstoreClient
|
31
|
+
end
|
32
|
+
|
28
33
|
it "should raise an exception on an unknown client" do
|
29
34
|
lambda {
|
30
35
|
bs = Bosh::Blobstore::Client.create('foobar', {})
|
@@ -15,6 +15,34 @@ describe Bosh::Blobstore::S3BlobstoreClient do
|
|
15
15
|
Bosh::Blobstore::S3BlobstoreClient.new(options)
|
16
16
|
end
|
17
17
|
|
18
|
+
describe "read only mode" do
|
19
|
+
it "does not establish S3 connection on creation" do
|
20
|
+
AWS::S3::Base.should_not_receive(:establish_connection!)
|
21
|
+
@client = s3_blobstore("bucket_name" => "test")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should raise an error on deletion" do
|
25
|
+
@client = s3_blobstore("bucket_name" => "test")
|
26
|
+
lambda {
|
27
|
+
@client.delete("id")
|
28
|
+
}.should raise_error "unsupported action"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should raise an error on creation" do
|
32
|
+
@client = s3_blobstore("bucket_name" => "test")
|
33
|
+
lambda {
|
34
|
+
@client.create("id")
|
35
|
+
}.should raise_error "unsupported action"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should fetch objects" do
|
39
|
+
simple = mock("simple", :to_ary => nil, :get_file => %w[foo id])
|
40
|
+
Bosh::Blobstore::SimpleBlobstoreClient.should_receive(:new).and_return(simple)
|
41
|
+
@client = s3_blobstore("bucket_name" => "test")
|
42
|
+
@client.get_file("foo", "id")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
18
46
|
describe "options" do
|
19
47
|
|
20
48
|
it "establishes S3 connection on creation" do
|
@@ -73,6 +101,17 @@ describe Bosh::Blobstore::S3BlobstoreClient do
|
|
73
101
|
@client.create("some content").should eql("object_id")
|
74
102
|
end
|
75
103
|
|
104
|
+
it "should not encrypt when encryption key is missing" do
|
105
|
+
client = s3_blobstore(:bucket_name => "test",
|
106
|
+
:access_key_id => "KEY",
|
107
|
+
:secret_access_key => "SECRET")
|
108
|
+
client.should_receive(:generate_object_id).and_return("object_id")
|
109
|
+
client.should_not_receive(:encrypt_stream)
|
110
|
+
|
111
|
+
AWS::S3::S3Object.should_receive(:store)
|
112
|
+
client.create("some content").should eql("object_id")
|
113
|
+
end
|
114
|
+
|
76
115
|
it "should raise an exception when there is an error creating an object" do
|
77
116
|
encrypted_file = nil
|
78
117
|
@client.should_receive(:generate_object_id).and_return("object_id")
|
@@ -114,6 +153,19 @@ describe Bosh::Blobstore::S3BlobstoreClient do
|
|
114
153
|
@client.get("object_id").should == "stuff"
|
115
154
|
end
|
116
155
|
|
156
|
+
it "should not decrypt when encryption key is missing" do
|
157
|
+
client = s3_blobstore(:bucket_name => "test",
|
158
|
+
:access_key_id => "KEY",
|
159
|
+
:secret_access_key => "SECRET")
|
160
|
+
|
161
|
+
mock_s3_object = mock("s3_object")
|
162
|
+
mock_s3_object.stub!(:value).and_yield("stuff")
|
163
|
+
AWS::S3::S3Object.should_receive(:find).with("object_id", "test").and_return(mock_s3_object)
|
164
|
+
client.should_not_receive(:decrypt_stream)
|
165
|
+
|
166
|
+
client.get("object_id").should == "stuff"
|
167
|
+
end
|
168
|
+
|
117
169
|
it "should raise an exception when there is an error fetching an object" do
|
118
170
|
AWS::S3::S3Object.should_receive(:find).with("object_id", "test").and_raise(AWS::S3::S3Exception.new("Epic Fail"))
|
119
171
|
lambda {
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blobstore_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-08-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-s3
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: 0.6.2
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.6.2
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: httpclient
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
@@ -32,10 +37,15 @@ dependencies:
|
|
32
37
|
version: '2.2'
|
33
38
|
type: :runtime
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '2.2'
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: multi_json
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ~>
|
@@ -43,10 +53,15 @@ dependencies:
|
|
43
53
|
version: 1.1.0
|
44
54
|
type: :runtime
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.1.0
|
47
62
|
- !ruby/object:Gem::Dependency
|
48
63
|
name: ruby-atmos-pure
|
49
|
-
requirement:
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
50
65
|
none: false
|
51
66
|
requirements:
|
52
67
|
- - ~>
|
@@ -54,18 +69,44 @@ dependencies:
|
|
54
69
|
version: 1.0.5
|
55
70
|
type: :runtime
|
56
71
|
prerelease: false
|
57
|
-
version_requirements:
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.0.5
|
58
78
|
- !ruby/object:Gem::Dependency
|
59
79
|
name: uuidtools
|
60
|
-
requirement:
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 2.1.2
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
61
89
|
none: false
|
62
90
|
requirements:
|
63
91
|
- - ~>
|
64
92
|
- !ruby/object:Gem::Version
|
65
93
|
version: 2.1.2
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: bosh_common
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0.5'
|
66
102
|
type: :runtime
|
67
103
|
prerelease: false
|
68
|
-
version_requirements:
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.5'
|
69
110
|
description: BOSH blobstore client
|
70
111
|
email: support@vmware.com
|
71
112
|
executables: []
|
@@ -104,7 +145,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
145
|
version: '0'
|
105
146
|
segments:
|
106
147
|
- 0
|
107
|
-
hash:
|
148
|
+
hash: 4134087797262691141
|
108
149
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
150
|
none: false
|
110
151
|
requirements:
|
@@ -113,10 +154,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
154
|
version: '0'
|
114
155
|
segments:
|
115
156
|
- 0
|
116
|
-
hash:
|
157
|
+
hash: 4134087797262691141
|
117
158
|
requirements: []
|
118
159
|
rubyforge_project:
|
119
|
-
rubygems_version: 1.8.
|
160
|
+
rubygems_version: 1.8.24
|
120
161
|
signing_key:
|
121
162
|
specification_version: 3
|
122
163
|
summary: BOSH blobstore client
|