aliyun-ess 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6220299fa48afc6abb759b80526dac2889b0052c
4
+ data.tar.gz: 6c6b2757addd2ae6314f5951823c3c16b2f7a2aa
5
+ SHA512:
6
+ metadata.gz: ca60cb188e0e809e4169a58d25486f591ef59e8800fb52e0a306961017e7613734d9aa67413a1b95814be5e2c725331d503a75e602cdc19644c34b1810a09785
7
+ data.tar.gz: af36c635c32689e74a812381008171e674e5d837e653743d2a61f7726355509ea6f07d03f55dd8ec5cbc3510d1de455885861a0895318d50e759d729b6813c9b
@@ -0,0 +1,5 @@
1
+ head:
2
+
3
+ 0.1.0:
4
+
5
+ - Initial public release
data/INSTALL ADDED
@@ -0,0 +1,13 @@
1
+ == Rubygems
2
+
3
+ The easiest way to install aliyun/oss is with Rubygems:
4
+
5
+ % gem i aliyun-ess -ry
6
+
7
+ == Directly from git
8
+
9
+ % git clone git://github.com/sunrisela/aliyun-ess-sdk-for-ruby.git
10
+
11
+ == Dependencies
12
+
13
+ Aliyun::ESS requires Ruby 1.9+.
data/bin/ess ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ lib = File.dirname(__FILE__) + '/../lib/aliyun/ess'
3
+ setup = File.dirname(__FILE__) + '/setup'
4
+ irb_name = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb'
5
+
6
+ exec "#{irb_name} -r #{lib} -r #{setup} "
@@ -0,0 +1,11 @@
1
+ # -*- encoding : utf-8 -*-
2
+ #!/usr/bin/env ruby
3
+ if ENV['ACCESS_KEY_ID'] && ENV['SECRET_ACCESS_KEY']
4
+ Aliyun::ESS::Base.establish_connection!(
5
+ :access_key_id => ENV['ACCESS_KEY_ID'],
6
+ :secret_access_key => ENV['SECRET_ACCESS_KEY']
7
+ )
8
+ end
9
+
10
+ #require File.dirname(__FILE__) + '/../test/fixtures'
11
+ include Aliyun::ESS
@@ -0,0 +1,33 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'cgi'
3
+ require 'uri'
4
+ require 'openssl'
5
+ require 'digest/sha1'
6
+ require 'net/https'
7
+ require 'time'
8
+ require 'date'
9
+ require 'open-uri'
10
+ require 'yajl/json_gem'
11
+ require 'rack/utils'
12
+ require 'byebug'
13
+
14
+ $:.unshift(File.dirname(__FILE__))
15
+
16
+ require 'ess/version'
17
+ require 'ess/extensions' unless defined? Aliyun::OSS
18
+ require 'ess/exceptions'
19
+ require 'ess/error'
20
+ require 'ess/authentication'
21
+ require 'ess/connection'
22
+ require 'ess/parsing'
23
+ require 'ess/base'
24
+ require 'ess/service'
25
+ require 'ess/collection'
26
+ require 'ess/scaling_group'
27
+ require 'ess/scaling_rule'
28
+ require 'ess/response'
29
+
30
+
31
+ Aliyun::ESS::Base.class_eval do
32
+ include Aliyun::ESS::Connection::Management
33
+ end
@@ -0,0 +1,121 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Aliyun
3
+ module ESS
4
+ class Authentication
5
+ class Signature < String #:nodoc:
6
+ attr_reader :params, :access_key_id, :secret_access_key, :options
7
+
8
+ def initialize(params, access_key_id, secret_access_key, options = {})
9
+ super()
10
+ @params, @access_key_id, @secret_access_key = params, access_key_id, secret_access_key
11
+ @options = options
12
+ end
13
+
14
+ private
15
+
16
+ def escape_string(str)
17
+ CGI.escape(str).gsub(/\+/,'%20').gsub(/%7E/, '~')
18
+ end
19
+
20
+ def sign_string
21
+ StringToSign.new(canonical_string)
22
+ end
23
+
24
+ def canonical_string
25
+ @canonical_string ||= CanonicalString.new(params, access_key_id, {})
26
+ end
27
+
28
+ def encoded_canonical
29
+ digest = OpenSSL::Digest::Digest.new('sha1')
30
+ b64_hmac = [OpenSSL::HMAC.digest(digest, secret_access_key+'&', sign_string)].pack("m").strip
31
+ url_encode? ? CGI.escape(b64_hmac) : b64_hmac
32
+ end
33
+
34
+ def url_encode?
35
+ !@options[:url_encode].nil?
36
+ end
37
+ end
38
+
39
+ class QueryString < Signature
40
+ def initialize(*args)
41
+ super
42
+ self << build
43
+ end
44
+
45
+ private
46
+
47
+ def build
48
+ "#{canonical_string}&Signature=#{escape_string(encoded_canonical)}"
49
+ end
50
+ end
51
+
52
+ class StringToSign < String
53
+ attr_reader :canonicalized_query_string
54
+ def initialize(canonicalized_query_string)
55
+ super()
56
+ @canonicalized_query_string = canonicalized_query_string
57
+ build
58
+ end
59
+
60
+ private
61
+
62
+ def build
63
+ self << 'GET'
64
+ self << '&%2F&'
65
+ self << escape_string(canonicalized_query_string)
66
+ end
67
+
68
+ def escape_string(str)
69
+ CGI.escape(str).gsub(/\+/,'%20').gsub(/%7E/, '~')
70
+ end
71
+ end
72
+
73
+ class CanonicalString < String
74
+ DEFAULT_PARAMS = {
75
+ 'Format' => 'JSON',
76
+ 'SignatureMethod' => 'HMAC-SHA1',
77
+ 'SignatureVersion' => '1.0',
78
+ 'Version' => '2014-08-28'
79
+ }
80
+
81
+ attr_reader :params, :query
82
+
83
+ def initialize(params, access_key_id, options = {})
84
+ super()
85
+ @params = params
86
+ @access_key_id = access_key_id
87
+ @options = options
88
+ self << build
89
+ end
90
+
91
+ private
92
+
93
+ def build
94
+ initialize_query
95
+
96
+ canonicalized_query_string = query.sort_by{|k, _| k}.map do |key, value|
97
+ value = value.to_s.strip
98
+ escaped_value = escape_string(value)
99
+ "#{key}=#{escaped_value}"
100
+ end.join('&')
101
+
102
+ canonicalized_query_string
103
+ end
104
+
105
+ def escape_string(str)
106
+ CGI.escape(str).gsub(/\+/,'%20').gsub(/%7E/, '~')
107
+ end
108
+
109
+ def initialize_query
110
+ @query = { 'AccessKeyId' => @access_key_id }
111
+ @query = params.merge(@query)
112
+ @query = DEFAULT_PARAMS.merge('SignatureNonce' => random_string, 'Timestamp' => Time.now.utc.iso8601).merge(@query)
113
+ end
114
+
115
+ def random_string(len=32)
116
+ rand(36**(len-1)..36**(len)).to_s 36
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,128 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Aliyun
3
+ module ESS
4
+ DEFAULT_HOST = 'ess.aliyuncs.com'
5
+ class Base
6
+ class << self
7
+ # Wraps the current connection's request method and picks the appropriate response class to wrap the response in.
8
+ # If the response is an error, it will raise that error as an exception. All such exceptions can be caught by rescuing
9
+ # their superclass, the ResponseError exception class.
10
+ #
11
+ # It is unlikely that you would call this method directly. Subclasses of Base have convenience methods for each http request verb
12
+ # that wrap calls to request.
13
+ def request(verb, path, params = {}, options = {}, body = nil, attempts = 0, &block)
14
+ Service.response = nil
15
+ process_params!(params, verb)
16
+ response = response_class.new(connection.request(verb, path, params, options, body, attempts, &block))
17
+ Service.response = response
18
+
19
+ Error::Response.new(response.response).error.raise if response.error?
20
+ response
21
+ # Once in a while, a request to OSS returns an internal error. A glitch in the matrix I presume. Since these
22
+ # errors are few and far between the request method will rescue InternalErrors the first three times they encouter them
23
+ # and will retry the request again. Most of the time the second attempt will work.
24
+ rescue InternalError, RequestTimeout
25
+ if attempts == 3
26
+ raise
27
+ else
28
+ attempts += 1
29
+ retry
30
+ end
31
+ end
32
+
33
+ [:get, :post, :put, :delete, :head].each do |verb|
34
+ class_eval(<<-EVAL, __FILE__, __LINE__)
35
+ def #{verb}(path, params = {}, headers = {}, body = nil, &block)
36
+ request(:#{verb}, path, params, headers, body, &block)
37
+ end
38
+ EVAL
39
+ end
40
+
41
+ private
42
+
43
+ def response_class
44
+ FindResponseClass.for(self)
45
+ end
46
+
47
+ def process_params!(params, verb)
48
+ params.replace(RequestParams.process(params, verb))
49
+ end
50
+
51
+ # Using the conventions layed out in the <tt>response_class</tt> works for more than 80% of the time.
52
+ # There are a few edge cases though where we want a given class to wrap its responses in different
53
+ # response classes depending on which method is being called.
54
+ def respond_with(klass)
55
+ eval(<<-EVAL, binding, __FILE__, __LINE__)
56
+ def new_response_class
57
+ #{klass}
58
+ end
59
+ class << self
60
+ alias_method :old_response_class, :response_class
61
+ alias_method :response_class, :new_response_class
62
+ end
63
+ EVAL
64
+ yield
65
+ ensure
66
+ # Restore the original version
67
+ eval(<<-EVAL, binding, __FILE__, __LINE__)
68
+ class << self
69
+ alias_method :response_class, :old_response_class
70
+ end
71
+ EVAL
72
+ end
73
+
74
+ class RequestParams < Hash #:nodoc:
75
+ attr_reader :options, :verb
76
+
77
+ class << self
78
+ def process(*args, &block)
79
+ new(*args, &block).process!
80
+ end
81
+ end
82
+
83
+ def initialize(options, verb = :get)
84
+ @options = options.inject({}) {|h, (k,v)| h[k.to_s.camelize] = v.to_s; h }
85
+ @verb = verb
86
+ super()
87
+ end
88
+
89
+ def process!
90
+ replace(options)
91
+ end
92
+ end
93
+ end
94
+
95
+ def initialize(attributes = {}) #:nodoc:
96
+ @attributes = attributes
97
+ end
98
+
99
+ private
100
+
101
+ attr_reader :attributes
102
+
103
+ def connection
104
+ self.class.connection
105
+ end
106
+
107
+ def http
108
+ connection.http
109
+ end
110
+
111
+ def request(*args, &block)
112
+ self.class.request(*args, &block)
113
+ end
114
+
115
+ def method_missing(method, *args, &block)
116
+ case
117
+ when attributes.has_key?(method.to_s)
118
+ attributes[method.to_s]
119
+ when attributes.has_key?(method)
120
+ attributes[method]
121
+ else
122
+ super
123
+ end
124
+ end
125
+
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Aliyun
3
+ module ESS
4
+ class Collection
5
+ include SelectiveAttributeProxy
6
+
7
+ attr_reader :response, :attributes
8
+
9
+ def initialize(response)
10
+ @response = response
11
+ @attributes = response.parsed.slice(*%W{page_number page_size total_count})
12
+ end
13
+
14
+ def items
15
+ @items ||= build_items!
16
+ end
17
+
18
+ def build_items!
19
+ item_class = eval response.class.name.sub(/::Response$/, '')
20
+ response.items.map{|e| item_class.new e }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,226 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Aliyun
3
+ module ESS
4
+ class Connection #:nodoc:
5
+ class << self
6
+ def connect(options = {})
7
+ new(options)
8
+ end
9
+
10
+ def prepare_path(path)
11
+ path = path.remove_extended unless path.valid_utf8?
12
+ URI.escape(path)
13
+ end
14
+ end
15
+
16
+ attr_reader :access_key_id, :secret_access_key, :http, :options
17
+
18
+ # Creates a new connection. Connections make the actual requests to OSS, though these requests are usually
19
+ # called from subclasses of Base.
20
+ #
21
+ # For details on establishing connections, check the Connection::Management::ClassMethods.
22
+ def initialize(options = {})
23
+ @options = Options.new(options)
24
+ connect
25
+ end
26
+
27
+ def request(verb, path, params = {}, headers = {}, body = nil, attempts = 0, &block)
28
+ body.rewind if body.respond_to?(:rewind) unless attempts.zero?
29
+
30
+ requester = Proc.new do
31
+ path = self.class.prepare_path(path) if attempts.zero? # Only escape the path once
32
+ params = ::Rack::Utils.parse_nested_query(URI(path).query).merge(params)
33
+
34
+ request_uri = "/?"+query_string_authentication(params)
35
+
36
+ puts request_uri
37
+
38
+ request = request_method(verb).new(request_uri, headers)
39
+ add_user_agent!(request)
40
+
41
+ if body
42
+ if body.respond_to?(:read)
43
+ request.body_stream = body
44
+ else
45
+ request.body = body
46
+ end
47
+ request.content_length = body.respond_to?(:lstat) ? body.stat.size : body.size
48
+ else
49
+ request.content_length = 0
50
+ end
51
+ http.request(request, &block)
52
+ end
53
+
54
+ if persistent?
55
+ http.start unless http.started?
56
+ requester.call
57
+ else
58
+ http.start(&requester)
59
+ end
60
+ rescue Errno::EPIPE, Timeout::Error, Errno::EINVAL, EOFError
61
+ @http = create_connection
62
+ attempts == 3 ? raise : (attempts += 1; retry)
63
+ end
64
+
65
+ def persistent?
66
+ options[:persistent]
67
+ end
68
+
69
+ def protocol(options = {})
70
+ # This always trumps http.use_ssl?
71
+ if options[:use_ssl] == false
72
+ 'http://'
73
+ elsif options[:use_ssl] || http.use_ssl?
74
+ 'https://'
75
+ else
76
+ 'http://'
77
+ end
78
+ end
79
+
80
+ private
81
+ def extract_keys!
82
+ missing_keys = []
83
+ extract_key = Proc.new {|key| options[key] || (missing_keys.push(key); nil)}
84
+ @access_key_id = extract_key[:access_key_id]
85
+ @secret_access_key = extract_key[:secret_access_key]
86
+ raise MissingAccessKey.new(missing_keys) unless missing_keys.empty?
87
+ end
88
+
89
+ def create_connection
90
+ http = Net::HTTP.new(options[:server], options[:port])
91
+ http.use_ssl = !options[:use_ssl].nil? || options[:port] == 443
92
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
93
+ http
94
+ end
95
+
96
+ def connect
97
+ extract_keys!
98
+ @http = create_connection
99
+ end
100
+
101
+ def port_string
102
+ default_port = options[:use_ssl] ? 443 : 80
103
+ http.port == default_port ? '' : ":#{http.port}"
104
+ end
105
+
106
+ # do authentication for now
107
+ def query_string_authentication(params)
108
+ Authentication::QueryString.new(params, access_key_id, secret_access_key)
109
+ end
110
+
111
+ def add_user_agent!(request)
112
+ request['User-Agent'] ||= "Aliyun::ESS/#{Version}"
113
+ end
114
+
115
+ def request_method(verb)
116
+ Net::HTTP.const_get(verb.to_s.capitalize)
117
+ end
118
+
119
+ def method_missing(method, *args, &block)
120
+ options[method] || super
121
+ end
122
+
123
+ module Management #:nodoc:
124
+ def self.included(base)
125
+ base.cattr_accessor :connections
126
+ base.connections = {}
127
+ base.extend ClassMethods
128
+ end
129
+
130
+ # Manage the creation and destruction of connections for Aliyun::OSS::Base and its subclasses. Connections are
131
+ # created with establish_connection!.
132
+ module ClassMethods
133
+ # Creates a new connection with which to make requests to the ESS servers for the calling class.
134
+ #
135
+ # Aliyun::ESS::Base.establish_connection!(:access_key_id => '...', :secret_access_key => '...')
136
+ #
137
+ # == Required arguments
138
+ #
139
+ # * <tt>:access_key_id</tt> - The access key id for your OSS account. Provided by Aliyun.
140
+ # * <tt>:secret_access_key</tt> - The secret access key for your OSS account. Provided by Aliyun.
141
+ #
142
+ # If any of these required arguments is missing, a MissingAccessKey exception will be raised.
143
+ #
144
+ # == Optional arguments
145
+ #
146
+ # * <tt>:server</tt> - The server to make requests to. You can use this to specify your bucket in the subdomain,
147
+ # or your own domain's cname if you are using virtual hosted buckets. Defaults to <tt>oss.aliyuncs.com</tt>.
148
+ # * <tt>:port</tt> - The port to the requests should be made on. Defaults to 80 or 443 if the <tt>:use_ssl</tt>
149
+ # argument is set.
150
+ # * <tt>:use_ssl</tt> - Whether requests should be made over SSL. If set to true, the <tt>:port</tt> argument
151
+ # will be implicitly set to 443, unless specified otherwise. Defaults to false.
152
+ # * <tt>:persistent</tt> - Whether to use a persistent connection to the server. Having this on provides around a two fold
153
+ # performance increase but for long running processes some firewalls may find the long lived connection suspicious and close the connection.
154
+ # If you run into connection errors, try setting <tt>:persistent</tt> to false. Defaults to false.
155
+ def establish_connection!(options = {})
156
+ # After you've already established the default connection, just specify
157
+ # the difference for subsequent connections
158
+ options = default_connection.options.merge(options) if connected?
159
+ connections[connection_name] = Connection.connect(options)
160
+ end
161
+
162
+ # Returns the connection for the current class, or Base's default connection if the current class does not
163
+ # have its own connection.
164
+ #
165
+ # If not connection has been established yet, NoConnectionEstablished will be raised.
166
+ def connection
167
+ if connected?
168
+ connections[connection_name] || default_connection
169
+ else
170
+ raise NoConnectionEstablished
171
+ end
172
+ end
173
+
174
+ # Returns true if a connection has been made yet.
175
+ def connected?
176
+ !connections.empty?
177
+ end
178
+
179
+ # Removes the connection for the current class. If there is no connection for the current class, the default
180
+ # connection will be removed.
181
+ def disconnect(name = connection_name)
182
+ name = default_connection unless connections.has_key?(name)
183
+ connection = connections[name]
184
+ connection.http.finish if connection.persistent?
185
+ connections.delete(name)
186
+ end
187
+
188
+ # Clears *all* connections, from all classes, with prejudice.
189
+ def disconnect!
190
+ connections.each_key {|connection| disconnect(connection)}
191
+ end
192
+
193
+ private
194
+ def connection_name
195
+ name
196
+ end
197
+
198
+ def default_connection_name
199
+ 'Aliyun::ESS::Base'
200
+ end
201
+
202
+ def default_connection
203
+ connections[default_connection_name]
204
+ end
205
+ end
206
+ end
207
+
208
+ class Options < Hash #:nodoc:
209
+ VALID_OPTIONS = [:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent].freeze
210
+
211
+ def initialize(options = {})
212
+ super()
213
+ validate(options)
214
+ replace(:server => DEFAULT_HOST, :port => (options[:use_ssl] ? 443 : 80))
215
+ merge!(options)
216
+ end
217
+
218
+ private
219
+ def validate(options)
220
+ invalid_options = options.keys - VALID_OPTIONS
221
+ raise InvalidConnectionOption.new(invalid_options) unless invalid_options.empty?
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end