waz-storage 0.5.7 → 0.5.8

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.
@@ -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