amaze_sns 1.3.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +26 -14
- data/lib/amaze/request.rb +18 -28
- data/lib/amaze/topic.rb +40 -31
- data/lib/amaze_sns.rb +31 -32
- data/spec/amaze_sns_spec.rb +123 -77
- data/spec/em_http_request_spec_helper.rb +74 -0
- data/spec/request_spec.rb +0 -0
- data/spec/spec.opts +0 -1
- data/spec/spec_helper.rb +21 -39
- data/spec/subscription_spec.rb +0 -0
- data/spec/topic_spec.rb +495 -5
- metadata +23 -57
data/README.md
CHANGED
@@ -2,26 +2,31 @@ Amazon SNS gem
|
|
2
2
|
==============
|
3
3
|
|
4
4
|
Introduction
|
5
|
-
|
5
|
+
------------
|
6
6
|
|
7
7
|
A Ruby gem for use with the Amazon Simple Notification service (http://aws.amazon.com/sns/).
|
8
8
|
|
9
9
|
Usage
|
10
|
-
|
10
|
+
------
|
11
|
+
|
12
|
+
### Quickstart Guide ###
|
11
13
|
|
12
|
-
gem install amaze_sns
|
14
|
+
gem install amaze_sns
|
13
15
|
|
14
|
-
require 'amaze-sns'
|
16
|
+
require 'amaze-sns'
|
15
17
|
|
16
|
-
AmazeSNS.skey =
|
18
|
+
AmazeSNS.skey = 'your amazon aws secret key'
|
17
19
|
|
18
|
-
AmazeSNS.akey =
|
20
|
+
AmazeSNS.akey = 'your amazon aws access key'
|
21
|
+
|
22
|
+
# add new topic to local hash and to SNS
|
23
|
+
# You need to call create in order to access the ARN of the topic
|
24
|
+
AmazeSNS['your_topic_name'].create
|
25
|
+
|
26
|
+
AmazeSNS['your_topic_name'].delete # removes it from both SNS and local hash
|
19
27
|
|
20
|
-
AmazeSNS
|
21
|
-
AmazeSNS['your_topic_name'].create # add new topic to local hash and to SNS
|
22
|
-
AmazeSNS['your_topic_name'].delete # removes it from both SNS and local hash
|
28
|
+
AmazeSNS.logger = my_logger # set a logger for the response`
|
23
29
|
|
24
|
-
AmazeSNS.logger = my_logger # set a logger for the response
|
25
30
|
|
26
31
|
Dependencies
|
27
32
|
---------------
|
@@ -34,14 +39,21 @@ higher than 0.2.10 although the gemspec has been updated to reflect this
|
|
34
39
|
|
35
40
|
|
36
41
|
Tests
|
37
|
-
|
42
|
+
------
|
38
43
|
The specs are partly working at the moment as the gem is still under development
|
39
44
|
|
40
45
|
The gem itself has been tested on systems running ruby 1.8.6, 1.8.7, 1.9.2
|
41
46
|
|
42
47
|
|
48
|
+
License
|
49
|
+
-------
|
50
|
+
|
51
|
+
(The MIT License)
|
52
|
+
|
53
|
+
Copyright © 2010 Chee Yeo, 29 Steps UK
|
54
|
+
|
55
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
43
56
|
|
44
|
-
|
45
|
-
---------
|
57
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
46
58
|
|
47
|
-
|
59
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/amaze/request.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
require "rubygems"
|
2
2
|
require 'crack/xml'
|
3
|
+
require 'ruby-debug'
|
3
4
|
|
4
5
|
require File.dirname(__FILE__) + "/helpers"
|
5
6
|
require File.dirname(__FILE__) + "/exceptions"
|
@@ -8,30 +9,12 @@ require 'em-http'
|
|
8
9
|
|
9
10
|
|
10
11
|
class Request
|
12
|
+
include EM::Deferrable
|
11
13
|
|
12
|
-
attr_accessor :params, :
|
14
|
+
attr_accessor :params, :httpresponse
|
13
15
|
|
14
|
-
def initialize(params
|
16
|
+
def initialize(params)
|
15
17
|
@params = params
|
16
|
-
@options = options
|
17
|
-
end
|
18
|
-
|
19
|
-
def process2
|
20
|
-
query_string = canonical_querystring(@params)
|
21
|
-
string_to_sign = "GET
|
22
|
-
#{AmazeSNS.host}
|
23
|
-
/
|
24
|
-
#{query_string}"
|
25
|
-
|
26
|
-
hmac = HMAC::SHA256.new(AmazeSNS.skey)
|
27
|
-
hmac.update( string_to_sign )
|
28
|
-
signature = Base64.encode64(hmac.digest).chomp
|
29
|
-
|
30
|
-
params['Signature'] = signature
|
31
|
-
querystring2 = params.collect { |key, value| [url_encode(key), url_encode(value)].join("=") }.join('&') # order doesn't matter for the actual request
|
32
|
-
response = HttpClient.get "#{AmazeSNS.host}?#{querystring2}"
|
33
|
-
parsed_response = Crack::XML.parse(response)
|
34
|
-
return parsed_response
|
35
18
|
end
|
36
19
|
|
37
20
|
def process
|
@@ -55,10 +38,13 @@ string_to_sign = "GET
|
|
55
38
|
|
56
39
|
require 'em-http' unless defined?(EventMachine::HttpRequest)
|
57
40
|
|
58
|
-
@httpresponse ||=
|
59
|
-
|
60
|
-
|
61
|
-
|
41
|
+
@httpresponse ||= http_class.new("http://#{AmazeSNS.host}/?").get({
|
42
|
+
:query => querystring2, :timeout => 2
|
43
|
+
})
|
44
|
+
|
45
|
+
# a bit misleading but call is still successful even if the status code is not 200
|
46
|
+
@httpresponse.callback{ success_callback }
|
47
|
+
@httpresponse.errback{ error_callback }
|
62
48
|
end
|
63
49
|
|
64
50
|
def http_class
|
@@ -68,6 +54,8 @@ string_to_sign = "GET
|
|
68
54
|
|
69
55
|
def success_callback
|
70
56
|
case @httpresponse.response_header.status
|
57
|
+
when 200
|
58
|
+
self.succeed(@httpresponse)
|
71
59
|
when 403
|
72
60
|
raise AuthorizationError
|
73
61
|
when 500
|
@@ -77,12 +65,14 @@ string_to_sign = "GET
|
|
77
65
|
when 404
|
78
66
|
raise NotFoundError
|
79
67
|
else
|
80
|
-
|
68
|
+
self.fail("Call to Amazon SNS API failed")
|
81
69
|
end #end case
|
82
70
|
end
|
83
71
|
|
84
72
|
def call_user_success_handler
|
85
|
-
@options[:on_success]
|
73
|
+
#puts "#{@options[:on_success]}"
|
74
|
+
@options[:on_success].call(@httpresponse) if @options[:on_success].respond_to?(:call)
|
75
|
+
#self.succeed(@httpresponse)
|
86
76
|
end
|
87
77
|
|
88
78
|
def error_callback
|
data/lib/amaze/topic.rb
CHANGED
@@ -5,18 +5,26 @@ require "json"
|
|
5
5
|
|
6
6
|
class Topic
|
7
7
|
|
8
|
-
attr_accessor :topic, :arn, :
|
8
|
+
attr_accessor :topic, :arn, :attributes
|
9
9
|
|
10
10
|
def initialize(topic, arn='')
|
11
11
|
@topic = topic
|
12
12
|
@arn = arn
|
13
|
-
@
|
13
|
+
@attributes = {}
|
14
14
|
end
|
15
15
|
|
16
|
-
def generate_request(params
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
def generate_request(params)
|
17
|
+
request = Request.new(params)
|
18
|
+
request.process
|
19
|
+
|
20
|
+
request.callback do |data|
|
21
|
+
yield data
|
22
|
+
end
|
23
|
+
|
24
|
+
request.errback do |resp|
|
25
|
+
puts "ERROR - #{resp.inspect}"
|
26
|
+
EM.stop
|
27
|
+
end
|
20
28
|
end
|
21
29
|
|
22
30
|
# for running th EM loop w/o repetitions
|
@@ -80,7 +88,7 @@ class Topic
|
|
80
88
|
def attrs
|
81
89
|
outcome = nil
|
82
90
|
params = {
|
83
|
-
'TopicArn' => "#{arn}",
|
91
|
+
'TopicArn' => "#{@arn}",
|
84
92
|
'Action' => 'GetTopicAttributes',
|
85
93
|
'SignatureMethod' => 'HmacSHA256',
|
86
94
|
'SignatureVersion' => 2,
|
@@ -93,6 +101,7 @@ class Topic
|
|
93
101
|
parsed_response = Crack::XML.parse(response.response)
|
94
102
|
res = parsed_response['GetTopicAttributesResponse']['GetTopicAttributesResult']['Attributes']["entry"]
|
95
103
|
outcome = make_hash(res) #res["entry"] is an array of hashes - need to turn it into hash with key value
|
104
|
+
self.attributes = outcome
|
96
105
|
EM.stop
|
97
106
|
end
|
98
107
|
}
|
@@ -108,6 +117,7 @@ class Topic
|
|
108
117
|
|
109
118
|
def set_attrs(opts)
|
110
119
|
outcome = nil
|
120
|
+
#TODO: check format of opts to make sure they are compilant with API
|
111
121
|
params = {
|
112
122
|
'AttributeName' => "#{opts[:name]}",
|
113
123
|
'AttributeValue' => "#{opts[:value]}",
|
@@ -123,6 +133,8 @@ class Topic
|
|
123
133
|
generate_request(params) do |response|
|
124
134
|
parsed_response = Crack::XML.parse(response.response)
|
125
135
|
outcome = parsed_response['SetTopicAttributesResponse']['ResponseMetadata']['RequestId']
|
136
|
+
# update the attributes hash if request is successful ...
|
137
|
+
self.attributes["#{opts[:name]}"] = "#{opts[:value]}" if response.response_header.status == 200
|
126
138
|
EM.stop
|
127
139
|
end
|
128
140
|
}
|
@@ -134,7 +146,7 @@ class Topic
|
|
134
146
|
raise InvalidOptions unless ( !(opts.empty?) && opts.instance_of?(Hash) )
|
135
147
|
res=''
|
136
148
|
params = {
|
137
|
-
'TopicArn' => "#{arn}",
|
149
|
+
'TopicArn' => "#{@arn}",
|
138
150
|
'Endpoint' => "#{opts[:endpoint]}",
|
139
151
|
'Protocol' => "#{opts[:protocol]}",
|
140
152
|
'Action' => 'Subscribe',
|
@@ -179,9 +191,9 @@ class Topic
|
|
179
191
|
|
180
192
|
# grabs list of subscriptions for this topic only
|
181
193
|
def subscriptions
|
182
|
-
|
194
|
+
res=nil
|
183
195
|
params = {
|
184
|
-
'TopicArn' => "#{arn}",
|
196
|
+
'TopicArn' => "#{@arn}",
|
185
197
|
'Action' => 'ListSubscriptionsByTopic',
|
186
198
|
'SignatureMethod' => 'HmacSHA256',
|
187
199
|
'SignatureVersion' => 2,
|
@@ -195,23 +207,21 @@ class Topic
|
|
195
207
|
arr = parsed_response['ListSubscriptionsByTopicResponse']['ListSubscriptionsByTopicResult']['Subscriptions']['member'] unless (parsed_response['ListSubscriptionsByTopicResponse']['ListSubscriptionsByTopicResult']['Subscriptions'].nil?)
|
196
208
|
|
197
209
|
if !(arr.nil?) && (arr.instance_of?(Array))
|
198
|
-
|
199
|
-
nh = arr.inject({}) do |h,v|
|
200
|
-
key = v["SubscriptionArn"].to_s
|
201
|
-
value = v
|
202
|
-
h[key.to_s] = value
|
203
|
-
h
|
204
|
-
end
|
210
|
+
res = arr
|
205
211
|
elsif !(arr.nil?) && (arr.instance_of?(Hash))
|
206
212
|
# to deal with one subscription issue
|
213
|
+
nh={}
|
207
214
|
key = arr["SubscriptionArn"]
|
208
215
|
arr.delete("SubscriptionArn")
|
209
216
|
nh[key.to_s] = arr
|
217
|
+
res = nh
|
218
|
+
else
|
219
|
+
res = []
|
210
220
|
end
|
211
221
|
EM.stop
|
212
222
|
end
|
213
223
|
}
|
214
|
-
|
224
|
+
res
|
215
225
|
end
|
216
226
|
|
217
227
|
# The AddPermission action adds a statement to a topic's access control policy, granting access for the
|
@@ -221,7 +231,7 @@ class Topic
|
|
221
231
|
raise InvalidOptions unless ( !(opts.empty?) && opts.instance_of?(Hash) )
|
222
232
|
res=''
|
223
233
|
params = {
|
224
|
-
'TopicArn' => "#{arn}",
|
234
|
+
'TopicArn' => "#{@arn}",
|
225
235
|
'Label' => "#{opts[:label]}",
|
226
236
|
'ActionName.member.1' => "#{opts[:action_name]}",
|
227
237
|
'AWSAccountId.member.1' => "#{opts[:account_id]}",
|
@@ -247,7 +257,7 @@ class Topic
|
|
247
257
|
raise InvalidOptions unless ( !(label.empty?) && label.instance_of?(String) )
|
248
258
|
res=''
|
249
259
|
params = {
|
250
|
-
'TopicArn' => "#{arn}",
|
260
|
+
'TopicArn' => "#{@arn}",
|
251
261
|
'Label' => "#{label}",
|
252
262
|
'Action' => 'RemovePermission',
|
253
263
|
'SignatureMethod' => 'HmacSHA256',
|
@@ -272,7 +282,7 @@ class Topic
|
|
272
282
|
res=''
|
273
283
|
params = {
|
274
284
|
'Subject' => subject,
|
275
|
-
'TopicArn' => "#{arn}",
|
285
|
+
'TopicArn' => "#{@arn}",
|
276
286
|
"Message" => "#{msg}",
|
277
287
|
'Action' => 'Publish',
|
278
288
|
'SignatureMethod' => 'HmacSHA256',
|
@@ -318,17 +328,16 @@ class Topic
|
|
318
328
|
|
319
329
|
|
320
330
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
331
|
+
private
|
332
|
+
def make_hash(arr)
|
333
|
+
hash = arr.inject({}) do |h, v|
|
334
|
+
(v["key"] == "Policy")? value = JSON.parse(v["value"]) : value = v["value"]
|
335
|
+
key = v["key"].to_s
|
336
|
+
h[key] = value
|
337
|
+
h
|
338
|
+
end
|
339
|
+
hash
|
328
340
|
end
|
329
|
-
|
330
|
-
hash
|
331
|
-
end
|
332
341
|
|
333
342
|
|
334
343
|
|
data/lib/amaze_sns.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
autoload 'Logger', 'logger'
|
2
2
|
|
3
|
-
|
4
3
|
require File.dirname(__FILE__) + "/amaze/topic"
|
5
4
|
require File.dirname(__FILE__) + "/amaze/subscription"
|
6
5
|
require File.dirname(__FILE__) + "/amaze/helpers"
|
@@ -18,13 +17,19 @@ class AmazeSNS
|
|
18
17
|
end
|
19
18
|
|
20
19
|
class << self
|
21
|
-
attr_accessor :host, :
|
22
|
-
|
20
|
+
attr_accessor :host, :topics, :skey, :akey, :subscriptions, :logger
|
21
|
+
|
22
|
+
def logger
|
23
|
+
@logger ||= begin
|
24
|
+
log = Logger.new(STDOUT)
|
25
|
+
log.level = Logger::INFO
|
26
|
+
log
|
27
|
+
end
|
28
|
+
end
|
23
29
|
end
|
24
|
-
|
25
30
|
|
31
|
+
|
26
32
|
self.host = 'sns.us-east-1.amazonaws.com'
|
27
|
-
self.logger = Logger.new($STDOUT)
|
28
33
|
self.skey = ''
|
29
34
|
self.akey=''
|
30
35
|
self.topics ||= {}
|
@@ -40,61 +45,59 @@ class AmazeSNS
|
|
40
45
|
def self.method_missing(id, *args, &blk)
|
41
46
|
case(id.to_s)
|
42
47
|
when /^list_(.*)/
|
43
|
-
send(:process_query, $1, &
|
48
|
+
send(:process_query, $1, &Proc.new)
|
44
49
|
when /^refresh_(.*)/
|
45
|
-
send(:process_query, $1
|
50
|
+
send(:process_query, $1)
|
46
51
|
else
|
47
|
-
#super
|
48
52
|
raise NoMethodError
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
52
|
-
def self.process_query(type
|
53
|
-
# p "INSIDE PROCESS query"
|
56
|
+
def self.process_query(type,&prc)
|
54
57
|
type = type.capitalize
|
55
58
|
params = {
|
56
59
|
'Action' => "List#{type}",
|
57
60
|
'SignatureMethod' => 'HmacSHA256',
|
58
61
|
'SignatureVersion' => 2,
|
59
|
-
'Timestamp' => Time.now.iso8601,
|
62
|
+
'Timestamp' => Time.now.iso8601, #Time.now.iso8601 makes tests fail
|
60
63
|
'AWSAccessKeyId' => @akey
|
61
64
|
}
|
62
65
|
|
63
|
-
|
66
|
+
request = Request.new(params)
|
67
|
+
request.process
|
64
68
|
|
65
|
-
|
66
|
-
|
67
|
-
else
|
68
|
-
prc = default_prc
|
69
|
+
request.callback do |data|
|
70
|
+
yield data
|
69
71
|
end
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
Request.new(params, req_options).process
|
76
|
-
}
|
73
|
+
request.errback do |resp|
|
74
|
+
puts "ERROR - #{resp.inspect}"
|
75
|
+
EM.stop
|
76
|
+
end
|
77
77
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def self.default_prc
|
81
81
|
prc = Proc.new do |resp|
|
82
82
|
parsed_response = Crack::XML.parse(resp.response)
|
83
|
-
#p "SUB RESPONSE: #{parsed_response.inspect}"
|
84
83
|
self.process_response(parsed_response)
|
85
84
|
EM.stop
|
86
85
|
end
|
87
86
|
end
|
88
87
|
|
88
|
+
def self.process_data(resp)
|
89
|
+
parsed_response = Crack::XML.parse(resp.response)
|
90
|
+
self.process_response(parsed_response)
|
91
|
+
EM.stop
|
92
|
+
end
|
93
|
+
|
89
94
|
def self.process_response(resp)
|
90
95
|
kind = (resp.has_key?("ListTopicsResponse"))? "Topics" : "Subscriptions"
|
91
96
|
cla = (resp.has_key?("ListTopicsResponse"))? "Topic" : "Subscription"
|
92
|
-
|
93
|
-
|
97
|
+
|
94
98
|
result = resp["List#{kind}Response"]["List#{kind}Result"]["#{kind}"]
|
95
99
|
if result.nil?
|
96
|
-
|
97
|
-
return nil
|
100
|
+
nil
|
98
101
|
else
|
99
102
|
results = result["member"]
|
100
103
|
end
|
@@ -120,11 +123,9 @@ class AmazeSNS
|
|
120
123
|
when "Topic"
|
121
124
|
@collection[label].arn = t["TopicArn"]
|
122
125
|
when "Subscription"
|
123
|
-
#@collection[label] = t["TopicArn"]
|
124
126
|
sub = Subscription.new(t)
|
125
127
|
@collection[label] << sub unless @collection[label].detect{|x| x.subarn == sub.subarn}
|
126
128
|
end
|
127
|
-
#@collection[label].arn = t["TopicArn"]
|
128
129
|
end
|
129
130
|
end
|
130
131
|
elsif (results.instance_of?(Hash))
|
@@ -136,11 +137,9 @@ class AmazeSNS
|
|
136
137
|
when "Subscription"
|
137
138
|
@collection[label] = Subscription.new(results)
|
138
139
|
end
|
139
|
-
|
140
|
-
#@collection[label] = Kernel.const_get("#{cla}").new(results)
|
141
140
|
end
|
142
141
|
else
|
143
|
-
|
142
|
+
nil
|
144
143
|
end # end outer if
|
145
144
|
end
|
146
145
|
|