waz-blobs 0.1.0

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