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 CHANGED
@@ -35,7 +35,6 @@ if defined?(RSpec)
35
35
  namespace :spec do
36
36
  desc "Run Unit Tests"
37
37
  rspec_task = RSpec::Core::RakeTask.new(:unit) do |t|
38
- t.gemfile = "Gemfile"
39
38
  t.pattern = "spec/unit/**/*_spec.rb"
40
39
  t.rspec_opts = %w(--format progress --colour)
41
40
  end
@@ -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(provider, options = {})
27
- p = PROVIDER_MAP[provider]
28
- if p
29
- p.new(options)
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 "Invalid client provider, available providers are: #{providers}"
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
- AWS::S3::Base.establish_connection!(aws_options)
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
- temp_path do |path|
38
- File.open(path, "w") do |temp_file|
39
- encrypt_stream(file, temp_file)
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
- File.open(path, "r") do |temp_file|
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
- decrypt_stream(from, file)
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}/resources", id].compact.join("/")
24
+ ["#{@endpoint}/#{@bucket}", id].compact.join("/")
24
25
  end
25
26
 
26
27
  def create_file(file)
@@ -3,7 +3,7 @@
3
3
  module Bosh
4
4
  module Blobstore
5
5
  class Client
6
- VERSION = "0.3.13"
6
+ VERSION = "0.4.0"
7
7
  end
8
8
  end
9
9
  end
@@ -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.3.13
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-03-23 00:00:00.000000000Z
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: &70295903087360 !ruby/object:Gem::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: *70295903087360
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: &70295903085860 !ruby/object:Gem::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: *70295903085860
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: &70295903084300 !ruby/object:Gem::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: *70295903084300
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: &70295903076320 !ruby/object:Gem::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: *70295903076320
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: &70295903075580 !ruby/object:Gem::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: *70295903075580
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: -2357219804209612013
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: -2357219804209612013
157
+ hash: 4134087797262691141
117
158
  requirements: []
118
159
  rubyforge_project:
119
- rubygems_version: 1.8.10
160
+ rubygems_version: 1.8.24
120
161
  signing_key:
121
162
  specification_version: 3
122
163
  summary: BOSH blobstore client