waz-blobs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/waz/blobs/base.rb +22 -0
- data/lib/waz/blobs/blob_object.rb +46 -0
- data/lib/waz/blobs/container.rb +85 -0
- data/lib/waz/blobs/exceptions.rb +12 -0
- data/lib/waz/blobs/service.rb +144 -0
- data/lib/waz/blobs/version.rb +11 -0
- data/lib/waz-blobs.rb +19 -0
- data/rakefile +40 -0
- metadata +81 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Blobs
|
3
|
+
class Base
|
4
|
+
class << self
|
5
|
+
def establish_connection!(options = {})
|
6
|
+
raise InvalidOption.new(:account_name) unless options.keys.include? :account_name
|
7
|
+
raise InvalidOption.new(:access_key) unless options.keys.include? :access_key
|
8
|
+
options[:use_ssl] = false unless options.keys.include? :use_ssl
|
9
|
+
@connection = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_connection
|
13
|
+
return @connection
|
14
|
+
end
|
15
|
+
|
16
|
+
def connected?
|
17
|
+
return !@connection.nil?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Blobs
|
3
|
+
class BlobObject
|
4
|
+
attr_accessor :name, :url, :content_type
|
5
|
+
|
6
|
+
def initialize(name, url, content_type)
|
7
|
+
self.name = name
|
8
|
+
self.url = url
|
9
|
+
self.content_type = content_type
|
10
|
+
end
|
11
|
+
|
12
|
+
def metadata
|
13
|
+
@properties ||= service_instance.get_blob_properties(path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def value
|
17
|
+
@value ||= service_instance.get_blob(path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def value=(new_value)
|
21
|
+
service_instance.put_blob(path, new_value, content_type, metadata)
|
22
|
+
@value = new_value
|
23
|
+
end
|
24
|
+
|
25
|
+
def put_properties(properties = {})
|
26
|
+
service_instance.set_blob_properties(path, properties)
|
27
|
+
@properties = metadata.merge(properties)
|
28
|
+
end
|
29
|
+
|
30
|
+
def destroy!
|
31
|
+
service_instance.delete_blob(path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def path
|
35
|
+
url.gsub(/https?:\/\/[^\/]+\//i, '').scan(/([^&]+)/i).first().first()
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def service_instance
|
40
|
+
options = Base.default_connection
|
41
|
+
@service_instance ||= Service.new(options[:account_name], options[:access_key])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Blobs
|
3
|
+
class Container
|
4
|
+
#Singleton methods
|
5
|
+
class << self
|
6
|
+
def create(name)
|
7
|
+
service_instance.create_container(name)
|
8
|
+
return Container.new(name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def find(name)
|
12
|
+
begin
|
13
|
+
properties = service_instance.get_container_properties(name)
|
14
|
+
return Container.new(name, properties)
|
15
|
+
rescue RestClient::ResourceNotFound
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def service_instance
|
22
|
+
options = Base.default_connection
|
23
|
+
return Service.new(options[:account_name], options[:access_key])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_accessor :name, :properties, :public_access
|
28
|
+
|
29
|
+
def initialize(name, metadata = nil)
|
30
|
+
self.name = name
|
31
|
+
self.properties = metadata
|
32
|
+
end
|
33
|
+
|
34
|
+
def metadata
|
35
|
+
self.properties ||= service_instance.get_container_properties(self.name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def put_properties(properties = {})
|
39
|
+
service_instance.set_container_properties(self.name, properties)
|
40
|
+
self.properties = metadata.merge!(properties)
|
41
|
+
end
|
42
|
+
|
43
|
+
def destroy!
|
44
|
+
service_instance.delete_container(self.name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def public_access?
|
48
|
+
public_access ||= service_instance.get_container_acl(self.name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def public_access=(value)
|
52
|
+
public_access = value
|
53
|
+
service_instance.set_container_acl(self.name, value)
|
54
|
+
end
|
55
|
+
|
56
|
+
def blobs
|
57
|
+
service_instance.list_blobs(name).map { |blob| WAZ::Blobs::BlobObject.new(blob[:name], blob[:url], blob[:content_type]) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def store(blob_name, payload, content_type, options = {})
|
61
|
+
service_instance.put_blob("#{self.name}/#{blob_name}", payload, content_type, options)
|
62
|
+
return BlobObject.new(blob_name,
|
63
|
+
service_instance.generate_request_uri(nil, "#{self.name}/#{blob_name}"),
|
64
|
+
content_type)
|
65
|
+
end
|
66
|
+
|
67
|
+
def [](blob_name)
|
68
|
+
begin
|
69
|
+
properties = service_instance.get_blob_properties("#{self.name}/#{blob_name}")
|
70
|
+
return BlobObject.new(blob_name,
|
71
|
+
service_instance.generate_request_uri(nil, "#{self.name}/#{blob_name}"),
|
72
|
+
properties[:content_type])
|
73
|
+
rescue RestClient::ResourceNotFound
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def service_instance
|
80
|
+
options = Base.default_connection
|
81
|
+
@service_instance ||= Service.new(options[:account_name], options[:access_key])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Blobs
|
3
|
+
class WAZStorageException < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class InvalidOption < WAZStorageException
|
7
|
+
def initialize(missing_option)
|
8
|
+
super("You did not provide both required access keys. Please provide the #{missing_option}.")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Blobs
|
3
|
+
class Service
|
4
|
+
attr_accessor :account_name, :account_key, :use_ssl, :base_url
|
5
|
+
|
6
|
+
def initialize(account_name, account_key, use_ssl = false, base_url = "blob.core.windows.net" )
|
7
|
+
self.account_name = account_name
|
8
|
+
self.account_key = account_key
|
9
|
+
self.use_ssl = use_ssl
|
10
|
+
self.base_url = base_url
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_container(container_name)
|
14
|
+
url = generate_request_uri(nil, container_name)
|
15
|
+
request = generate_request("PUT", url)
|
16
|
+
request.execute()
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_container_properties(container_name)
|
20
|
+
url = generate_request_uri(nil, container_name)
|
21
|
+
request = generate_request("GET", url)
|
22
|
+
request.execute().headers
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_container_properties(container_name, properties = {})
|
26
|
+
url = generate_request_uri("metadata", container_name)
|
27
|
+
request = generate_request("PUT", url, properties)
|
28
|
+
request.execute()
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_container_acl(container_name)
|
32
|
+
url = generate_request_uri("acl", container_name)
|
33
|
+
request = generate_request("GET", url)
|
34
|
+
request.execute().headers[:x_ms_prop_publicaccess].downcase == true.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_container_acl(container_name, public_available = false)
|
38
|
+
url = generate_request_uri("acl", container_name)
|
39
|
+
request = generate_request("PUT", url, "x-ms-prop-publicaccess" => public_available.to_s)
|
40
|
+
request.execute()
|
41
|
+
end
|
42
|
+
|
43
|
+
def list_containers(options = {})
|
44
|
+
url = generate_request_uri("list", nil, options)
|
45
|
+
request = generate_request("GET", url)
|
46
|
+
doc = REXML::Document.new(request.execute())
|
47
|
+
containers = []
|
48
|
+
REXML::XPath.each(doc, '//Container/') do |item|
|
49
|
+
containers << { :name => REXML::XPath.first(item, "Name").text,
|
50
|
+
:url => REXML::XPath.first(item, "Url").text,
|
51
|
+
:last_modified => REXML::XPath.first(item, "LastModified").text}
|
52
|
+
end
|
53
|
+
return containers
|
54
|
+
end
|
55
|
+
|
56
|
+
def delete_container(container_name)
|
57
|
+
url = generate_request_uri(nil, container_name)
|
58
|
+
request = generate_request("DELETE", url)
|
59
|
+
request.execute()
|
60
|
+
end
|
61
|
+
|
62
|
+
def list_blobs(container_name)
|
63
|
+
url = generate_request_uri("list", container_name)
|
64
|
+
request = generate_request("GET", url)
|
65
|
+
doc = REXML::Document.new(request.execute())
|
66
|
+
containers = []
|
67
|
+
REXML::XPath.each(doc, '//Blob/') do |item|
|
68
|
+
containers << { :name => REXML::XPath.first(item, "Name").text,
|
69
|
+
:url => REXML::XPath.first(item, "Url").text,
|
70
|
+
:content_type => REXML::XPath.first(item, "ContentType").text }
|
71
|
+
end
|
72
|
+
return containers
|
73
|
+
end
|
74
|
+
|
75
|
+
def put_blob(path, payload, content_type = "application/octet-stream", metadata = {})
|
76
|
+
url = generate_request_uri(nil, path)
|
77
|
+
request = generate_request("PUT", url, metadata.merge("Content-Type" => content_type), payload)
|
78
|
+
request.execute()
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_blob(path)
|
82
|
+
url = generate_request_uri(nil, path)
|
83
|
+
request = generate_request("GET", url)
|
84
|
+
request.execute()
|
85
|
+
end
|
86
|
+
|
87
|
+
def delete_blob(path)
|
88
|
+
url = generate_request_uri(nil, path)
|
89
|
+
request = generate_request("DELETE", url)
|
90
|
+
request.execute()
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_blob_properties(path)
|
94
|
+
url = generate_request_uri(nil, path)
|
95
|
+
request = generate_request("HEAD", url)
|
96
|
+
request.execute().headers
|
97
|
+
end
|
98
|
+
|
99
|
+
def set_blob_properties(path, properties ={})
|
100
|
+
url = generate_request_uri("metadata", path)
|
101
|
+
request = generate_request("PUT", url, properties)
|
102
|
+
request.execute()
|
103
|
+
end
|
104
|
+
|
105
|
+
def generate_request(verb, url, headers = {}, payload = nil)
|
106
|
+
http_headers = {}
|
107
|
+
headers.each{ |k, v| http_headers[k.to_s.gsub(/_/, '-')] = v}
|
108
|
+
request = RestClient::Request.new(:method => verb.downcase.to_sym, :url => url, :headers => http_headers, :payload => payload)
|
109
|
+
request.headers["x-ms-Date"] = Time.new.httpdate
|
110
|
+
request.headers["Content-Length"] = (request.payload or "").length
|
111
|
+
request.headers["Authorization"] = "SharedKey #{account_name}:#{generate_signature(request)}"
|
112
|
+
return request
|
113
|
+
end
|
114
|
+
|
115
|
+
def generate_request_uri(operation = nil, path = nil, options = {})
|
116
|
+
protocol = use_ssl ? "https" : "http"
|
117
|
+
query_params = options.keys.sort{ |a, b| a.to_s <=> b.to_s}.map{ |k| "#{k.to_s.gsub(/_/, '')}=#{options[k]}"}.join("&") unless options.empty?
|
118
|
+
uri = "#{protocol}://#{account_name}.#{base_url}#{(path or "").start_with?("/") ? "" : "/"}#{(path or "")}#{operation ? "?comp=" + operation : ""}"
|
119
|
+
uri << "#{operation ? "&" : "?"}#{query_params}" if query_params
|
120
|
+
return uri
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.canonicalize_headers(headers)
|
124
|
+
cannonicalized_headers = headers.keys.select {|h| h.to_s.start_with? 'x-ms'}.map{ |h| "#{h.downcase.strip}:#{headers[h].strip}" }.sort{ |a, b| a <=> b }.join("\x0A")
|
125
|
+
return cannonicalized_headers
|
126
|
+
end
|
127
|
+
|
128
|
+
def canonicalize_message(url)
|
129
|
+
uri_component = url.gsub(/https?:\/\/[^\/]+\//i, '').scan(/([^&]+)/i).first()
|
130
|
+
cannonicalized_message = "/#{self.account_name}/#{uri_component}"
|
131
|
+
end
|
132
|
+
|
133
|
+
def generate_signature(request)
|
134
|
+
signature = request.method.to_s.upcase + "\x0A" +
|
135
|
+
(request.headers["Content-MD5"] or "") + "\x0A" +
|
136
|
+
(request.headers["Content-Type"] or "") + "\x0A" +
|
137
|
+
(request.headers["Date"] or "")+ "\x0A" +
|
138
|
+
self.class.canonicalize_headers(request.headers) + "\x0A" +
|
139
|
+
canonicalize_message(request.url)
|
140
|
+
return Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.account_key)).update(signature.toutf8).digest)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/waz-blobs.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'cgi'
|
3
|
+
require 'base64'
|
4
|
+
require 'rexml/document'
|
5
|
+
require 'rexml/xpath'
|
6
|
+
|
7
|
+
require 'restclient'
|
8
|
+
require 'hmac-sha2'
|
9
|
+
|
10
|
+
$:.unshift(File.dirname(__FILE__))
|
11
|
+
|
12
|
+
require 'waz/blobs/base'
|
13
|
+
require 'waz/blobs/blob_object'
|
14
|
+
require 'waz/blobs/container'
|
15
|
+
require 'waz/blobs/exceptions'
|
16
|
+
require 'waz/blobs/service'
|
17
|
+
require 'waz/blobs/version'
|
18
|
+
|
19
|
+
|
data/rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'lib/waz-blobs'
|
6
|
+
|
7
|
+
|
8
|
+
namespace :test do
|
9
|
+
Spec::Rake::SpecTask.new('run_with_rcov') do |t|
|
10
|
+
puts "running tests and code coverage"
|
11
|
+
t.spec_files = FileList['tests/waz/*.rb']
|
12
|
+
t.rcov = true
|
13
|
+
t.rcov_opts = ['--text-report', '--exclude', ".gem,test,Library,#{ENV['GEM_HOME']}", '--sort', 'coverage' ]
|
14
|
+
t.spec_opts = ['-cfn']
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
namespace :dist do
|
19
|
+
spec = Gem::Specification.new do |s|
|
20
|
+
s.name = 'waz-blobs'
|
21
|
+
s.version = Gem::Version.new(WAZ::Blobs::Version)
|
22
|
+
s.summary = "Client library for Windows Azure's Blob Storage Service's REST API"
|
23
|
+
s.description = s.summary
|
24
|
+
s.email = 'johnny.halife@me.com'
|
25
|
+
s.author = 'Johnny G. Halife'
|
26
|
+
s.homepage = 'http://github.com/johnnyhalife/waz-blobs'
|
27
|
+
s.require_paths = ["lib"]
|
28
|
+
s.files = FileList['rakefile', 'lib/**/*.rb']
|
29
|
+
s.test_files = Dir['test/**/*']
|
30
|
+
s.has_rdoc = false
|
31
|
+
|
32
|
+
# Dependencies
|
33
|
+
s.add_runtime_dependency 'rest-client', '>= 1.0.3'
|
34
|
+
s.add_dependency 'ruby-hmac'
|
35
|
+
end
|
36
|
+
|
37
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
38
|
+
pkg.need_tar = true
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: waz-blobs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Johnny G. Halife
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-05 00:00:00 -03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rest-client
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ruby-hmac
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: Client library for Windows Azure's Blob Storage Service's REST API
|
36
|
+
email: johnny.halife@me.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- rakefile
|
45
|
+
- lib/waz/blobs/base.rb
|
46
|
+
- lib/waz/blobs/blob_object.rb
|
47
|
+
- lib/waz/blobs/container.rb
|
48
|
+
- lib/waz/blobs/exceptions.rb
|
49
|
+
- lib/waz/blobs/service.rb
|
50
|
+
- lib/waz/blobs/version.rb
|
51
|
+
- lib/waz-blobs.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/johnnyhalife/waz-blobs
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.3.5
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Client library for Windows Azure's Blob Storage Service's REST API
|
80
|
+
test_files: []
|
81
|
+
|