waz-blobs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,11 @@
1
+ module WAZ
2
+ module Blobs
3
+ module VERSION #:nodoc:
4
+ MAJOR = '0'
5
+ MINOR = '1'
6
+ TINY = '0'
7
+ end
8
+
9
+ Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY].compact * '.'
10
+ end
11
+ 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
+