sndacs 0.0.1 → 0.1.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/Gemfile.lock CHANGED
@@ -20,7 +20,6 @@ GEM
20
20
  rspec-expectations (2.9.0)
21
21
  diff-lcs (~> 1.1.3)
22
22
  rspec-mocks (2.9.0)
23
- test-unit (2.4.8)
24
23
 
25
24
  PLATFORMS
26
25
  ruby
@@ -30,4 +29,3 @@ DEPENDENCIES
30
29
  mocha
31
30
  rspec (~> 2.0)
32
31
  sndacs!
33
- test-unit (>= 2.0)
data/README.rdoc CHANGED
@@ -13,8 +13,26 @@ bases on the s3 gem: https://github.com/qoobaa/s3
13
13
  === Initialize the service
14
14
 
15
15
  require "sndacs"
16
- service = Sndacs::Service.new(:access_key_id => "...",
17
- :secret_access_key => "...")
16
+
17
+ service = Sndacs::Service.new(:access_key_id => '...'
18
+ :secret_access_key => '...')
19
+ #=> #<Sndacs::Service:...>
20
+
21
+ or use even more flexible config style
22
+
23
+ require "sndacs"
24
+
25
+ # Setup global default configurations, see Sndacs::Config for more info
26
+ Config.access_key_id = '...'
27
+ Config.secret_access_key = '...'
28
+ Config.host = 'storage.grandcloud.cn'
29
+ Config.content_host = 'storage.sdcloud.cn'
30
+ Config.proxy = nil
31
+ Config.timeout = 60
32
+ Config.use_ssl = false
33
+ Config.chunk_size = 1048576
34
+ Config.debug = false
35
+ service = Sndacs::Service.new
18
36
  #=> #<Sndacs::Service:...>
19
37
 
20
38
  === List buckets
@@ -64,7 +82,7 @@ bases on the s3 gem: https://github.com/qoobaa/s3
64
82
  new_object.save
65
83
  #=> true
66
84
 
67
- Please note that new objects are created with "public-read" ACL by
85
+ Please note that new objects are created with "private" ACL by
68
86
  default.
69
87
 
70
88
  == See also
data/lib/sndacs/bucket.rb CHANGED
@@ -1,83 +1,101 @@
1
1
  module Sndacs
2
+
2
3
  class Bucket
3
4
  include Parser
4
5
  include Proxies
5
6
  extend Forwardable
6
7
 
7
- attr_reader :name, :service
8
+ attr_reader :name, :location, :service
8
9
 
9
10
  def_instance_delegators :service, :service_request
10
11
  private_class_method :new
11
12
 
13
+ # Compares the bucket with other bucket. Returns true if the names
14
+ # of the buckets are the same, and both have the same locations and services
15
+ # (see Service equality)
16
+ def ==(other)
17
+ self.name == other.name and self.location == other.location and self.service == other.service
18
+ end
19
+
12
20
  # Retrieves the bucket information from the server. Raises an
13
- # S3::Error exception if the bucket doesn't exist or you don't
21
+ # Sndacs::Error exception if the bucket doesn't exist or you don't
14
22
  # have access to it, etc.
15
23
  def retrieve
16
24
  bucket_headers
25
+
17
26
  self
18
27
  end
19
28
 
20
- # Returns location of the bucket, e.g. "EU"
29
+ # Returns location of the bucket, e.g. "huabei-1"
21
30
  def location(reload = false)
22
31
  return @location if defined?(@location) and not reload
32
+
23
33
  @location = location_constraint
24
34
  end
25
35
 
26
- # Compares the bucket with other bucket. Returns true if the names
27
- # of the buckets are the same, and both have the same services
28
- # (see Service equality)
29
- def ==(other)
30
- self.name == other.name and self.service == other.service
31
- end
36
+ # Saves the newly built bucket. Raises Sndacs::Error::BucketAlreadyExists
37
+ # exception if the bucket already exists.
38
+ #
39
+ # ==== Options
40
+ # * <tt>:location</tt> - location of the bucket
41
+ # (<tt>huabei-1</tt> or <tt>huadong-1</tt>)
42
+ # * Any other options are passed through to Connection#request
43
+ def save(options = {})
44
+ if options
45
+ if options.is_a?(String) && options.strip != ''
46
+ options = {:location => options.strip}
47
+ end
48
+
49
+ if options.is_a?(Hash) && !options.has_key?(:location)
50
+ options.merge!(:location => location)
51
+ end
52
+ else
53
+ options = {:location => location}
54
+ end
55
+
56
+ create_bucket_configuration(options)
32
57
 
33
- # Similar to retrieve, but catches S3::Error::NoSuchBucket
34
- # exceptions and returns false instead.
35
- def exists?
36
- retrieve
37
58
  true
38
- rescue Error::NoSuchBucket
39
- false
40
59
  end
41
60
 
42
- # Destroys given bucket. Raises an S3::Error::BucketNotEmpty
43
- # exception if the bucket is not empty. You can destroy non-empty
44
- # bucket passing true (to force destroy)
61
+ # Destroys given bucket. Raises an Sndacs::Error::BucketNotEmpty
62
+ # exception if the bucket is not empty.
63
+ # You can destroy non-empty bucket passing true (to force destroy)
45
64
  def destroy(force = false)
46
65
  delete_bucket
66
+
47
67
  true
48
68
  rescue Error::BucketNotEmpty
49
69
  if force
50
70
  objects.destroy_all
71
+
51
72
  retry
52
73
  else
53
74
  raise
54
75
  end
55
76
  end
56
77
 
57
- # Saves the newly built bucket.
58
- #
59
- # ==== Options
60
- # * <tt>:location</tt> - location of the bucket
61
- # (<tt>:eu</tt> or <tt>us</tt>)
62
- # * Any other options are passed through to
63
- # Connection#request
64
- def save(options = {})
65
- options = {:location => options} unless options.is_a?(Hash)
66
- create_bucket_configuration(options)
78
+ # Similar to retrieve, but catches Sndacs::Error::NoSuchBucket
79
+ # exceptions and returns false instead.
80
+ def exists?
81
+ retrieve
82
+
67
83
  true
84
+ rescue Error::NoSuchBucket
85
+ false
68
86
  end
69
87
 
70
- # Returns true if the name of the bucket can be used like +VHOST+
71
- # name. If the bucket contains characters like underscore it can't
72
- # be used as +VHOST+ (e.g. <tt>bucket_name.s3.amazonaws.com</tt>)
88
+ # Returns true if the name of the bucket can be used like +VHOST+ name.
89
+ # If the bucket contains characters like underscore it can't
90
+ # be used as +VHOST+ (e.g. <tt>bucket_name.storage.grandcloud.cn</tt>)
73
91
  def vhost?
74
- #"#@name.#{HOST}" =~ /\A#{URI::REGEXP::PATTERN::HOSTNAME}\Z/
92
+ #"#@name.#{region_host}" =~ /\A#{URI::REGEXP::PATTERN::HOSTNAME}\Z/
75
93
  false
76
94
  end
77
95
 
78
96
  # Returns host name of the bucket according (see #vhost? method)
79
- def host
80
- vhost? ? "#@name.#{HOST}" : "#{HOST}"
97
+ def host(public_accessible = false)
98
+ vhost? ? "#@name.#{region_host(public_accessible)}" : region_host(public_accessible)
81
99
  end
82
100
 
83
101
  # Returns path prefix for non +VHOST+ bucket. Path prefix is used
@@ -102,19 +120,26 @@ module Sndacs
102
120
  "#<#{self.class}:#{name}>"
103
121
  end
104
122
 
105
- private
123
+ private
106
124
 
107
125
  attr_writer :service
108
126
 
109
- def location_constraint
110
- response = bucket_request(:get, :params => {:location => nil})
111
- parse_location_constraint(response.body)
127
+ def create_bucket_configuration(options = {})
128
+ location = options[:location].to_s.downcase if options[:location]
129
+
130
+ options[:headers] ||= {}
131
+ if location and location != REGION_DEFAULT
132
+ options[:headers][:content_type] = "application/xml"
133
+ options[:body] = "<CreateBucketConfiguration><LocationConstraint>#{location}</LocationConstraint></CreateBucketConfiguration>"
134
+ end
135
+
136
+ bucket_request(:put, options)
112
137
  end
113
138
 
114
139
  def list_bucket(options = {})
115
140
  response = bucket_request(:get, :params => options)
116
141
  max_keys = options[:max_keys]
117
- objects_attributes = parse_list_bucket_result(response.body)
142
+ objects_attributes = parse_all_objects_result(response.body)
118
143
 
119
144
  # If there are more than 1000 objects S3 truncates listing and
120
145
  # we need to request another listing for the remaining objects.
@@ -123,11 +148,12 @@ module Sndacs
123
148
 
124
149
  if max_keys
125
150
  break if objects_attributes.length >= max_keys
151
+
126
152
  next_request_options[:max_keys] = max_keys - objects_attributes.length
127
153
  end
128
154
 
129
155
  response = bucket_request(:get, :params => options.merge(next_request_options))
130
- objects_attributes += parse_list_bucket_result(response.body)
156
+ objects_attributes += parse_all_objects_result(response.body)
131
157
  end
132
158
 
133
159
  objects_attributes.map { |object_attributes| Object.send(:new, self, object_attributes) }
@@ -143,37 +169,59 @@ module Sndacs
143
169
  end
144
170
  end
145
171
 
146
- def create_bucket_configuration(options = {})
147
- location = options[:location].to_s.upcase if options[:location]
148
- options[:headers] ||= {}
149
- if location and location != "US"
150
- options[:body] = "<CreateBucketConfiguration><LocationConstraint>#{location}</LocationConstraint></CreateBucketConfiguration>"
151
- options[:headers][:content_type] = "application/xml"
152
- end
153
- bucket_request(:put, options)
172
+ def location_constraint
173
+ response = bucket_request(:get, :params => {:location => nil})
174
+
175
+ parse_location_constraint(response.body)
154
176
  end
155
177
 
156
178
  def delete_bucket
157
179
  bucket_request(:delete)
158
180
  end
159
181
 
160
- def initialize(service, name) #:nodoc:
182
+ def initialize(service, name, location = nil) #:nodoc:
161
183
  self.service = service
162
184
  self.name = name
185
+
186
+ unless location
187
+ begin
188
+ location = location_constraint
189
+ rescue
190
+ location = REGION_DEFAULT
191
+ end
192
+ end
193
+
194
+ self.location = location
163
195
  end
164
196
 
165
197
  def name=(name)
166
198
  raise ArgumentError.new("Invalid bucket name: #{name}") unless name_valid?(name)
199
+
167
200
  @name = name
168
201
  end
169
202
 
170
- def bucket_request(method, options = {})
171
- path = "#{path_prefix}#{options[:path]}"
172
- service_request(method, options.merge(:host => host, :path => path))
203
+ def location=(location)
204
+ @location = location
173
205
  end
174
206
 
175
207
  def name_valid?(name)
176
208
  name =~ /\A[a-z0-9][a-z0-9\._-]{2,254}\Z/i and name !~ /\A#{URI::REGEXP::PATTERN::IPV4ADDR}\Z/
177
209
  end
210
+
211
+ def region_host(public_accessible = false)
212
+ rhost = public_accessible ? Config.content_host : Config.host
213
+ if @location
214
+ rhost = (public_accessible ? REGION_CONTENT_HOST : REGION_HOST) % @location
215
+ end
216
+
217
+ rhost
218
+ end
219
+
220
+ def bucket_request(method, options = {})
221
+ path = "#{path_prefix}#{options[:path]}"
222
+
223
+ service_request(method, options.merge(:host => host, :path => path))
224
+ end
178
225
  end
226
+
179
227
  end
@@ -1,4 +1,5 @@
1
1
  module Sndacs
2
+
2
3
  module BucketsExtension
3
4
  # Builds new bucket with given name
4
5
  def build(name)
@@ -8,7 +9,10 @@ module Sndacs
8
9
  # Finds the bucket with given name
9
10
  def find_first(name)
10
11
  bucket = build(name)
12
+
11
13
  bucket.retrieve
14
+ rescue Error::NoSuchBucket, Error::ForbiddenBucket
15
+ nil
12
16
  end
13
17
  alias :find :find_first
14
18
 
@@ -23,4 +27,5 @@ module Sndacs
23
27
  proxy_target.each { |bucket| bucket.destroy(force) }
24
28
  end
25
29
  end
30
+
26
31
  end
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: utf-8 -*-
3
+
4
+ module Sndacs
5
+
6
+ class Config
7
+
8
+ class << self
9
+ attr_accessor :access_key_id
10
+ attr_accessor :secret_access_key
11
+
12
+ attr_accessor :proxy
13
+ attr_accessor :host
14
+ attr_accessor :content_host
15
+ attr_accessor :timeout
16
+ attr_accessor :use_ssl
17
+ attr_accessor :chunk_size
18
+ attr_accessor :debug
19
+
20
+ def access_key_id
21
+ @access_key_id ||= ''
22
+ end
23
+
24
+ def secret_access_key
25
+ @secret_access_key ||= ''
26
+ end
27
+
28
+ def proxy
29
+ @proxy ||= nil
30
+ end
31
+
32
+ def host
33
+ @host ||= 'storage.grandcloud.cn'
34
+ end
35
+
36
+ def content_host
37
+ @content_host ||= 'storage.sdcloud.cn'
38
+ end
39
+
40
+ def timeout
41
+ @timeout ||= 60
42
+ end
43
+
44
+ def use_ssl
45
+ @use_ssl ||= false
46
+ end
47
+
48
+ def chunk_size
49
+ @chunk_size ||= 1048576
50
+ end
51
+
52
+ def debug
53
+ @debug ||= false
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -1,7 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: utf-8 -*-
3
+
1
4
  module Sndacs
2
5
 
3
- # Class responsible for handling connections to amazon hosts
6
+ # Class responsible for handling connections to grandcloud hosts
4
7
  class Connection
8
+
5
9
  include Parser
6
10
 
7
11
  attr_accessor :access_key_id, :secret_access_key, :use_ssl, :timeout, :debug, :proxy
@@ -24,13 +28,13 @@ module Sndacs
24
28
  # * <tt>:chunk_size</tt> - Size of a chunk when streaming
25
29
  # (1048576 (1 MiB) by default)
26
30
  def initialize(options = {})
27
- @access_key_id = options.fetch(:access_key_id)
28
- @secret_access_key = options.fetch(:secret_access_key)
29
- @use_ssl = options.fetch(:use_ssl, false)
30
- @debug = options.fetch(:debug, false)
31
- @timeout = options.fetch(:timeout, 60)
32
- @proxy = options.fetch(:proxy, nil)
33
- @chunk_size = options.fetch(:chunk_size, 1048576)
31
+ @access_key_id = options.fetch(:access_key_id, Config.access_key_id)
32
+ @secret_access_key = options.fetch(:secret_access_key, Config.secret_access_key)
33
+ @proxy = options.fetch(:proxy, Config.proxy)
34
+ @timeout = options.fetch(:timeout, Config.timeout)
35
+ @use_ssl = options.fetch(:use_ssl, Config.use_ssl)
36
+ @chunk_size = options.fetch(:chunk_size, Config.chunk_size)
37
+ @debug = options.fetch(:debug, Config.debug)
34
38
  end
35
39
 
36
40
  # Makes request with given HTTP method, sets missing parameters,
@@ -43,7 +47,7 @@ module Sndacs
43
47
  #
44
48
  # ==== Options:
45
49
  # * <tt>:host</tt> - Hostname to connecto to, defaults
46
- # to <tt>s3.amazonaws.com</tt>
50
+ # to <tt>storage.grandcloud.cn</tt>
47
51
  # * <tt>:path</tt> - path to send request to (REQUIRED)
48
52
  # * <tt>:body</tt> - Request body, only meaningful for
49
53
  # <tt>:put</tt> request
@@ -55,7 +59,7 @@ module Sndacs
55
59
  # ==== Returns
56
60
  # Net::HTTPResponse object -- response from the server
57
61
  def request(method, options)
58
- host = options.fetch(:host, HOST)
62
+ host = options.fetch(:host, Config.host)
59
63
  path = options.fetch(:path)
60
64
  body = options.fetch(:body, nil)
61
65
  params = options.fetch(:params, {})
@@ -67,7 +71,10 @@ module Sndacs
67
71
 
68
72
  if params
69
73
  params = params.is_a?(String) ? params : self.class.parse_params(params)
70
- path << "?#{params}"
74
+
75
+ if params != ''
76
+ path << "?#{params}"
77
+ end
71
78
  end
72
79
 
73
80
  request = Request.new(@chunk_size, method.to_s.upcase, !!body, method.to_s.upcase != "HEAD", path)
@@ -83,6 +90,7 @@ module Sndacs
83
90
  else
84
91
  request.body = body
85
92
  end
93
+
86
94
  request.content_length = body.respond_to?(:lstat) ? body.stat.size : body.size
87
95
  end
88
96
 
@@ -105,6 +113,7 @@ module Sndacs
105
113
  params.each do |key, value|
106
114
  if interesting_keys.include?(key)
107
115
  parsed_key = key.to_s.gsub("_", "-")
116
+
108
117
  case value
109
118
  when nil
110
119
  result << parsed_key
@@ -113,6 +122,7 @@ module Sndacs
113
122
  end
114
123
  end
115
124
  end
125
+
116
126
  result.join("&")
117
127
  end
118
128
 
@@ -144,18 +154,21 @@ module Sndacs
144
154
  if interesting_keys.include?(key)
145
155
  parsed_key = key.to_s.gsub("_", "-")
146
156
  parsed_value = value
157
+
147
158
  case value
148
159
  when Range
149
160
  parsed_value = "bytes=#{value.first}-#{value.last}"
150
161
  end
162
+
151
163
  parsed_headers[parsed_key] = parsed_value
152
164
  end
153
165
  end
154
166
  end
167
+
155
168
  parsed_headers
156
169
  end
157
170
 
158
- private
171
+ private
159
172
 
160
173
  def port
161
174
  use_ssl ? 443 : 80
@@ -171,6 +184,7 @@ module Sndacs
171
184
  http.use_ssl = @use_ssl
172
185
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @use_ssl
173
186
  http.read_timeout = @timeout if @timeout
187
+
174
188
  http
175
189
  end
176
190
 
@@ -195,9 +209,17 @@ module Sndacs
195
209
  http.request(request)
196
210
  end
197
211
 
198
- if response.code.to_i == 307
212
+ case response.code.to_i
213
+ when 301
214
+ if response.body
215
+ doc = Document.new response.body
216
+
217
+ send_request(doc.elements["Error"].elements["Endpoint"].text, request, true)
218
+ end
219
+ when 307
199
220
  if response.body
200
221
  doc = Document.new response.body
222
+
201
223
  send_request(doc.elements["Error"].elements["Endpoint"].text, request, true)
202
224
  end
203
225
  else
@@ -214,12 +236,15 @@ module Sndacs
214
236
  raise Error::ResponseError.new(nil, response)
215
237
  else
216
238
  code, message = parse_error(response.body)
239
+
217
240
  raise Error::ResponseError.exception(code).new(message, response)
218
241
  end
219
242
  else
220
243
  raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
221
244
  end
245
+
222
246
  response
223
247
  end
224
248
  end
249
+
225
250
  end
@@ -1,4 +1,5 @@
1
1
  module Sndacs
2
+
2
3
  module Error
3
4
 
4
5
  # All responses with a code between 300 and 599 that contain an
@@ -12,7 +13,7 @@ module Sndacs
12
13
  class ResponseError < StandardError
13
14
  attr_reader :response
14
15
 
15
- # Creates new S3::ResponseError.
16
+ # Creates new Sndacs::ResponseError.
16
17
  #
17
18
  # ==== Parameters
18
19
  # * <tt>message</tt> - what went wrong
@@ -23,7 +24,7 @@ module Sndacs
23
24
  end
24
25
 
25
26
  # Factory for all other Exception classes in module, each for
26
- # every error response available from AmazonAWS
27
+ # every error response available from SNDA Cloud Storage
27
28
  #
28
29
  # ==== Parameters
29
30
  # * <tt>code</tt> - Code name of exception
@@ -32,7 +33,7 @@ module Sndacs
32
33
  # Descendant of ResponseError suitable for that exception code
33
34
  # or ResponseError class if no class found
34
35
  def self.exception(code)
35
- S3::Error.const_get(code)
36
+ Sndacs::Error.const_get(code)
36
37
  rescue NameError
37
38
  ResponseError
38
39
  end
@@ -52,6 +53,7 @@ module Sndacs
52
53
  class EntityTooSmall < ResponseError; end
53
54
  class EntityTooLarge < ResponseError; end
54
55
  class ExpiredToken < ResponseError; end
56
+ class ForbiddenBucket < ResponseError; end
55
57
  class IncompleteBody < ResponseError; end
56
58
  class IncorrectNumberOfFilesInPostRequestPOST < ResponseError; end
57
59
  class InlineDataTooLarge < ResponseError; end
@@ -107,4 +109,5 @@ module Sndacs
107
109
  class UnresolvableGrantByEmailAddress < ResponseError; end
108
110
  class UserKeyMustBeSpecified < ResponseError; end
109
111
  end
112
+
110
113
  end