blobstore_client 0.3.13 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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