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.
- data/lib/waz/blobs/blob_object.rb +14 -1
- data/lib/waz/blobs/container.rb +1 -0
- data/lib/waz/blobs/exceptions.rb +11 -0
- data/lib/waz/blobs/service.rb +12 -6
- data/lib/waz/queues/queue.rb +4 -3
- data/lib/waz/storage/core_service.rb +37 -35
- data/lib/waz/storage/validation_rules.rb +17 -0
- data/lib/waz/storage/version.rb +1 -1
- data/lib/waz-blobs.rb +1 -0
- data/lib/waz-storage.rb +1 -0
- data/rakefile +1 -1
- metadata +5 -3
@@ -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
|
data/lib/waz/blobs/container.rb
CHANGED
@@ -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
|
data/lib/waz/blobs/service.rb
CHANGED
@@ -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
|
-
|
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-
|
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 => :
|
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
|
data/lib/waz/queues/queue.rb
CHANGED
@@ -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('
|
10
|
+
# WAZ::Queues::Queue.create('test-queue')
|
11
11
|
#
|
12
12
|
# # get a specific queue
|
13
|
-
# queue = WAZ::Queues::
|
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
|
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
|
-
|
23
|
-
|
24
|
-
request
|
25
|
-
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(
|
59
|
-
return generate_signature20090919(
|
60
|
-
|
61
|
-
|
62
|
-
(
|
63
|
-
(
|
64
|
-
|
65
|
-
|
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
|
-
|
70
|
+
Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature.toutf8).digest)
|
68
71
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
(
|
74
|
-
(
|
75
|
-
(
|
76
|
-
(
|
77
|
-
(
|
78
|
-
(
|
79
|
-
(
|
80
|
-
(
|
81
|
-
(
|
82
|
-
(
|
83
|
-
(
|
84
|
-
|
85
|
-
|
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.
|
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
|
data/lib/waz/storage/version.rb
CHANGED
data/lib/waz-blobs.rb
CHANGED
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
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.
|
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:
|
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:
|
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
|