waz-storage 0.5.7 → 0.5.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -35,7 +35,7 @@ module WAZ
35
35
  end
36
36
  end
37
37
 
38
- attr_accessor :name, :url, :content_type
38
+ attr_accessor :name, :url, :content_type, :snapshot_date
39
39
 
40
40
  # Creates a new instance of the Blob object. This constructor is internally used by the Container
41
41
  # it's initialized thru a hash that's received as parameter. It has the following requirements:
@@ -48,6 +48,7 @@ module WAZ
48
48
  self.name = options[:name]
49
49
  self.url = options[:url]
50
50
  self.content_type = options[:content_type]
51
+ self.snapshot_date = options[:snapshot_date]
51
52
  end
52
53
 
53
54
  # Returns the blob properties from Windows Azure. This properties always come as HTTP Headers and they include standard headers like
@@ -65,6 +66,7 @@ module WAZ
65
66
  # Assigns the given value to the blob content. It also stores a local copy of it in order to avoid round trips
66
67
  # on scenarios when you do Save and Display on the same context.
67
68
  def value=(new_value)
69
+ raise WAZ::Blobs::InvalidOperation if self.snapshot_date
68
70
  self.class.service_instance.put_blob(path, new_value, content_type, metadata)
69
71
  @value = new_value
70
72
  end
@@ -72,6 +74,7 @@ module WAZ
72
74
  # Stores the blob properties. Those properties are sent as HTTP Headers it's really important that you name your custom
73
75
  # properties with the <em>x-ms-meta</em> prefix, if not the won't be persisted by the Windows Azure Blob Storage API.
74
76
  def put_properties!(properties = {})
77
+ raise WAZ::Blobs::InvalidOperation if self.snapshot_date
75
78
  self.class.service_instance.set_blob_properties(path, properties)
76
79
  end
77
80
 
@@ -92,6 +95,16 @@ module WAZ
92
95
  :content_type => properties[:content_type])
93
96
  end
94
97
 
98
+ # Creates and returns a read-only snapshot of a blob as it looked like in time.
99
+ def snapshot
100
+ date = self.class.service_instance.snapshot_blob(self.path)
101
+ properties = self.class.service_instance.get_blob_properties(self.path)
102
+ return BlobObject.new(:name => self.name,
103
+ :url => self.class.service_instance.generate_request_uri(self.path) + "?snapshot=#{date}",
104
+ :content_type => properties[:content_type],
105
+ :snapshot_date => date)
106
+ end
107
+
95
108
  # Returns the blob path. This is specially important when simulating containers inside containers
96
109
  # by enabling the API to point to the appropiated resource.
97
110
  def path
@@ -41,6 +41,7 @@ module WAZ
41
41
  class << self
42
42
  # Creates a new container with the given name.
43
43
  def create(name)
44
+ raise WAZ::Storage::InvalidParameterValue, {:name => "name", :values => ["lower letters, numbers or - (hypen), and must not start or end with - (hyphen)"]} unless WAZ::Storage::ValidationRules.valid_name?(name)
44
45
  service_instance.create_container(name)
45
46
  return Container.new(:name => name)
46
47
  end
@@ -0,0 +1,11 @@
1
+ module WAZ
2
+ module Blobs
3
+ # This exception is raised when the user tries to perform an operation over a snapshoted blob. Since
4
+ # Snapshots are read-only copies of the original blob they cannot be modified
5
+ class InvalidOperation < WAZ::Storage::StorageException
6
+ def initialize()
7
+ super("A snapshoted blob cannot be modified.")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -60,7 +60,7 @@ module WAZ
60
60
 
61
61
  # Lists all the blobs inside the given container.
62
62
  def list_blobs(container_name)
63
- content = execute(:get, container_name, { :comp => 'list' })
63
+ content = execute(:get, container_name, { :comp => 'list'})
64
64
  doc = REXML::Document.new(content)
65
65
  containers = []
66
66
  REXML::XPath.each(doc, '//Blob/') do |item|
@@ -79,17 +79,18 @@ module WAZ
79
79
  #
80
80
  # metadata is a hash that stores all the properties that you want to add to the blob when creating it.
81
81
  def put_blob(path, payload, content_type = "application/octet-stream", metadata = {})
82
- execute :put, path, nil, metadata.merge("Content-Type" => content_type), payload
82
+ default_headers = {"Content-Type" => content_type, :x_ms_version => "2009-09-19", :x_ms_blob_type => "BlockBlob"}
83
+ execute :put, path, nil, metadata.merge(default_headers), payload
83
84
  end
84
85
 
85
86
  # Retrieves a blob (content + headers) from the current path.
86
87
  def get_blob(path, options = {})
87
- execute :get, path, options
88
+ execute :get, path, options, {:x_ms_version => "2009-09-19"}
88
89
  end
89
90
 
90
91
  # Deletes the blob existing on the current path.
91
92
  def delete_blob(path)
92
- execute :delete, path
93
+ execute :delete, path, nil, {:x_ms_version => "2009-09-19"}
93
94
  end
94
95
 
95
96
  # Retrieves the properties associated with the blob at the given path.
@@ -104,7 +105,7 @@ module WAZ
104
105
 
105
106
  # Copies a blob within the same account (not necessarily to the same container)
106
107
  def copy_blob(source_path, dest_path)
107
- execute :put, dest_path, nil, { :x_ms_version => "2009-04-14", :x_ms_copy_source => canonicalize_message(source_path) }
108
+ execute :put, dest_path, nil, { :x_ms_version => "2009-09-19", :x_ms_copy_source => canonicalize_message(source_path) }
108
109
  end
109
110
 
110
111
  # Adds a block to the block list of the given blob
@@ -114,7 +115,7 @@ module WAZ
114
115
 
115
116
  # Retrieves the list of blocks associated with a single blob. The list is filtered (or not) by type of blob
116
117
  def list_blocks(path, block_list_type = 'all')
117
- raise WAZ::Storage::InvalidParameterValue , {:name => :bloclisttype, :values => ['all', 'uncommitted', 'committed']} unless (block_list_type or "") =~ /all|committed|uncommitted/i
118
+ raise WAZ::Storage::InvalidParameterValue , {:name => :blocklisttype, :values => ['all', 'uncommitted', 'committed']} unless (block_list_type or "") =~ /all|committed|uncommitted/i
118
119
  content = execute(:get, path, {:comp => 'blocklist'}.merge(:blocklisttype => block_list_type.downcase), { :x_ms_version => "2009-04-14" })
119
120
  doc = REXML::Document.new(content)
120
121
  blocks = []
@@ -125,6 +126,11 @@ module WAZ
125
126
  end
126
127
  return blocks
127
128
  end
129
+
130
+ # Creates a read-only snapshot of a blob as it looked like in time.
131
+ def snapshot_blob(path)
132
+ execute(:put, path, { :comp => 'snapshot' }, {:x_ms_version => "2009-09-19"}).headers[:x_ms_snapshot]
133
+ end
128
134
  end
129
135
  end
130
136
  end
@@ -7,15 +7,15 @@ module WAZ
7
7
  # WAZ::Queues::Queue.list
8
8
  #
9
9
  # # create a queue (here you can also send hashed metadata)
10
- # WAZ::Queues::Queue.create('my-container')
10
+ # WAZ::Queues::Queue.create('test-queue')
11
11
  #
12
12
  # # get a specific queue
13
- # queue = WAZ::Queues::Container.find('my-container')
13
+ # queue = WAZ::Queues::Queue.find('test-queue')
14
14
  #
15
15
  # # get queue properties (including default headers)
16
16
  # queue.metadata #=> hash containing beautified metadata (:x_ms_meta_name)
17
17
  #
18
- # # set container properties (should follow x-ms-meta to be persisted)
18
+ # # set queue properties (should follow x-ms-meta to be persisted)
19
19
  # # if you specify the optional parameter overwrite, existing metadata
20
20
  # # will be deleted else merged with new one.
21
21
  # queue.put_properties!(:x_ms_meta_MyProperty => "my value")
@@ -55,6 +55,7 @@ module WAZ
55
55
  # metadata to be stored on the queue. (Remember that metadata on the storage account must start with
56
56
  # :x_ms_metadata_{yourCustomPropertyName}, if not it will not be persisted).
57
57
  def create(queue_name, metadata = {})
58
+ raise WAZ::Storage::InvalidParameterValue, {:name => "name", :values => ["lower letters, numbers or - (hypen), and must not start or end with - (hyphen)"]} unless WAZ::Storage::ValidationRules.valid_name?(queue_name)
58
59
  service_instance.create_queue(queue_name, metadata)
59
60
  WAZ::Queues::Queue.new(:name => queue_name, :url => service_instance.generate_request_uri(queue_name))
60
61
  end
@@ -3,13 +3,14 @@ module WAZ
3
3
  # This module is imported by the specific services that use Shared Key authentication profile. On the current implementation
4
4
  # this module is imported from WAZ::Queues::Service and WAZ::Blobs::Service.
5
5
  module SharedKeyCoreService
6
- attr_accessor :account_name, :access_key, :use_ssl, :base_url
6
+ attr_accessor :account_name, :access_key, :use_ssl, :base_url, :type_of_service
7
7
 
8
8
  # Creates an instance of the implementor service (internally used by the API).
9
9
  def initialize(options = {})
10
10
  self.account_name = options[:account_name]
11
11
  self.access_key = options[:access_key]
12
12
  self.use_ssl = options[:use_ssl] or false
13
+ self.type_of_service = options[:type_of_service]
13
14
  self.base_url = "#{options[:type_of_service] or "blobs"}.#{options[:base_url] or "core.windows.net"}"
14
15
  end
15
16
 
@@ -19,11 +20,11 @@ module WAZ
19
20
  def generate_request(verb, url, headers = {}, payload = nil)
20
21
  http_headers = {}
21
22
  headers.each{ |k, v| http_headers[k.to_s.gsub(/_/, '-')] = v} unless headers.nil?
22
- request = RestClient::Request.new(:method => verb.to_s.downcase.to_sym, :url => url, :headers => http_headers, :payload => payload)
23
- request.headers["x-ms-Date"] = Time.new.httpdate
24
- request.headers["Content-Length"] = (request.payload or "").length
25
- request.headers["Authorization"] = "SharedKey #{account_name}:#{generate_signature(request)}"
26
- return request
23
+ http_headers.merge!("x-ms-Date" => Time.new.httpdate)
24
+ http_headers.merge!("Content-Length" => (payload or "").length)
25
+ request = {:headers => http_headers, :method => verb.to_s.downcase.to_sym, :url => url, :payload => payload}
26
+ request[:headers].merge!("Authorization" => "SharedKey #{account_name}:#{generate_signature(request)}")
27
+ return RestClient::Request.new(request)
27
28
  end
28
29
 
29
30
  # Generates the request uri based on the resource path, the protocol, the account name and the parameters passed
@@ -55,42 +56,43 @@ module WAZ
55
56
  # Generates the signature based on Micosoft specs for the REST API. It includes some special headers,
56
57
  # the canonicalized header line and the canonical form of the message, all of the joined by \n character. Encoded with
57
58
  # Base64 and encrypted with SHA256 using the access_key as the seed.
58
- def generate_signature(request)
59
- return generate_signature20090919(request) if request.headers["x-ms-version"] == "2009-09-19"
60
- signature = request.method.to_s.upcase + "\x0A" +
61
- (request.headers["Content-MD5"] or "") + "\x0A" +
62
- (request.headers["Content-Type"] or "") + "\x0A" +
63
- (request.headers["Date"] or "")+ "\x0A" +
64
- canonicalize_headers(request.headers) + "\x0A" +
65
- canonicalize_message(request.url)
59
+ def generate_signature(options = {})
60
+ return generate_signature20090919(options) if options[:headers]["x-ms-version"] == "2009-09-19"
61
+
62
+ signature = options[:method].to_s.upcase + "\x0A" +
63
+ (options[:headers]["Content-MD5"] or "") + "\x0A" +
64
+ (options[:headers]["Content-Type"] or "") + "\x0A" +
65
+ (options[:headers]["Date"] or "")+ "\x0A"
66
+
67
+ signature += canonicalize_headers(options[:headers]) + "\x0A" unless self.type_of_service == 'table'
68
+ signature += canonicalize_message(options[:url])
66
69
 
67
- return Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature.toutf8).digest)
70
+ Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature.toutf8).digest)
68
71
  end
69
-
70
-
71
- def generate_signature20090919(request)
72
- signature = request.method.to_s.upcase + "\x0A" +
73
- (request.headers["Content-Encoding"] or "") + "\x0A" +
74
- (request.headers["Content-Language"] or "") + "\x0A" +
75
- (request.headers["Content-Length"] or "").to_s + "\x0A" +
76
- (request.headers["Content-MD5"] or "") + "\x0A" +
77
- (request.headers["Content-Type"] or "") + "\x0A" +
78
- (request.headers["Date"] or "")+ "\x0A" +
79
- (request.headers["If-Modified-Since"] or "")+ "\x0A" +
80
- (request.headers["If-Match"] or "")+ "\x0A" +
81
- (request.headers["If-None-Match"] or "")+ "\x0A" +
82
- (request.headers["If-Unmodified-Since"] or "")+ "\x0A" +
83
- (request.headers["Range"] or "")+ "\x0A" +
84
- canonicalize_headers(request.headers) + "\x0A" +
85
- canonicalize_message20090919(request.url)
86
-
87
- return Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature.toutf8).digest)
72
+
73
+ def generate_signature20090919(options = {})
74
+ signature = options[:method].to_s.upcase + "\x0A" +
75
+ (options[:headers]["Content-Encoding"] or "") + "\x0A" +
76
+ (options[:headers]["Content-Language"] or "") + "\x0A" +
77
+ (options[:headers]["Content-Length"] or "").to_s + "\x0A" +
78
+ (options[:headers]["Content-MD5"] or "") + "\x0A" +
79
+ (options[:headers]["Content-Type"] or "") + "\x0A" +
80
+ (options[:headers]["Date"] or "")+ "\x0A" +
81
+ (options[:headers]["If-Modified-Since"] or "")+ "\x0A" +
82
+ (options[:headers]["If-Match"] or "")+ "\x0A" +
83
+ (options[:headers]["If-None-Match"] or "")+ "\x0A" +
84
+ (options[:headers]["If-Unmodified-Since"] or "")+ "\x0A" +
85
+ (options[:headers]["Range"] or "")+ "\x0A" +
86
+ canonicalize_headers(options[:headers]) + "\x0A" +
87
+ canonicalize_message20090919(options[:url])
88
+
89
+ Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature.toutf8).digest)
88
90
  end
89
91
 
90
92
  def canonicalize_message20090919(url)
91
93
  uri_component = url.gsub(/https?:\/\/[^\/]+\//i, '').gsub(/\?.*/i, '')
92
94
  query_component = (url.scan(/\?(.*)/i).first() or []).first()
93
- query_component = query_component.downcase.split('&').sort{|a, b| a <=> b}.map{ |p| p.split('=').join(':') }.join("\n") if query_component
95
+ query_component = query_component.split('&').sort{|a, b| a <=> b}.map{ |p| p.split('=').join(':') }.join("\n") if query_component
94
96
  canonicalized_message = "/#{self.account_name}/#{uri_component}"
95
97
  canonicalized_message << "\n#{query_component}" if query_component
96
98
  return canonicalized_message
@@ -0,0 +1,17 @@
1
+ module WAZ
2
+ module Storage
3
+ class ValidationRules
4
+ class << self
5
+ # Validates that the Container/Queue name given matches with the requirements of Windows Azure.
6
+ #
7
+ # -Container/Queue names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
8
+ # -Every dash (-) character must be immediately preceded and followed by a letter or number.
9
+ # -All letters in a container name must be lowercase.
10
+ # -Container/Queue names must be from 3 through 63 characters long.
11
+ def valid_name?(name)
12
+ name =~ /^[a-z0-9][a-z0-9\-]{1,}[^-]$/ && name.length < 64
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,7 +3,7 @@ module WAZ
3
3
  module VERSION #:nodoc:
4
4
  MAJOR = '0'
5
5
  MINOR = '5'
6
- TINY = '7'
6
+ TINY = '8'
7
7
  end
8
8
 
9
9
  Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY].compact * '.'
data/lib/waz-blobs.rb CHANGED
@@ -10,6 +10,7 @@ $:.unshift(File.dirname(__FILE__))
10
10
  require 'waz-storage'
11
11
  require 'waz/blobs/blob_object'
12
12
  require 'waz/blobs/container'
13
+ require 'waz/blobs/exceptions'
13
14
  require 'waz/blobs/service'
14
15
 
15
16
 
data/lib/waz-storage.rb CHANGED
@@ -3,6 +3,7 @@ require 'waz/storage/base'
3
3
  require 'waz/storage/core_service'
4
4
  require 'waz/storage/exceptions'
5
5
  require 'waz/storage/version'
6
+ require 'waz/storage/validation_rules'
6
7
 
7
8
  # It will depende on which version of Ruby (or if you have Rails)
8
9
  # but this method is required so we will add it the String class.
data/rakefile CHANGED
@@ -31,7 +31,7 @@ namespace :dist do
31
31
  s.rdoc_options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
32
32
 
33
33
  # Dependencies
34
- s.add_runtime_dependency 'rest-client', '>= 1.0.3'
34
+ s.add_dependency 'rest-client'
35
35
  s.add_dependency 'ruby-hmac'
36
36
  end
37
37
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: waz-storage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.7
4
+ version: 0.5.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johnny G. Halife
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-08 00:00:00 -03:00
12
+ date: 2010-02-03 00:00:00 -03:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 1.0.3
23
+ version: "0"
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: ruby-hmac
@@ -44,6 +44,7 @@ files:
44
44
  - rakefile
45
45
  - lib/waz/blobs/blob_object.rb
46
46
  - lib/waz/blobs/container.rb
47
+ - lib/waz/blobs/exceptions.rb
47
48
  - lib/waz/blobs/service.rb
48
49
  - lib/waz/queues/exceptions.rb
49
50
  - lib/waz/queues/message.rb
@@ -52,6 +53,7 @@ files:
52
53
  - lib/waz/storage/base.rb
53
54
  - lib/waz/storage/core_service.rb
54
55
  - lib/waz/storage/exceptions.rb
56
+ - lib/waz/storage/validation_rules.rb
55
57
  - lib/waz/storage/version.rb
56
58
  - lib/waz-blobs.rb
57
59
  - lib/waz-queues.rb